@idonatedev/idonate-sdk 1.2.0-dev2 → 1.2.0-dev20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/constants.js +11 -2
  2. package/dist/esm/constants.js +11 -2
  3. package/dist/esm/idonate-client.js +18 -8
  4. package/dist/esm/recaptcha.d.ts +1 -0
  5. package/dist/esm/recaptcha.js +26 -2
  6. package/dist/esm/shared.js +9 -1
  7. package/dist/esm/tokenize/CardConnectTokenizer.d.ts +8 -1
  8. package/dist/esm/tokenize/CardConnectTokenizer.js +353 -104
  9. package/dist/esm/tokenize/PayPalTokenizer.d.ts +6 -0
  10. package/dist/esm/tokenize/PayPalTokenizer.js +66 -16
  11. package/dist/esm/tokenize/SpreedlyTokenizer.d.ts +17 -1
  12. package/dist/esm/tokenize/SpreedlyTokenizer.js +270 -138
  13. package/dist/esm/tokenize/Tokenizer.d.ts +6 -2
  14. package/dist/esm/tokenize/Tokenizer.js +24 -8
  15. package/dist/esm/tokenize/gateway-utils.d.ts +2 -0
  16. package/dist/esm/tokenize/gateway-utils.js +27 -0
  17. package/dist/esm/tokenize/index.d.ts +3 -2
  18. package/dist/esm/tokenize/index.js +1 -0
  19. package/dist/esm/tokenize/spreedly-secure.js +6 -2
  20. package/dist/esm/tokenize/styles.d.ts +1 -1
  21. package/dist/esm/tokenize/styles.js +20 -1
  22. package/dist/esm/tokenize/tokenizer-constants.d.ts +1 -0
  23. package/dist/esm/tokenize/tokenizer-constants.js +1 -0
  24. package/dist/esm/tokenize/tokenizer-utils.d.ts +4 -1
  25. package/dist/esm/tokenize/tokenizer-utils.js +77 -4
  26. package/dist/esm/tokenize/types.d.ts +33 -8
  27. package/dist/esm/typeAdapters.js +6 -4
  28. package/dist/esm/types.d.ts +4 -10
  29. package/dist/idonate-client.js +18 -8
  30. package/dist/recaptcha.d.ts +1 -0
  31. package/dist/recaptcha.js +27 -2
  32. package/dist/shared.js +9 -1
  33. package/dist/tokenize/CardConnectTokenizer.d.ts +8 -1
  34. package/dist/tokenize/CardConnectTokenizer.js +351 -105
  35. package/dist/tokenize/PayPalTokenizer.d.ts +6 -0
  36. package/dist/tokenize/PayPalTokenizer.js +66 -16
  37. package/dist/tokenize/SpreedlyTokenizer.d.ts +17 -1
  38. package/dist/tokenize/SpreedlyTokenizer.js +267 -135
  39. package/dist/tokenize/Tokenizer.d.ts +6 -2
  40. package/dist/tokenize/Tokenizer.js +24 -8
  41. package/dist/tokenize/gateway-utils.d.ts +2 -0
  42. package/dist/tokenize/gateway-utils.js +30 -0
  43. package/dist/tokenize/index.d.ts +3 -2
  44. package/dist/tokenize/index.js +3 -1
  45. package/dist/tokenize/spreedly-secure.js +6 -2
  46. package/dist/tokenize/styles.d.ts +1 -1
  47. package/dist/tokenize/styles.js +20 -1
  48. package/dist/tokenize/tokenizer-constants.d.ts +1 -0
  49. package/dist/tokenize/tokenizer-constants.js +2 -1
  50. package/dist/tokenize/tokenizer-utils.d.ts +4 -1
  51. package/dist/tokenize/tokenizer-utils.js +80 -4
  52. package/dist/tokenize/types.d.ts +33 -8
  53. package/dist/typeAdapters.js +6 -4
  54. package/dist/types.d.ts +4 -10
  55. package/package.json +33 -2
  56. package/umd/idonate-sdk.js +1 -1
@@ -21,16 +21,20 @@ const tokenizer_utils_1 = require("./tokenizer-utils");
21
21
  const SPREEDLY_SCRIPT_URL = 'https://core.spreedly.com/iframe/iframe-v1.min.js';
22
22
  const SPREEDLY_SCRIPT_ID = 'spreedly-iframe-script';
23
23
  class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
24
- constructor(environmentKey, containerId, styles, mode = 'credit_card', bankCountry = 'US', enableTestMode = false, layout = 'single-line') {
24
+ constructor(environmentKey, containerId, styles, mode = 'credit_card', bankCountry = 'US', enableTestMode = false, layout = 'single-line', responsiveBreakpoint = tokenizer_constants_1.RESPONSIVE_BREAKPOINT, customPlaceholders) {
25
25
  super();
26
26
  this.environmentKey = environmentKey;
27
27
  this.containerId = containerId;
28
28
  this.layout = layout;
29
+ this.customPlaceholders = customPlaceholders;
29
30
  this.bankCountry = 'US';
30
31
  this.enableTestMode = false;
32
+ this.effectiveLayout = 'single-line';
33
+ this.responsiveBreakpoint = tokenizer_constants_1.RESPONSIVE_BREAKPOINT;
31
34
  this.mode = mode;
32
35
  this.bankCountry = bankCountry;
33
36
  this.enableTestMode = enableTestMode;
37
+ this.responsiveBreakpoint = responsiveBreakpoint;
34
38
  if (mode === 'credit_card') {
35
39
  const SpreedlyFrame = window.SpreedlyPaymentFrame;
36
40
  if (SpreedlyFrame) {
@@ -66,40 +70,33 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
66
70
  }
67
71
  static create(gateway, container, config) {
68
72
  return __awaiter(this, void 0, void 0, function* () {
69
- var _a;
73
+ var _a, _b;
70
74
  if (container.mode !== 'bank_account') {
71
75
  yield SpreedlyTokenizer.ensureSpreedlyLoaded();
72
76
  }
73
- let environmentKey = (_a = gateway.config) === null || _a === void 0 ? void 0 : _a.environment_key;
77
+ let environmentKey = (_a = gateway.config) === null || _a === void 0 ? void 0 : _a.environmentKey;
74
78
  if (!environmentKey && config.clientConfig.spreedlyEnvironmentKey) {
75
79
  environmentKey = config.clientConfig.spreedlyEnvironmentKey;
76
80
  }
77
81
  if (!environmentKey) {
78
- environmentKey = '';
82
+ throw new Error('Spreedly environment key is required. Provide it via gateway.config.environmentKey or client config.');
79
83
  }
80
- const tokenizer = new SpreedlyTokenizer(environmentKey, container.containerId, container.styling, container.mode || 'credit_card', container.bankCountry || 'US', container.enableTestMode || false, container.layout || 'single-line');
84
+ const tokenizer = new SpreedlyTokenizer(environmentKey, container.containerId, container.styling, container.mode || 'credit_card', container.bankCountry || 'US', container.enableTestMode || false, container.layout || 'single-line', (_b = container.responsiveBreakpoint) !== null && _b !== void 0 ? _b : tokenizer_constants_1.RESPONSIVE_BREAKPOINT, container.placeholders);
81
85
  tokenizer.organizationId = config.organizationId;
82
86
  tokenizer.embedId = config.embedId;
83
87
  tokenizer.clientConfig = config.clientConfig;
84
88
  tokenizer.createInternalElements();
85
- let securityArgs;
86
89
  if (container.mode === 'credit_card' &&
87
90
  config.clientConfig.enableSpreedlySecureTokenization) {
88
91
  if (!config.organizationId || !config.embedId) {
89
92
  throw new Error('Secure tokenization is enabled but organizationId and embedId are required');
90
93
  }
91
- try {
92
- securityArgs = yield (0, spreedly_secure_1.fetchSpreedlySecurityArgs)(config.clientConfig, config.organizationId, config.embedId);
93
- }
94
- catch (error) {
95
- throw new Error(`Secure tokenization is enabled but failed to initialize: ${error instanceof Error ? error.message : 'Unknown error'}`);
96
- }
97
94
  }
98
- yield tokenizer.init(securityArgs);
95
+ yield tokenizer.init();
99
96
  return tokenizer;
100
97
  });
101
98
  }
102
- init(securityArgs) {
99
+ init() {
103
100
  return __awaiter(this, void 0, void 0, function* () {
104
101
  this.containerEl = document.getElementById(this.containerId);
105
102
  if (this.mode === 'bank_account') {
@@ -143,11 +140,12 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
143
140
  reject(new Error('Spreedly initialization timeout'));
144
141
  }, tokenizer_constants_1.INIT_TIMEOUT);
145
142
  this.spreedly.on('ready', () => {
143
+ var _a, _b;
146
144
  clearTimeout(timeout);
147
- this.spreedly.setPlaceholder('number', tokenizer_constants_1.PLACEHOLDERS.cardNumber);
145
+ this.spreedly.setPlaceholder('number', ((_a = this.customPlaceholders) === null || _a === void 0 ? void 0 : _a.cardNumber) || tokenizer_constants_1.PLACEHOLDERS.cardNumber);
148
146
  this.spreedly.setFieldType('number', 'text');
149
147
  this.spreedly.setNumberFormat('prettyFormat');
150
- this.spreedly.setPlaceholder('cvv', tokenizer_constants_1.PLACEHOLDERS.cvv);
148
+ this.spreedly.setPlaceholder('cvv', ((_b = this.customPlaceholders) === null || _b === void 0 ? void 0 : _b.cvv) || tokenizer_constants_1.PLACEHOLDERS.cvv);
151
149
  this.spreedly.setFieldType('cvv', 'text');
152
150
  this.applyUnifiedStyles();
153
151
  if (this.enableTestMode &&
@@ -165,23 +163,37 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
165
163
  this.emit('ready');
166
164
  resolve();
167
165
  });
166
+ this.spreedly.on('paymentMethod', (token, pmData) => {
167
+ const paymentToken = {
168
+ token,
169
+ lastFour: pmData.last_four_digits,
170
+ cardType: this.normalizeCardType(pmData.card_type),
171
+ paymentMethodType: 'credit_card',
172
+ provider: 'spreedly',
173
+ };
174
+ if (this.tokenizationResolve) {
175
+ const resolveTokenization = this.tokenizationResolve;
176
+ this.clearTokenizationState();
177
+ this.emit('tokenReady', paymentToken);
178
+ resolveTokenization(paymentToken);
179
+ }
180
+ });
168
181
  this.spreedly.on('errors', (errors) => {
169
- this.emit('error', new types_1.TokenizationError(errors));
182
+ const error = new types_1.TokenizationError(errors);
183
+ this.emit('error', error);
184
+ if (this.tokenizationReject) {
185
+ const rejectTokenization = this.tokenizationReject;
186
+ this.clearTokenizationState();
187
+ rejectTokenization(error);
188
+ }
170
189
  });
171
190
  this.spreedly.on('fieldEvent', (fieldName, eventType, activeEl, inputProperties) => {
172
191
  this.handleFieldEvent(fieldName, eventType, inputProperties);
173
192
  });
174
- const initOptions = {
193
+ this.spreedly.init(this.environmentKey, {
175
194
  numberEl: this.numberEl,
176
195
  cvvEl: this.cvvEl,
177
- };
178
- if (securityArgs) {
179
- initOptions.certificateToken = securityArgs.certificate_token;
180
- initOptions.signature = securityArgs.signature;
181
- initOptions.timestamp = securityArgs.timestamp;
182
- initOptions.nonce = securityArgs.nonce;
183
- }
184
- this.spreedly.init(this.environmentKey, initOptions);
196
+ });
185
197
  });
186
198
  });
187
199
  }
@@ -199,58 +211,45 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
199
211
  });
200
212
  }
201
213
  tokenizeCardInternal(cardData) {
214
+ if (this.tokenizationPromise) {
215
+ return this.tokenizationPromise;
216
+ }
217
+ const expiry = this.parseExpiry(this.expiryEl);
218
+ if (!expiry) {
219
+ return Promise.reject(new types_1.TokenizationError('Expiration date is required', 'VALIDATION_ERROR'));
220
+ }
221
+ this.tokenizationPromise = this.executeCardTokenization(cardData, expiry).finally(() => {
222
+ this.tokenizationPromise = undefined;
223
+ });
224
+ return this.tokenizationPromise;
225
+ }
226
+ executeCardTokenization(cardData, expiry) {
202
227
  return __awaiter(this, void 0, void 0, function* () {
228
+ var _a;
229
+ let securityFields = {};
230
+ if ((_a = this.clientConfig) === null || _a === void 0 ? void 0 : _a.enableSpreedlySecureTokenization) {
231
+ try {
232
+ const args = yield (0, spreedly_secure_1.fetchSpreedlySecurityArgs)(this.clientConfig, this.organizationId, this.embedId);
233
+ securityFields = {
234
+ nonce: args.nonce,
235
+ timestamp: args.timestamp,
236
+ certificateToken: args.certificate_token,
237
+ signature: args.signature,
238
+ };
239
+ }
240
+ catch (error) {
241
+ throw new types_1.TokenizationError(`Secure tokenization failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'SECURE_TOKENIZATION_ERROR');
242
+ }
243
+ }
203
244
  return new Promise((resolve, reject) => {
204
245
  var _a, _b, _c, _d, _e, _f;
205
- const timeout = setTimeout(() => {
206
- if (this.spreedly && this.spreedly.removeHandlers) {
207
- this.spreedly.removeHandlers();
208
- }
246
+ this.tokenizationResolve = resolve;
247
+ this.tokenizationReject = reject;
248
+ this.tokenizationTimeout = setTimeout(() => {
249
+ this.clearTokenizationState();
209
250
  reject(new types_1.TokenizationError('Tokenization timeout', 'TIMEOUT'));
210
251
  }, tokenizer_constants_1.TOKENIZE_TIMEOUT);
211
- const cleanup = () => {
212
- clearTimeout(timeout);
213
- if (this.spreedly && this.spreedly.removeHandlers) {
214
- this.spreedly.removeHandlers();
215
- }
216
- };
217
- this.spreedly.on('paymentMethod', (token, pmData) => {
218
- cleanup();
219
- const paymentToken = {
220
- token,
221
- lastFour: pmData.last_four_digits,
222
- cardType: this.normalizeCardType(pmData.card_type),
223
- provider: 'spreedly',
224
- };
225
- this.emit('tokenReady', paymentToken);
226
- resolve(paymentToken);
227
- });
228
- this.spreedly.on('errors', (errors) => {
229
- cleanup();
230
- reject(new types_1.TokenizationError(errors));
231
- });
232
- const expiry = this.parseExpiry(this.expiryEl);
233
- if (!expiry) {
234
- cleanup();
235
- reject(new types_1.TokenizationError('Expiration date is required', 'VALIDATION_ERROR'));
236
- return;
237
- }
238
- const expiryMonth = expiry.month;
239
- const expiryYear = expiry.year;
240
- this.spreedly.tokenizeCreditCard({
241
- first_name: cardData.firstName,
242
- last_name: cardData.lastName,
243
- month: expiryMonth,
244
- year: expiryYear,
245
- email: cardData.email,
246
- phone_number: cardData.phone,
247
- address1: (_a = cardData.address) === null || _a === void 0 ? void 0 : _a.address1,
248
- address2: (_b = cardData.address) === null || _b === void 0 ? void 0 : _b.address2,
249
- city: (_c = cardData.address) === null || _c === void 0 ? void 0 : _c.city,
250
- state: (_d = cardData.address) === null || _d === void 0 ? void 0 : _d.state,
251
- zip: (_e = cardData.address) === null || _e === void 0 ? void 0 : _e.zip,
252
- country: (_f = cardData.address) === null || _f === void 0 ? void 0 : _f.country,
253
- });
252
+ this.spreedly.tokenizeCreditCard(Object.assign({ first_name: cardData.firstName, last_name: cardData.lastName, month: expiry.month, year: expiry.year, email: cardData.email, phone_number: cardData.phone, address1: (_a = cardData.address) === null || _a === void 0 ? void 0 : _a.address1, address2: (_b = cardData.address) === null || _b === void 0 ? void 0 : _b.address2, city: (_c = cardData.address) === null || _c === void 0 ? void 0 : _c.city, state: (_d = cardData.address) === null || _d === void 0 ? void 0 : _d.state, zip: (_e = cardData.address) === null || _e === void 0 ? void 0 : _e.zip, country: (_f = cardData.address) === null || _f === void 0 ? void 0 : _f.country }, securityFields));
254
253
  });
255
254
  });
256
255
  }
@@ -262,7 +261,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
262
261
  minWidth: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.accountType.minWidth,
263
262
  });
264
263
  this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'left');
265
- container.appendChild(this.accountTypeEl);
264
+ this.appendField(container, this.accountTypeEl, 'Account Type');
266
265
  this.institutionNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-institution`, 'text', 'Inst *', 3);
267
266
  Object.assign(this.institutionNumberEl.style, {
268
267
  flex: '1',
@@ -279,7 +278,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
279
278
  }
280
279
  this.updateBankAccountValidation();
281
280
  });
282
- container.appendChild(this.institutionNumberEl);
281
+ this.appendField(container, this.institutionNumberEl, 'Institution');
283
282
  this.transitNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-transit`, 'text', 'Transit *', 5);
284
283
  Object.assign(this.transitNumberEl.style, {
285
284
  flex: '2',
@@ -296,7 +295,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
296
295
  }
297
296
  this.updateBankAccountValidation();
298
297
  });
299
- container.appendChild(this.transitNumberEl);
298
+ this.appendField(container, this.transitNumberEl, 'Transit');
300
299
  this.accountNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-account`, 'text', 'Account Number *');
301
300
  Object.assign(this.accountNumberEl.style, {
302
301
  flex: '1',
@@ -314,7 +313,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
314
313
  }
315
314
  this.updateBankAccountValidation();
316
315
  });
317
- container.appendChild(this.accountNumberEl);
316
+ this.appendField(container, this.accountNumberEl, 'Account Number');
318
317
  }
319
318
  else {
320
319
  this.institutionNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-institution`, 'text', 'Inst *', 3);
@@ -333,7 +332,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
333
332
  }
334
333
  this.updateBankAccountValidation();
335
334
  });
336
- container.appendChild(this.institutionNumberEl);
335
+ this.appendField(container, this.institutionNumberEl, 'Institution');
337
336
  this.transitNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-transit`, 'text', 'Transit *', 5);
338
337
  Object.assign(this.transitNumberEl.style, {
339
338
  flex: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.transitNumber.flex,
@@ -350,7 +349,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
350
349
  }
351
350
  this.updateBankAccountValidation();
352
351
  });
353
- container.appendChild(this.transitNumberEl);
352
+ this.appendField(container, this.transitNumberEl, 'Transit');
354
353
  this.accountNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-account`, 'text', 'Account Number *');
355
354
  Object.assign(this.accountNumberEl.style, {
356
355
  flex: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.accountNumber.flex,
@@ -367,14 +366,14 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
367
366
  }
368
367
  this.updateBankAccountValidation();
369
368
  });
370
- container.appendChild(this.accountNumberEl);
369
+ this.appendField(container, this.accountNumberEl, 'Account Number');
371
370
  this.accountTypeEl = (0, tokenizer_utils_1.createAccountTypeSelect)(`${this.containerId}-account-type`);
372
371
  Object.assign(this.accountTypeEl.style, {
373
372
  flex: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.accountType.flex,
374
373
  minWidth: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.accountType.minWidth,
375
374
  });
376
375
  this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'right');
377
- container.appendChild(this.accountTypeEl);
376
+ this.appendField(container, this.accountTypeEl, 'Account Type');
378
377
  }
379
378
  }
380
379
  tokenizeBankAccountInternal(bankData) {
@@ -442,18 +441,23 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
442
441
  accountType: accountType,
443
442
  accountHolderType: accountHolderType,
444
443
  },
445
- }, this.clientConfig);
446
- return {
444
+ }, this.clientConfig, this.environmentKey);
445
+ const paymentToken = {
447
446
  token: result,
448
447
  lastFour: accountNumber.slice(-4),
449
448
  accountType: accountType,
450
449
  paymentMethodType: 'bank_account',
451
450
  provider: 'spreedly',
452
451
  };
452
+ this.emit('tokenReady', paymentToken);
453
+ return paymentToken;
453
454
  });
454
455
  }
455
456
  validate() {
456
457
  return __awaiter(this, void 0, void 0, function* () {
458
+ if (!this.isReady) {
459
+ throw new Error('Tokenizer not initialized');
460
+ }
457
461
  if (this.mode === 'bank_account') {
458
462
  return this.validateBankAccountInternal();
459
463
  }
@@ -588,7 +592,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
588
592
  const isConnected = ((_a = this.mergedStyles.container) === null || _a === void 0 ? void 0 : _a.gap) === '0';
589
593
  if (this.mergedStyles.input) {
590
594
  if (isConnected) {
591
- const numberStyles = Object.assign(Object.assign({}, this.mergedStyles.input), { borderRight: 'none', borderRadius: this.mergedStyles.input.borderRadius
595
+ const numberStyles = Object.assign(Object.assign({}, this.mergedStyles.input), { borderRight: '1px solid transparent', borderRadius: this.mergedStyles.input.borderRadius
592
596
  ? `${this.mergedStyles.input.borderRadius} 0 0 ${this.mergedStyles.input.borderRadius}`
593
597
  : '0' });
594
598
  const numberCss = this.stylesToCssString(numberStyles);
@@ -611,7 +615,11 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
611
615
  this.spreedly.setStyle('cvv:focus', focusCss);
612
616
  }
613
617
  if (this.mergedStyles.error) {
614
- const errorCss = this.stylesToCssString(this.mergedStyles.error);
618
+ const errorStyles = Object.assign({}, this.mergedStyles.error);
619
+ if (errorStyles.borderColor) {
620
+ errorStyles.borderRightColor = errorStyles.borderColor;
621
+ }
622
+ const errorCss = this.stylesToCssString(errorStyles);
615
623
  this.spreedly.setStyle('number.invalid', errorCss);
616
624
  this.spreedly.setStyle('cvv.invalid', errorCss);
617
625
  }
@@ -641,13 +649,33 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
641
649
  this.accountNumberEl.value = '';
642
650
  if (this.accountTypeEl)
643
651
  this.accountTypeEl.selectedIndex = 0;
652
+ this.currentValidationState = {
653
+ isValid: false,
654
+ routingNumber: { isValid: false, isEmpty: true },
655
+ accountNumber: { isValid: false, isEmpty: true },
656
+ };
644
657
  }
645
658
  else {
646
659
  this.spreedly.reload();
647
660
  if (this.expiryEl) {
648
661
  this.expiryEl.value = '';
649
662
  }
663
+ this.currentValidationState = {
664
+ isValid: false,
665
+ cardNumber: { isValid: false, isEmpty: true },
666
+ cvv: { isValid: false, isEmpty: true },
667
+ expiry: { isValid: false, isEmpty: true },
668
+ };
650
669
  }
670
+ if (this.tokenizationReject) {
671
+ const reject = this.tokenizationReject;
672
+ this.clearTokenizationState();
673
+ reject(new types_1.TokenizationError('Tokenizer cleared', 'CLEARED'));
674
+ }
675
+ else {
676
+ this.clearTokenizationState();
677
+ }
678
+ this.emit('validation', this.currentValidationState);
651
679
  }
652
680
  focus(field) {
653
681
  if (this.mode === 'bank_account') {
@@ -683,12 +711,84 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
683
711
  element === null || element === void 0 ? void 0 : element.focus();
684
712
  }
685
713
  }
714
+ determineLayout(width) {
715
+ return width < this.responsiveBreakpoint ? 'two-line' : 'single-line';
716
+ }
717
+ setupResizeObserver() {
718
+ if (this.layout !== 'responsive')
719
+ return;
720
+ const container = document.getElementById(this.containerId);
721
+ if (!container)
722
+ return;
723
+ let debounceTimer;
724
+ this.resizeObserver = new ResizeObserver((entries) => {
725
+ clearTimeout(debounceTimer);
726
+ debounceTimer = setTimeout(() => {
727
+ const newWidth = entries[0].contentRect.width;
728
+ const newLayout = this.determineLayout(newWidth);
729
+ if (newLayout !== this.effectiveLayout) {
730
+ this.applyLayoutStyles(newLayout);
731
+ }
732
+ }, 100);
733
+ });
734
+ this.resizeObserver.observe(container);
735
+ }
736
+ applyLayoutStyles(newLayout) {
737
+ this.effectiveLayout = newLayout;
738
+ const container = document.getElementById(this.containerId);
739
+ if (!container)
740
+ return;
741
+ if (newLayout === 'two-line') {
742
+ container.style.flexWrap = 'wrap';
743
+ if (this.cardNumberDiv) {
744
+ this.cardNumberDiv.style.flex = '1';
745
+ this.cardNumberDiv.style.flexBasis = '100%';
746
+ }
747
+ if (this.expiryEl) {
748
+ this.expiryEl.style.flex = '1';
749
+ this.expiryEl.style.flexBasis = 'auto';
750
+ }
751
+ if (this.cvvDiv) {
752
+ this.cvvDiv.style.flex = '1';
753
+ this.cvvDiv.style.flexBasis = 'auto';
754
+ }
755
+ }
756
+ else {
757
+ container.style.flexWrap = 'nowrap';
758
+ if (this.cardNumberDiv) {
759
+ this.cardNumberDiv.style.flex = tokenizer_constants_1.FIELD_FLEX.cardNumber.flex;
760
+ }
761
+ if (this.expiryEl) {
762
+ this.expiryEl.style.flex = tokenizer_constants_1.FIELD_FLEX.expiry.flex;
763
+ }
764
+ if (this.cvvDiv) {
765
+ this.cvvDiv.style.flex = tokenizer_constants_1.FIELD_FLEX.cvv.flex;
766
+ }
767
+ }
768
+ }
769
+ clearTokenizationState() {
770
+ if (this.tokenizationTimeout) {
771
+ clearTimeout(this.tokenizationTimeout);
772
+ this.tokenizationTimeout = undefined;
773
+ }
774
+ this.tokenizationResolve = undefined;
775
+ this.tokenizationReject = undefined;
776
+ }
686
777
  destroy() {
778
+ var _a;
779
+ this.markDestroyed();
780
+ (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
781
+ if (this.tokenizationReject) {
782
+ const reject = this.tokenizationReject;
783
+ this.clearTokenizationState();
784
+ reject(new types_1.TokenizationError('Tokenizer destroyed', 'DESTROYED'));
785
+ }
687
786
  if (this.spreedly && this.spreedly.removeHandlers) {
688
787
  this.spreedly.removeHandlers();
689
788
  }
690
789
  this.eventHandlers.clear();
691
- if (this.expiryEl) {
790
+ if (this.containerEl) {
791
+ this.containerEl.innerHTML = '';
692
792
  }
693
793
  }
694
794
  hasToken() {
@@ -735,15 +835,6 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
735
835
  }
736
836
  this.updateOverallValidation();
737
837
  }
738
- if (eventType === 'focus') {
739
- this.emit('focus', { field: fieldName });
740
- }
741
- else if (eventType === 'blur') {
742
- this.emit('blur', { field: fieldName });
743
- }
744
- else if (eventType === 'input') {
745
- this.emit('change', { field: fieldName });
746
- }
747
838
  }
748
839
  createInternalElements() {
749
840
  const container = document.getElementById(this.containerId);
@@ -759,13 +850,25 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
759
850
  this.createCreditCardFields(container);
760
851
  }
761
852
  }
853
+ appendField(container, field, labelText) {
854
+ container.appendChild((0, tokenizer_utils_1.wrapFieldWithLabel)(field, labelText, this.mergedStyles));
855
+ }
762
856
  createCreditCardFields(container) {
763
- if (this.layout === 'two-line') {
857
+ var _a, _b;
858
+ if (this.layout === 'responsive') {
859
+ this.effectiveLayout = this.determineLayout(container.offsetWidth);
860
+ }
861
+ else {
862
+ this.effectiveLayout =
863
+ this.layout === 'two-line' ? 'two-line' : 'single-line';
864
+ }
865
+ if (this.effectiveLayout === 'two-line' && this.layout !== 'responsive') {
764
866
  const cardNumberDiv = (0, tokenizer_utils_1.createFieldContainer)(this.numberEl, '1', tokenizer_constants_1.FIELD_FLEX.cardNumber.minWidth);
765
867
  cardNumberDiv.style.height = this.mergedStyles.input.height;
766
868
  cardNumberDiv.style.flexBasis = '100%';
767
- container.appendChild(cardNumberDiv);
768
- this.expiryEl = (0, tokenizer_utils_1.createInputElement)(this.expiryId, 'text', tokenizer_constants_1.PLACEHOLDERS.expiry, tokenizer_constants_1.FIELD_CONSTRAINTS.expiry.maxLength);
869
+ this.cardNumberDiv = cardNumberDiv;
870
+ this.appendField(container, cardNumberDiv, 'Card Number');
871
+ this.expiryEl = (0, tokenizer_utils_1.createInputElement)(this.expiryId, 'text', ((_a = this.customPlaceholders) === null || _a === void 0 ? void 0 : _a.expiry) || tokenizer_constants_1.PLACEHOLDERS.expiry, tokenizer_constants_1.FIELD_CONSTRAINTS.expiry.maxLength);
769
872
  Object.assign(this.expiryEl.style, {
770
873
  flex: '1',
771
874
  minWidth: tokenizer_constants_1.FIELD_FLEX.expiry.minWidth,
@@ -781,16 +884,22 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
781
884
  };
782
885
  this.updateOverallValidation();
783
886
  });
784
- container.appendChild(this.expiryEl);
887
+ this.expiryEl.addEventListener('blur', () => {
888
+ this.validateExpiry();
889
+ this.updateOverallValidation();
890
+ });
891
+ this.appendField(container, this.expiryEl, 'Expiration');
785
892
  const cvvDiv = (0, tokenizer_utils_1.createFieldContainer)(this.cvvEl, '1', tokenizer_constants_1.FIELD_FLEX.cvv.minWidth);
786
893
  cvvDiv.style.height = this.mergedStyles.input.height;
787
- container.appendChild(cvvDiv);
894
+ this.cvvDiv = cvvDiv;
895
+ this.appendField(container, cvvDiv, 'CVV');
788
896
  }
789
897
  else {
790
898
  const cardNumberDiv = (0, tokenizer_utils_1.createFieldContainer)(this.numberEl, tokenizer_constants_1.FIELD_FLEX.cardNumber.flex, tokenizer_constants_1.FIELD_FLEX.cardNumber.minWidth);
791
899
  cardNumberDiv.style.height = this.mergedStyles.input.height;
792
- container.appendChild(cardNumberDiv);
793
- this.expiryEl = (0, tokenizer_utils_1.createInputElement)(this.expiryId, 'text', tokenizer_constants_1.PLACEHOLDERS.expiry, tokenizer_constants_1.FIELD_CONSTRAINTS.expiry.maxLength);
900
+ this.cardNumberDiv = cardNumberDiv;
901
+ this.appendField(container, cardNumberDiv, 'Card Number');
902
+ this.expiryEl = (0, tokenizer_utils_1.createInputElement)(this.expiryId, 'text', ((_b = this.customPlaceholders) === null || _b === void 0 ? void 0 : _b.expiry) || tokenizer_constants_1.PLACEHOLDERS.expiry, tokenizer_constants_1.FIELD_CONSTRAINTS.expiry.maxLength);
794
903
  Object.assign(this.expiryEl.style, {
795
904
  flex: tokenizer_constants_1.FIELD_FLEX.expiry.flex,
796
905
  minWidth: tokenizer_constants_1.FIELD_FLEX.expiry.minWidth,
@@ -806,10 +915,19 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
806
915
  };
807
916
  this.updateOverallValidation();
808
917
  });
809
- container.appendChild(this.expiryEl);
918
+ this.expiryEl.addEventListener('blur', () => {
919
+ this.validateExpiry();
920
+ this.updateOverallValidation();
921
+ });
922
+ this.appendField(container, this.expiryEl, 'Expiration');
810
923
  const cvvDiv = (0, tokenizer_utils_1.createFieldContainer)(this.cvvEl, tokenizer_constants_1.FIELD_FLEX.cvv.flex, tokenizer_constants_1.FIELD_FLEX.cvv.minWidth);
811
924
  cvvDiv.style.height = this.mergedStyles.input.height;
812
- container.appendChild(cvvDiv);
925
+ this.cvvDiv = cvvDiv;
926
+ this.appendField(container, cvvDiv, 'CVV');
927
+ if (this.layout === 'responsive') {
928
+ this.applyLayoutStyles(this.effectiveLayout);
929
+ this.setupResizeObserver();
930
+ }
813
931
  }
814
932
  }
815
933
  createBankAccountFields(container) {
@@ -821,6 +939,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
821
939
  }
822
940
  }
823
941
  createUSBankAccountFields(container) {
942
+ var _a, _b, _c, _d;
824
943
  if (this.layout === 'two-line') {
825
944
  this.accountTypeEl = (0, tokenizer_utils_1.createAccountTypeSelect)(`${this.containerId}-account-type`);
826
945
  Object.assign(this.accountTypeEl.style, {
@@ -828,8 +947,8 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
828
947
  minWidth: tokenizer_constants_1.BANK_FIELD_FLEX.accountType.minWidth,
829
948
  });
830
949
  this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'left');
831
- container.appendChild(this.accountTypeEl);
832
- this.routingNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-routing`, 'text', 'Routing *', 9);
950
+ this.appendField(container, this.accountTypeEl, 'Account Type');
951
+ this.routingNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-routing`, 'text', ((_a = this.customPlaceholders) === null || _a === void 0 ? void 0 : _a.routingNumber) || 'Routing *', 9);
833
952
  Object.assign(this.routingNumberEl.style, {
834
953
  flex: '1',
835
954
  minWidth: tokenizer_constants_1.BANK_FIELD_FLEX.routingNumber.minWidth,
@@ -845,8 +964,8 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
845
964
  }
846
965
  this.updateBankAccountValidation();
847
966
  });
848
- container.appendChild(this.routingNumberEl);
849
- this.accountNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-account`, 'text', 'Account Number *', 17);
967
+ this.appendField(container, this.routingNumberEl, 'Routing Number');
968
+ this.accountNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-account`, 'text', ((_b = this.customPlaceholders) === null || _b === void 0 ? void 0 : _b.accountNumber) || 'Account Number *', 17);
850
969
  Object.assign(this.accountNumberEl.style, {
851
970
  flex: '1',
852
971
  flexBasis: '100%',
@@ -863,10 +982,10 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
863
982
  }
864
983
  this.updateBankAccountValidation();
865
984
  });
866
- container.appendChild(this.accountNumberEl);
985
+ this.appendField(container, this.accountNumberEl, 'Account Number');
867
986
  }
868
987
  else {
869
- this.routingNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-routing`, 'text', 'Routing *', 9);
988
+ this.routingNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-routing`, 'text', ((_c = this.customPlaceholders) === null || _c === void 0 ? void 0 : _c.routingNumber) || 'Routing *', 9);
870
989
  Object.assign(this.routingNumberEl.style, {
871
990
  flex: tokenizer_constants_1.BANK_FIELD_FLEX.routingNumber.flex,
872
991
  minWidth: tokenizer_constants_1.BANK_FIELD_FLEX.routingNumber.minWidth,
@@ -882,8 +1001,8 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
882
1001
  }
883
1002
  this.updateBankAccountValidation();
884
1003
  });
885
- container.appendChild(this.routingNumberEl);
886
- this.accountNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-account`, 'text', 'Account Number *', 17);
1004
+ this.appendField(container, this.routingNumberEl, 'Routing Number');
1005
+ this.accountNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-account`, 'text', ((_d = this.customPlaceholders) === null || _d === void 0 ? void 0 : _d.accountNumber) || 'Account Number *', 17);
887
1006
  Object.assign(this.accountNumberEl.style, {
888
1007
  flex: tokenizer_constants_1.BANK_FIELD_FLEX.accountNumber.flex,
889
1008
  minWidth: tokenizer_constants_1.BANK_FIELD_FLEX.accountNumber.minWidth,
@@ -899,14 +1018,14 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
899
1018
  }
900
1019
  this.updateBankAccountValidation();
901
1020
  });
902
- container.appendChild(this.accountNumberEl);
1021
+ this.appendField(container, this.accountNumberEl, 'Account Number');
903
1022
  this.accountTypeEl = (0, tokenizer_utils_1.createAccountTypeSelect)(`${this.containerId}-account-type`);
904
1023
  Object.assign(this.accountTypeEl.style, {
905
1024
  flex: tokenizer_constants_1.BANK_FIELD_FLEX.accountType.flex,
906
1025
  minWidth: tokenizer_constants_1.BANK_FIELD_FLEX.accountType.minWidth,
907
1026
  });
908
1027
  this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'right');
909
- container.appendChild(this.accountTypeEl);
1028
+ this.appendField(container, this.accountTypeEl, 'Account Type');
910
1029
  }
911
1030
  }
912
1031
  updateBankAccountValidation() {
@@ -963,30 +1082,42 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
963
1082
  if (!this.expiryEl)
964
1083
  return;
965
1084
  const expiry = this.parseExpiry(this.expiryEl);
1085
+ const isEmpty = !this.expiryEl.value;
1086
+ let isValid = false;
966
1087
  if (!expiry) {
967
- this.applyErrorStyles(this.expiryEl);
968
- return;
969
- }
970
- const month = parseInt(expiry.month, 10);
971
- const year = parseInt(expiry.year, 10);
972
- if (month < 1 || month > 12) {
973
- this.applyErrorStyles(this.expiryEl);
974
- return;
1088
+ if (!isEmpty)
1089
+ this.applyErrorStyles(this.expiryEl);
975
1090
  }
976
- const now = new Date();
977
- const expiryDate = new Date(year, month - 1);
978
- if (expiryDate < now) {
979
- this.applyErrorStyles(this.expiryEl);
980
- return;
1091
+ else {
1092
+ const month = parseInt(expiry.month, 10);
1093
+ const year = parseInt(expiry.year, 10);
1094
+ if (month < 1 || month > 12) {
1095
+ this.applyErrorStyles(this.expiryEl);
1096
+ }
1097
+ else {
1098
+ const now = new Date();
1099
+ const expiryEndDate = new Date(year, month);
1100
+ if (expiryEndDate <= now) {
1101
+ this.applyErrorStyles(this.expiryEl);
1102
+ }
1103
+ else {
1104
+ isValid = true;
1105
+ const expiryPosition = this.effectiveLayout === 'two-line' ? 'left' : 'middle';
1106
+ this.applyInputStyles(this.expiryEl, this.mergedStyles, expiryPosition);
1107
+ }
1108
+ }
981
1109
  }
982
- this.applyInputStyles(this.expiryEl, this.mergedStyles, 'middle');
1110
+ this.currentValidationState.expiry = { isValid, isEmpty };
983
1111
  }
984
1112
  applyErrorStyles(element) {
985
- if (this.mergedStyles.error) {
986
- Object.assign(element.style, this.mergedStyles.error);
1113
+ var _a, _b, _c;
1114
+ const errorColor = ((_a = this.mergedStyles.error) === null || _a === void 0 ? void 0 : _a.borderColor) || '#dc3545';
1115
+ element.style.border = `1px solid ${errorColor}`;
1116
+ if ((_b = this.mergedStyles.error) === null || _b === void 0 ? void 0 : _b.backgroundColor) {
1117
+ element.style.backgroundColor = this.mergedStyles.error.backgroundColor;
987
1118
  }
988
- else {
989
- element.style.borderColor = '#dc3545';
1119
+ if ((_c = this.mergedStyles.error) === null || _c === void 0 ? void 0 : _c.color) {
1120
+ element.style.color = this.mergedStyles.error.color;
990
1121
  }
991
1122
  }
992
1123
  static ensureSpreedlyLoaded() {
@@ -1033,13 +1164,14 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
1033
1164
  throw new Error(`Timeout waiting for global: ${globalName}`);
1034
1165
  });
1035
1166
  }
1036
- static tokenizeBankAccount(data, config) {
1037
- if (config.spreedlyEnvironmentKey === undefined) {
1167
+ static tokenizeBankAccount(data, config, environmentKeyOverride) {
1168
+ const environmentKey = environmentKeyOverride || config.spreedlyEnvironmentKey;
1169
+ if (!environmentKey) {
1038
1170
  return Promise.reject(new Error('Spreedly is not configured.'));
1039
1171
  }
1040
1172
  return fetch(constants_1.SPREEDLY_TOKENIZER_URL +
1041
1173
  '?' +
1042
- new URLSearchParams({ environment_key: config.spreedlyEnvironmentKey }), {
1174
+ new URLSearchParams({ environment_key: environmentKey }), {
1043
1175
  method: 'POST',
1044
1176
  headers: {
1045
1177
  'Content-Type': 'application/json',