@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
@@ -9,17 +9,18 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { Tokenizer } from './Tokenizer';
11
11
  import { TokenizationError, } from './types';
12
- import ConfigHandler from '../config-handler';
13
12
  import { DEFAULT_UNIFIED_STYLES, mergeStyles, getContainerStylesForLayout, } from './styles';
14
- import { INIT_TIMEOUT, TOKENIZE_TIMEOUT, BANK_FIELD_FLEX, } from './tokenizer-constants';
15
- import { createInputElement, validateRoutingNumber, validateAccountNumber, createAccountTypeSelect, } from './tokenizer-utils';
13
+ import { INIT_TIMEOUT, TOKENIZE_TIMEOUT, BANK_FIELD_FLEX, RESPONSIVE_BREAKPOINT, } from './tokenizer-constants';
14
+ import { createInputElement, validateRoutingNumber, validateAccountNumber, createAccountTypeSelect, wrapFieldWithLabel, cssLengthToPixels, } from './tokenizer-utils';
16
15
  export class CardConnectTokenizer extends Tokenizer {
17
- constructor(iframe, iframeUrl, containerId, mode = 'credit_card', enableTestMode = false, layout = 'single-line') {
16
+ constructor(iframe, iframeUrl, containerId, mode = 'credit_card', enableTestMode = false, layout = 'single-line', customPlaceholders) {
18
17
  super();
19
18
  this.iframeUrl = iframeUrl;
20
19
  this.containerId = containerId;
21
20
  this.layout = layout;
21
+ this.customPlaceholders = customPlaceholders;
22
22
  this.enableTestMode = false;
23
+ this.acceptAutoToken = true;
23
24
  this.mode = mode;
24
25
  this.enableTestMode = enableTestMode;
25
26
  this.iframe = iframe;
@@ -34,17 +35,28 @@ export class CardConnectTokenizer extends Tokenizer {
34
35
  }
35
36
  static create(gateway, container, config) {
36
37
  return __awaiter(this, void 0, void 0, function* () {
37
- var _a;
38
+ var _a, _b;
38
39
  if (container.mode === 'bank_account' && container.bankCountry === 'CA') {
39
40
  throw new Error('CardConnect does not support Canadian bank accounts');
40
41
  }
41
- let baseUrl = ((_a = gateway.config) === null || _a === void 0 ? void 0 : _a.base_url) || config.clientConfig.cardConnectBaseUrl;
42
- config.cardConnectBaseUrl = baseUrl;
42
+ let effectiveLayout = 'single-line';
43
+ if (container.layout === 'responsive') {
44
+ const containerEl = document.getElementById(container.containerId);
45
+ const containerWidth = (containerEl === null || containerEl === void 0 ? void 0 : containerEl.offsetWidth) || 0;
46
+ const breakpoint = (_a = container.responsiveBreakpoint) !== null && _a !== void 0 ? _a : RESPONSIVE_BREAKPOINT;
47
+ effectiveLayout =
48
+ containerWidth < breakpoint ? 'two-line' : 'single-line';
49
+ }
50
+ else if (container.layout === 'two-line') {
51
+ effectiveLayout = 'two-line';
52
+ }
53
+ const resolvedContainer = Object.assign(Object.assign({}, container), { layout: effectiveLayout });
54
+ const baseUrl = ((_b = gateway.config) === null || _b === void 0 ? void 0 : _b.baseUrl) || config.clientConfig.cardConnectBaseUrl;
43
55
  const mergedStyles = mergeStyles(DEFAULT_UNIFIED_STYLES, container.styling);
44
- const iframeUrl = CardConnectTokenizer.generateIframeUrl(baseUrl, container);
56
+ const iframeUrl = CardConnectTokenizer.generateIframeUrl(baseUrl, resolvedContainer);
45
57
  const iframe = CardConnectTokenizer.createIframe(iframeUrl, mergedStyles);
46
- const tokenizer = new CardConnectTokenizer(iframe, iframeUrl, container.containerId, container.mode || 'credit_card', container.enableTestMode || false, container.layout || 'single-line');
47
- tokenizer.createInternalElements(container);
58
+ const tokenizer = new CardConnectTokenizer(iframe, iframeUrl, container.containerId, container.mode || 'credit_card', container.enableTestMode || false, effectiveLayout, container.placeholders);
59
+ tokenizer.createInternalElements(resolvedContainer);
48
60
  yield tokenizer.init();
49
61
  return tokenizer;
50
62
  });
@@ -66,19 +78,32 @@ export class CardConnectTokenizer extends Tokenizer {
66
78
  }
67
79
  }
68
80
  createCreditCardFields(containerEl, mergedStyles) {
81
+ var _a;
69
82
  this.iframe.style.width = '100%';
70
- if (this.layout === 'two-line') {
71
- const inputHeight = mergedStyles.input.height || '40px';
72
- this.iframe.style.height = `calc((${inputHeight}) * 2 + 10px)`;
83
+ const hasLabels = ((_a = mergedStyles.label) === null || _a === void 0 ? void 0 : _a.show) &&
84
+ (this.layout === 'two-line' || this.layout === 'responsive');
85
+ const inputHeight = mergedStyles.input.height || '40px';
86
+ if (this.layout === 'two-line' || this.layout === 'responsive') {
87
+ if (hasLabels) {
88
+ const labelFontPx = cssLengthToPixels(mergedStyles.label.fontSize);
89
+ const labelMarginPx = parseFloat(mergedStyles.label.marginBottom) || 0;
90
+ const labelRowHeight = Math.ceil(labelFontPx * 1.2) + labelMarginPx;
91
+ const extraHeight = Math.ceil(labelRowHeight * 2) + 34;
92
+ this.iframe.style.height = `calc(${inputHeight} * 2 + ${extraHeight}px)`;
93
+ }
94
+ else {
95
+ this.iframe.style.height = `calc((${inputHeight}) * 2 + 10px)`;
96
+ }
73
97
  }
74
98
  else {
75
- this.iframe.style.height = mergedStyles.input.height || '40px';
99
+ this.iframe.style.height = inputHeight;
76
100
  }
77
101
  this.iframe.style.border = 'none';
78
102
  this.iframe.style.display = 'block';
79
103
  containerEl.appendChild(this.iframe);
80
104
  }
81
105
  createBankAccountFields(containerEl, mergedStyles) {
106
+ var _a, _b, _c, _d;
82
107
  this.iframe.style.display = 'none';
83
108
  if (this.layout === 'two-line') {
84
109
  this.accountTypeEl = createAccountTypeSelect(`${this.containerId}-account-type`);
@@ -87,47 +112,115 @@ export class CardConnectTokenizer extends Tokenizer {
87
112
  minWidth: BANK_FIELD_FLEX.accountType.minWidth,
88
113
  });
89
114
  this.applyInputStyles(this.accountTypeEl, mergedStyles, 'left');
90
- containerEl.appendChild(this.accountTypeEl);
91
- this.routingNumberEl = createInputElement(`${this.containerId}-routing`, 'text', 'Routing *', 9);
115
+ containerEl.appendChild(wrapFieldWithLabel(this.accountTypeEl, 'Account Type', mergedStyles));
116
+ this.routingNumberEl = createInputElement(`${this.containerId}-routing`, 'text', ((_a = this.customPlaceholders) === null || _a === void 0 ? void 0 : _a.routingNumber) || 'Routing *', 9);
92
117
  Object.assign(this.routingNumberEl.style, {
93
118
  flex: '1',
94
119
  minWidth: BANK_FIELD_FLEX.routingNumber.minWidth,
95
120
  });
96
121
  this.applyInputStyles(this.routingNumberEl, mergedStyles, 'right');
97
- containerEl.appendChild(this.routingNumberEl);
98
- this.accountNumberEl = createInputElement(`${this.containerId}-account`, 'text', 'Account Number *', 17);
122
+ containerEl.appendChild(wrapFieldWithLabel(this.routingNumberEl, 'Routing Number', mergedStyles));
123
+ this.accountNumberEl = createInputElement(`${this.containerId}-account`, 'text', ((_b = this.customPlaceholders) === null || _b === void 0 ? void 0 : _b.accountNumber) || 'Account Number *', 17);
99
124
  Object.assign(this.accountNumberEl.style, {
100
125
  flex: '1',
101
126
  flexBasis: '100%',
102
127
  minWidth: BANK_FIELD_FLEX.accountNumber.minWidth,
103
128
  });
104
129
  this.applyInputStyles(this.accountNumberEl, mergedStyles);
105
- containerEl.appendChild(this.accountNumberEl);
130
+ containerEl.appendChild(wrapFieldWithLabel(this.accountNumberEl, 'Account Number', mergedStyles));
106
131
  }
107
132
  else {
108
- this.routingNumberEl = createInputElement(`${this.containerId}-routing`, 'text', 'Routing *', 9);
133
+ this.routingNumberEl = createInputElement(`${this.containerId}-routing`, 'text', ((_c = this.customPlaceholders) === null || _c === void 0 ? void 0 : _c.routingNumber) || 'Routing *', 9);
109
134
  Object.assign(this.routingNumberEl.style, {
110
135
  flex: BANK_FIELD_FLEX.routingNumber.flex,
111
136
  minWidth: BANK_FIELD_FLEX.routingNumber.minWidth,
112
137
  });
113
138
  this.applyInputStyles(this.routingNumberEl, mergedStyles, 'left');
114
- containerEl.appendChild(this.routingNumberEl);
115
- this.accountNumberEl = createInputElement(`${this.containerId}-account`, 'text', 'Account Number *', 17);
139
+ containerEl.appendChild(wrapFieldWithLabel(this.routingNumberEl, 'Routing Number', mergedStyles));
140
+ this.accountNumberEl = createInputElement(`${this.containerId}-account`, 'text', ((_d = this.customPlaceholders) === null || _d === void 0 ? void 0 : _d.accountNumber) || 'Account Number *', 17);
116
141
  Object.assign(this.accountNumberEl.style, {
117
142
  flex: BANK_FIELD_FLEX.accountNumber.flex,
118
143
  minWidth: BANK_FIELD_FLEX.accountNumber.minWidth,
119
144
  });
120
145
  this.applyInputStyles(this.accountNumberEl, mergedStyles, 'middle');
121
- containerEl.appendChild(this.accountNumberEl);
146
+ containerEl.appendChild(wrapFieldWithLabel(this.accountNumberEl, 'Account Number', mergedStyles));
122
147
  this.accountTypeEl = createAccountTypeSelect(`${this.containerId}-account-type`);
123
148
  Object.assign(this.accountTypeEl.style, {
124
149
  flex: BANK_FIELD_FLEX.accountType.flex,
125
150
  minWidth: BANK_FIELD_FLEX.accountType.minWidth,
126
151
  });
127
152
  this.applyInputStyles(this.accountTypeEl, mergedStyles, 'right');
128
- containerEl.appendChild(this.accountTypeEl);
153
+ containerEl.appendChild(wrapFieldWithLabel(this.accountTypeEl, 'Account Type', mergedStyles));
154
+ }
155
+ this.mergedStyles = mergedStyles;
156
+ this.currentValidationState = {
157
+ isValid: false,
158
+ routingNumber: { isValid: false, isEmpty: true },
159
+ accountNumber: { isValid: false, isEmpty: true },
160
+ };
161
+ this.addBankAccountValidationListeners(mergedStyles);
162
+ }
163
+ addBankAccountValidationListeners(styles) {
164
+ if (this.routingNumberEl) {
165
+ this.routingNumberEl.addEventListener('blur', () => {
166
+ this.updateBankAccountValidation(styles);
167
+ });
168
+ this.routingNumberEl.addEventListener('input', () => {
169
+ this.updateBankAccountValidation(styles);
170
+ });
171
+ }
172
+ if (this.accountNumberEl) {
173
+ this.accountNumberEl.addEventListener('blur', () => {
174
+ this.updateBankAccountValidation(styles);
175
+ });
176
+ this.accountNumberEl.addEventListener('input', () => {
177
+ this.updateBankAccountValidation(styles);
178
+ });
129
179
  }
130
180
  }
181
+ updateBankAccountValidation(styles) {
182
+ var _a, _b;
183
+ const routingValue = ((_a = this.routingNumberEl) === null || _a === void 0 ? void 0 : _a.value) || '';
184
+ const accountValue = ((_b = this.accountNumberEl) === null || _b === void 0 ? void 0 : _b.value) || '';
185
+ const routingValid = routingValue.length > 0 && validateRoutingNumber(routingValue);
186
+ const accountValid = accountValue.length > 0 && validateAccountNumber(accountValue);
187
+ if (this.routingNumberEl) {
188
+ if (routingValue.length > 0 && !routingValid) {
189
+ this.routingNumberEl.style.border = `1px solid ${styles.error.borderColor}`;
190
+ if (styles.error.backgroundColor) {
191
+ this.routingNumberEl.style.backgroundColor =
192
+ styles.error.backgroundColor;
193
+ }
194
+ }
195
+ else {
196
+ this.applyInputStyles(this.routingNumberEl, this.mergedStyles, 'left');
197
+ }
198
+ }
199
+ if (this.accountNumberEl) {
200
+ if (accountValue.length > 0 && !accountValid) {
201
+ this.accountNumberEl.style.border = `1px solid ${styles.error.borderColor}`;
202
+ if (styles.error.backgroundColor) {
203
+ this.accountNumberEl.style.backgroundColor =
204
+ styles.error.backgroundColor;
205
+ }
206
+ }
207
+ else {
208
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles, 'right');
209
+ }
210
+ }
211
+ this.currentValidationState = {
212
+ isValid: routingValid && accountValid,
213
+ routingNumber: {
214
+ isValid: routingValid,
215
+ isEmpty: routingValue.length === 0,
216
+ },
217
+ accountNumber: {
218
+ isValid: accountValid,
219
+ isEmpty: accountValue.length === 0,
220
+ },
221
+ };
222
+ this.emit('validation', this.currentValidationState);
223
+ }
131
224
  init() {
132
225
  return __awaiter(this, void 0, void 0, function* () {
133
226
  if (this.mode === 'bank_account') {
@@ -163,8 +256,6 @@ export class CardConnectTokenizer extends Tokenizer {
163
256
  window.addEventListener('message', this.messageHandler);
164
257
  this.iframe.onload = () => {
165
258
  clearTimeout(timeout);
166
- if (this.enableTestMode && this.mode === 'credit_card') {
167
- }
168
259
  this.emit('ready');
169
260
  resolve();
170
261
  };
@@ -196,31 +287,39 @@ export class CardConnectTokenizer extends Tokenizer {
196
287
  }
197
288
  tokenizeCardInternal(cardData) {
198
289
  return __awaiter(this, void 0, void 0, function* () {
290
+ var _a, _b, _c;
199
291
  if (this.cachedTokenResult && this.cachedTokenResult.errorCode === '0') {
292
+ const cardType = this.cachedTokenResult.cardType
293
+ ? this.normalizeCardType(this.cachedTokenResult.cardType)
294
+ : this.normalizeCardType('unknown');
200
295
  return {
201
296
  token: this.cachedTokenResult.token,
202
- lastFour: this.cachedTokenResult.token.slice(-4),
203
- cardType: this.normalizeCardType('unknown'),
297
+ lastFour: this.cachedTokenResult.last4 ||
298
+ this.cachedTokenResult.token.slice(-4),
299
+ cardType,
300
+ paymentMethodType: 'credit_card',
204
301
  provider: 'cardconnect',
205
302
  };
206
303
  }
207
304
  if (this.tokenizationPromise) {
208
305
  return this.tokenizationPromise;
209
306
  }
307
+ if (!this.currentValidationState.isValid &&
308
+ (((_a = this.currentValidationState.cardNumber) === null || _a === void 0 ? void 0 : _a.isEmpty) ||
309
+ ((_b = this.currentValidationState.cvv) === null || _b === void 0 ? void 0 : _b.isEmpty) ||
310
+ ((_c = this.currentValidationState.expiry) === null || _c === void 0 ? void 0 : _c.isEmpty))) {
311
+ throw new TokenizationError('Card fields are incomplete', 'VALIDATION_ERROR');
312
+ }
210
313
  this.tokenizationPromise = new Promise((resolve, reject) => {
211
314
  this.tokenizationResolve = resolve;
212
315
  this.tokenizationReject = reject;
213
- const timeout = setTimeout(() => {
316
+ this.tokenizationTimeout = setTimeout(() => {
317
+ this.tokenizationTimeout = undefined;
214
318
  this.tokenizationPromise = undefined;
215
319
  this.tokenizationResolve = undefined;
216
320
  this.tokenizationReject = undefined;
217
321
  reject(new TokenizationError('Tokenization timeout - ensure all card fields are filled in iframe', 'TIMEOUT'));
218
322
  }, TOKENIZE_TIMEOUT);
219
- const tokenizationHandler = (data) => {
220
- clearTimeout(timeout);
221
- this.off('tokenization', tokenizationHandler);
222
- };
223
- this.on('tokenization', tokenizationHandler);
224
323
  });
225
324
  return this.tokenizationPromise;
226
325
  });
@@ -242,16 +341,16 @@ export class CardConnectTokenizer extends Tokenizer {
242
341
  throw new TokenizationError('Invalid account number', 'VALIDATION_ERROR');
243
342
  }
244
343
  const baseUrl = new URL(this.iframeUrl).origin;
245
- const config = new ConfigHandler({});
246
- config.cardConnectBaseUrl = baseUrl;
247
- const result = yield CardConnectTokenizer.tokenizeBankAccount(routingNumber, accountNumber, config);
248
- return {
344
+ const result = yield CardConnectTokenizer.tokenizeBankAccount(routingNumber, accountNumber, baseUrl);
345
+ const paymentToken = {
249
346
  token: result.token,
250
347
  lastFour: accountNumber.slice(-4),
251
348
  accountType: accountType,
252
349
  paymentMethodType: 'bank_account',
253
350
  provider: 'cardconnect',
254
351
  };
352
+ this.emit('tokenReady', paymentToken);
353
+ return paymentToken;
255
354
  });
256
355
  }
257
356
  validate() {
@@ -388,12 +487,39 @@ export class CardConnectTokenizer extends Tokenizer {
388
487
  action: 'clear',
389
488
  }, this.expectedOrigin);
390
489
  }
391
- this.currentValidationState = {
392
- isValid: false,
393
- cardNumber: { isValid: false, isEmpty: true },
394
- cvv: { isValid: false, isEmpty: true },
395
- expiry: { isValid: false, isEmpty: true },
396
- };
490
+ this.cachedTokenResult = undefined;
491
+ this.acceptAutoToken = false;
492
+ if (this.tokenizationTimeout) {
493
+ clearTimeout(this.tokenizationTimeout);
494
+ this.tokenizationTimeout = undefined;
495
+ }
496
+ if (this.tokenizationReject) {
497
+ const reject = this.tokenizationReject;
498
+ this.tokenizationPromise = undefined;
499
+ this.tokenizationResolve = undefined;
500
+ this.tokenizationReject = undefined;
501
+ reject(new TokenizationError('Tokenizer cleared', 'CLEARED'));
502
+ }
503
+ else {
504
+ this.tokenizationPromise = undefined;
505
+ this.tokenizationResolve = undefined;
506
+ this.tokenizationReject = undefined;
507
+ }
508
+ if (this.mode === 'bank_account') {
509
+ this.currentValidationState = {
510
+ isValid: false,
511
+ routingNumber: { isValid: false, isEmpty: true },
512
+ accountNumber: { isValid: false, isEmpty: true },
513
+ };
514
+ }
515
+ else {
516
+ this.currentValidationState = {
517
+ isValid: false,
518
+ cardNumber: { isValid: false, isEmpty: true },
519
+ cvv: { isValid: false, isEmpty: true },
520
+ expiry: { isValid: false, isEmpty: true },
521
+ };
522
+ }
397
523
  this.emit('validation', this.currentValidationState);
398
524
  }
399
525
  focus(field) {
@@ -422,8 +548,22 @@ export class CardConnectTokenizer extends Tokenizer {
422
548
  }
423
549
  }
424
550
  destroy() {
551
+ this.markDestroyed();
552
+ if (this.tokenizationTimeout) {
553
+ clearTimeout(this.tokenizationTimeout);
554
+ this.tokenizationTimeout = undefined;
555
+ }
556
+ if (this.tokenizationReject) {
557
+ const reject = this.tokenizationReject;
558
+ this.tokenizationPromise = undefined;
559
+ this.tokenizationResolve = undefined;
560
+ this.tokenizationReject = undefined;
561
+ reject(new TokenizationError('Tokenizer destroyed', 'DESTROYED'));
562
+ }
563
+ this.cachedTokenResult = undefined;
425
564
  if (this.messageHandler) {
426
565
  window.removeEventListener('message', this.messageHandler);
566
+ this.messageHandler = undefined;
427
567
  }
428
568
  if (this.containerEl) {
429
569
  this.containerEl.innerHTML = '';
@@ -450,6 +590,7 @@ export class CardConnectTokenizer extends Tokenizer {
450
590
  lastFour: this.cachedTokenResult.last4 ||
451
591
  this.cachedTokenResult.token.slice(-4),
452
592
  cardType,
593
+ paymentMethodType: 'credit_card',
453
594
  provider: 'cardconnect',
454
595
  };
455
596
  }
@@ -462,8 +603,14 @@ export class CardConnectTokenizer extends Tokenizer {
462
603
  try {
463
604
  const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
464
605
  if (data.token && data.errorCode !== undefined) {
606
+ if (!this.acceptAutoToken && !this.tokenizationResolve) {
607
+ return;
608
+ }
465
609
  this.cachedTokenResult = data;
466
- this.emit('tokenization', data);
610
+ if (this.tokenizationTimeout) {
611
+ clearTimeout(this.tokenizationTimeout);
612
+ this.tokenizationTimeout = undefined;
613
+ }
467
614
  if (data.errorCode === '0') {
468
615
  const cardType = data.cardType
469
616
  ? this.normalizeCardType(data.cardType)
@@ -476,6 +623,7 @@ export class CardConnectTokenizer extends Tokenizer {
476
623
  token: data.token,
477
624
  lastFour: data.last4 || data.token.slice(-4),
478
625
  cardType,
626
+ paymentMethodType: 'credit_card',
479
627
  provider: 'cardconnect',
480
628
  };
481
629
  if (this.tokenizationResolve) {
@@ -490,12 +638,14 @@ export class CardConnectTokenizer extends Tokenizer {
490
638
  cvv: { isValid: true, isEmpty: false },
491
639
  expiry: { isValid: true, isEmpty: false },
492
640
  };
493
- this.emit('validation', { isValid: true });
641
+ this.emit('validation', this.currentValidationState);
494
642
  this.emit('tokenReady', token);
495
643
  }
496
644
  else {
645
+ const error = new TokenizationError(data.errorMessage || 'Tokenization failed', data.errorCode || 'UNKNOWN');
646
+ this.emit('error', error);
497
647
  if (this.tokenizationReject) {
498
- this.tokenizationReject(new TokenizationError(data.errorMessage || 'Tokenization failed', data.errorCode || 'UNKNOWN'));
648
+ this.tokenizationReject(error);
499
649
  this.tokenizationPromise = undefined;
500
650
  this.tokenizationResolve = undefined;
501
651
  this.tokenizationReject = undefined;
@@ -503,19 +653,16 @@ export class CardConnectTokenizer extends Tokenizer {
503
653
  }
504
654
  }
505
655
  if (data.event === 'validation' || data.validationError !== undefined) {
656
+ this.acceptAutoToken = true;
506
657
  this.handleValidationMessage(data);
507
658
  }
508
- if (data.event === 'focus' || data.event === 'blur') {
509
- this.emit(data.event, { field: data.data });
510
- }
511
- if (data.event === 'input' || data.event === 'change') {
512
- this.emit('change', { field: data.data });
513
- if (data.cardType) {
514
- const cardType = this.normalizeCardType(data.cardType);
515
- if (cardType !== this.currentCardType) {
516
- this.currentCardType = cardType;
517
- this.emit('cardTypeChange', { cardType });
518
- }
659
+ if ((data.event === 'input' || data.event === 'change') &&
660
+ data.cardType) {
661
+ this.acceptAutoToken = true;
662
+ const cardType = this.normalizeCardType(data.cardType);
663
+ if (cardType !== this.currentCardType) {
664
+ this.currentCardType = cardType;
665
+ this.emit('cardTypeChange', { cardType });
519
666
  }
520
667
  }
521
668
  }
@@ -523,7 +670,6 @@ export class CardConnectTokenizer extends Tokenizer {
523
670
  }
524
671
  }
525
672
  handleValidationMessage(data) {
526
- var _a, _b, _c, _d, _e, _f;
527
673
  if (data.validationError) {
528
674
  const errorLower = data.validationError.toLowerCase();
529
675
  if (errorLower.includes('card') || errorLower.includes('number')) {
@@ -541,14 +687,48 @@ export class CardConnectTokenizer extends Tokenizer {
541
687
  errorLower.includes('year')) {
542
688
  this.currentValidationState.expiry = { isValid: false, isEmpty: false };
543
689
  }
544
- this.emit('error', new TokenizationError(data.validationError));
690
+ this.currentValidationState.isValid = false;
691
+ this.emit('validation', this.currentValidationState);
692
+ }
693
+ else {
694
+ this.currentValidationState.cardNumber = {
695
+ isValid: true,
696
+ isEmpty: false,
697
+ };
698
+ this.currentValidationState.cvv = { isValid: true, isEmpty: false };
699
+ this.currentValidationState.expiry = { isValid: true, isEmpty: false };
700
+ this.currentValidationState.isValid = true;
701
+ this.emit('validation', this.currentValidationState);
545
702
  }
546
- this.currentValidationState.isValid =
547
- ((_b = (_a = this.currentValidationState.cardNumber) === null || _a === void 0 ? void 0 : _a.isValid) !== null && _b !== void 0 ? _b : false) &&
548
- ((_d = (_c = this.currentValidationState.cvv) === null || _c === void 0 ? void 0 : _c.isValid) !== null && _d !== void 0 ? _d : false) &&
549
- ((_f = (_e = this.currentValidationState.expiry) === null || _e === void 0 ? void 0 : _e.isValid) !== null && _f !== void 0 ? _f : false);
703
+ }
704
+ static measureCvvLabelMarginLeft(monthWidth, yearWidth, cvvMarginLeft) {
705
+ var _a;
706
+ const targetCvvX = monthWidth + 4 + 4 + yearWidth + 12 + cvvMarginLeft;
707
+ const host = document.createElement('div');
708
+ host.style.cssText =
709
+ 'position:absolute;visibility:hidden;left:0;top:0;width:9999px;height:0;overflow:hidden;';
710
+ document.body.appendChild(host);
711
+ const shadow = host.attachShadow({ mode: 'open' });
712
+ shadow.innerHTML = [
713
+ '<input type="text" size="2" style="margin-right:4px">',
714
+ '<label>/</label>',
715
+ '<input type="text" size="4" style="margin-left:4px;margin-right:12px">',
716
+ '<input type="tel" size="5">',
717
+ ].join('');
718
+ const inputs = shadow.querySelectorAll('input');
719
+ const hostX = host.getBoundingClientRect().x;
720
+ const cvvRect = (_a = inputs[2]) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
721
+ const defaultCvvX = cvvRect ? cvvRect.x - hostX : -1;
722
+ document.body.removeChild(host);
723
+ if (defaultCvvX <= 0 || defaultCvvX > 500) {
724
+ console.warn('[CardConnect] CVV measurement out of range, using fallback margin-left:100px');
725
+ return 100;
726
+ }
727
+ const predictedLabelLeft = defaultCvvX + 8;
728
+ return Math.round(targetCvvX - predictedLabelLeft);
550
729
  }
551
730
  static generateIframeUrl(baseUrl, container) {
731
+ var _a, _b, _c, _d, _e, _f, _g, _h;
552
732
  const params = new URLSearchParams({
553
733
  invalidinputevent: 'true',
554
734
  enhancedresponse: 'true',
@@ -557,25 +737,46 @@ export class CardConnectTokenizer extends Tokenizer {
557
737
  formatinput: 'true',
558
738
  unique: 'true',
559
739
  norsa: 'true',
560
- placeholder: 'Card Number *',
561
- placeholdercvv: 'CVV',
740
+ placeholder: ((_a = container.placeholders) === null || _a === void 0 ? void 0 : _a.cardNumber) || 'Card Number *',
741
+ placeholdercvv: ((_b = container.placeholders) === null || _b === void 0 ? void 0 : _b.cvv) || 'CVV',
562
742
  invalidcreditcardevent: 'true',
563
743
  invalidexpiry: 'true',
564
744
  invalidcvv: 'true',
565
745
  });
746
+ const mergedStyles = mergeStyles(DEFAULT_UNIFIED_STYLES, container.styling);
747
+ const showLabelsInUrl = ((_c = mergedStyles.label) === null || _c === void 0 ? void 0 : _c.show) && container.layout === 'two-line';
748
+ if (showLabelsInUrl) {
749
+ params.set('cardlabel', 'Card Number');
750
+ params.set('expirylabel', 'Expiration');
751
+ params.set('cvvlabel', 'CVV');
752
+ }
753
+ else {
754
+ params.set('cardlabel', '');
755
+ params.set('expirylabel', '');
756
+ params.set('cvvlabel', '');
757
+ }
566
758
  if (container.layout === 'two-line') {
567
759
  params.set('useexpiryfield', 'true');
568
760
  params.set('orientation', 'horizontal');
569
- params.set('placeholdermonth', 'MM');
570
- params.set('placeholderyear', 'YYYY');
761
+ params.set('placeholdermonth', ((_d = container.placeholders) === null || _d === void 0 ? void 0 : _d.expiryMonth) || 'MM');
762
+ params.set('placeholderyear', ((_e = container.placeholders) === null || _e === void 0 ? void 0 : _e.expiryYear) || 'YYYY');
571
763
  }
572
764
  else {
573
765
  params.set('orientation', 'horizontal');
574
- params.set('placeholdermonth', 'MM');
575
- params.set('placeholderyear', 'YYYY');
766
+ params.set('placeholdermonth', ((_f = container.placeholders) === null || _f === void 0 ? void 0 : _f.expiryMonth) || 'MM');
767
+ params.set('placeholderyear', ((_g = container.placeholders) === null || _g === void 0 ? void 0 : _g.expiryYear) || 'YYYY');
576
768
  }
577
- const mergedStyles = mergeStyles(DEFAULT_UNIFIED_STYLES, container.styling);
578
- const cssString = CardConnectTokenizer.generateCardConnectCss(mergedStyles, container.layout || 'single-line');
769
+ const isDesktopSafari = typeof navigator !== 'undefined' &&
770
+ /Safari/.test(navigator.userAgent) &&
771
+ /Apple Computer/.test(navigator.vendor) &&
772
+ !/Chrome/.test(navigator.userAgent) &&
773
+ !/Mobile/.test(navigator.userAgent);
774
+ const showLabels = ((_h = mergedStyles.label) === null || _h === void 0 ? void 0 : _h.show) && container.layout === 'two-line';
775
+ let cvvLabelMarginLeft;
776
+ if (showLabels && typeof document !== 'undefined') {
777
+ cvvLabelMarginLeft = CardConnectTokenizer.measureCvvLabelMarginLeft(60, 80, 40);
778
+ }
779
+ const cssString = CardConnectTokenizer.generateCardConnectCss(mergedStyles, (container.layout || 'single-line'), isDesktopSafari, cvvLabelMarginLeft);
579
780
  const queryPairs = [];
580
781
  params.forEach((value, key) => {
581
782
  queryPairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
@@ -590,7 +791,7 @@ export class CardConnectTokenizer extends Tokenizer {
590
791
  iframe.style.width = '100%';
591
792
  iframe.style.height = styles.input.height;
592
793
  iframe.style.border = 'none';
593
- iframe.style.overflow = 'hidden';
794
+ iframe.style.overflow = '';
594
795
  iframe.style.display = 'block';
595
796
  iframe.style.minWidth = '0';
596
797
  if (styles.container) {
@@ -608,18 +809,30 @@ export class CardConnectTokenizer extends Tokenizer {
608
809
  iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-forms');
609
810
  return iframe;
610
811
  }
611
- static generateCardConnectCss(styles, layout = 'single-line') {
612
- var _a, _b, _c, _d;
812
+ static generateCardConnectCss(styles, layout = 'single-line', isDesktopSafari = false, cvvLabelMarginLeft) {
813
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
613
814
  const css = [];
614
- if (layout === 'two-line') {
615
- css.push('html,form,body{margin:0;padding:0;}');
815
+ const showLabelsInIframe = ((_a = styles.label) === null || _a === void 0 ? void 0 : _a.show) && layout === 'two-line';
816
+ if (showLabelsInIframe) {
817
+ const labelCss = `font-size:${styles.label.fontSize};font-weight:${styles.label.fontWeight};font-family:${styles.label.fontFamily};color:${styles.label.color};margin-bottom:${styles.label.marginBottom}`;
818
+ css.push(`label{${labelCss};}`);
819
+ css.push(`label[for="ccexpiryfieldyear"]{display:none;}`);
820
+ css.push(`br{line-height:0;font-size:0;margin:0;}`);
821
+ css.push(`input,select{margin-top:0;margin-bottom:0;}`);
822
+ }
823
+ else {
616
824
  css.push('label{display:none;}');
617
- css.push('br{display:none;}');
825
+ }
826
+ if (layout === 'two-line') {
827
+ css.push('html,body{margin:0;padding:0;overflow:hidden;}');
828
+ css.push('form{margin:0;padding:0;width:100%;box-sizing:border-box;}');
829
+ if (!showLabelsInIframe) {
830
+ css.push('br{display:none;}');
831
+ }
618
832
  }
619
833
  else {
620
834
  css.push('body{margin:0;padding:0;display:flex;align-items:center;}');
621
835
  css.push('form{margin:0;padding:0;display:flex;width:100%;align-items:center;}');
622
- css.push('label{display:none;}');
623
836
  css.push('br{display:none;}');
624
837
  }
625
838
  const isConnected = styles.container.gap === '0';
@@ -643,14 +856,31 @@ export class CardConnectTokenizer extends Tokenizer {
643
856
  commonStyles.push('box-sizing:border-box');
644
857
  const baseStyles = commonStyles.join(';');
645
858
  css.push(`input{${baseStyles};}`);
646
- css.push(`select{${baseStyles};}`);
859
+ if (isDesktopSafari) {
860
+ css.push(`select{${baseStyles};-webkit-appearance:none;appearance:none;}`);
861
+ }
862
+ else {
863
+ css.push(`select{${baseStyles};}`);
864
+ }
647
865
  }
648
866
  if (layout === 'two-line') {
649
867
  css.push('input#ccnumfield{width:100%;display:block;margin-bottom:8px;}');
650
- css.push('input#ccexpiryfieldmonth{width:20%;}');
651
- css.push('input#ccexpiryfieldyear{width:24%;}');
652
- css.push('input#cccvvfield{width:calc(56% - 20px);}');
653
- if ((_a = styles.input) === null || _a === void 0 ? void 0 : _a.borderRadius) {
868
+ const twoLinePadding = ((_b = styles.twoLine) === null || _b === void 0 ? void 0 : _b.padding) || ((_c = styles.input) === null || _c === void 0 ? void 0 : _c.padding) || '10px';
869
+ const twoLineFontSize = ((_d = styles.twoLine) === null || _d === void 0 ? void 0 : _d.fontSize) || ((_e = styles.input) === null || _e === void 0 ? void 0 : _e.fontSize) || '14px';
870
+ const twoLineTextAlign = ((_f = styles.twoLine) === null || _f === void 0 ? void 0 : _f.textAlign) || 'left';
871
+ if (showLabelsInIframe) {
872
+ css.push(`input#ccexpiryfieldmonth{width:60px;padding:${twoLinePadding};font-size:${twoLineFontSize};text-align:${twoLineTextAlign};}`);
873
+ css.push(`input#ccexpiryfieldyear{width:80px;padding:${twoLinePadding};font-size:${twoLineFontSize};text-align:${twoLineTextAlign};}`);
874
+ css.push(`input#cccvvfield{width:80px;margin-left:40px;padding:${twoLinePadding};font-size:${twoLineFontSize};text-align:${twoLineTextAlign};}`);
875
+ const labelMargin = cvvLabelMarginLeft !== null && cvvLabelMarginLeft !== void 0 ? cvvLabelMarginLeft : 100;
876
+ css.push(`label#cccvvlabel{margin-left:${labelMargin}px;}`);
877
+ }
878
+ else {
879
+ css.push(`input#ccexpiryfieldmonth{min-width:4em;padding:${twoLinePadding};font-size:${twoLineFontSize};text-align:${twoLineTextAlign};}`);
880
+ css.push(`input#ccexpiryfieldyear{min-width:4em;padding:${twoLinePadding};font-size:${twoLineFontSize};text-align:${twoLineTextAlign};}`);
881
+ css.push(`input#cccvvfield{min-width:4em;padding:${twoLinePadding};font-size:${twoLineFontSize};text-align:${twoLineTextAlign};}`);
882
+ }
883
+ if ((_g = styles.input) === null || _g === void 0 ? void 0 : _g.borderRadius) {
654
884
  css.push(`input{border-radius:${styles.input.borderRadius};}`);
655
885
  }
656
886
  }
@@ -659,16 +889,16 @@ export class CardConnectTokenizer extends Tokenizer {
659
889
  css.push('select#ccexpirymonth{width:16%;margin:0;margin-right:-12px;}');
660
890
  css.push('select#ccexpiryyear{width:20%;}');
661
891
  css.push('input#cccvvfield{width:20%;margin:0;margin-left:-12px;}');
662
- if ((_b = styles.input) === null || _b === void 0 ? void 0 : _b.borderRadius) {
663
- css.push(`input#ccnumfield{border-radius:${styles.input.borderRadius} 0 0 ${styles.input.borderRadius};border-right:none;}`);
664
- css.push('select#ccexpirymonth{border-radius:0;border-right:none;}');
665
- css.push('select#ccexpiryyear{border-radius:0;border-right:none;}');
892
+ if ((_h = styles.input) === null || _h === void 0 ? void 0 : _h.borderRadius) {
893
+ css.push(`input#ccnumfield{border-radius:${styles.input.borderRadius} 0 0 ${styles.input.borderRadius};border-right-color:transparent;}`);
894
+ css.push('select#ccexpirymonth{border-radius:0;border-right-color:transparent;}');
895
+ css.push('select#ccexpiryyear{border-radius:0;border-right-color:transparent;}');
666
896
  css.push(`input#cccvvfield{border-radius:0 ${styles.input.borderRadius} ${styles.input.borderRadius} 0;}`);
667
897
  }
668
898
  else {
669
- css.push('input#ccnumfield{border-right:none;}');
670
- css.push('select#ccexpirymonth{border-right:none;}');
671
- css.push('select#ccexpiryyear{border-right:none;}');
899
+ css.push('input#ccnumfield{border-right-color:transparent;}');
900
+ css.push('select#ccexpirymonth{border-right-color:transparent;}');
901
+ css.push('select#ccexpiryyear{border-right-color:transparent;}');
672
902
  }
673
903
  }
674
904
  else {
@@ -677,7 +907,7 @@ export class CardConnectTokenizer extends Tokenizer {
677
907
  css.push(`select#ccexpirymonth{width:15%;margin-right:${marginRight};}`);
678
908
  css.push(`select#ccexpiryyear{width:20%;margin-right:${marginRight};}`);
679
909
  css.push('input#cccvvfield{width:15%;}');
680
- if ((_c = styles.input) === null || _c === void 0 ? void 0 : _c.borderRadius) {
910
+ if ((_j = styles.input) === null || _j === void 0 ? void 0 : _j.borderRadius) {
681
911
  css.push(`input,select{border-radius:${styles.input.borderRadius};}`);
682
912
  }
683
913
  }
@@ -693,7 +923,7 @@ export class CardConnectTokenizer extends Tokenizer {
693
923
  if (focusStyles.length > 0) {
694
924
  css.push(`input:focus{${focusStyles.join(';')};}`);
695
925
  css.push(`select:focus{${focusStyles.join(';')};}`);
696
- if (isConnected && ((_d = styles.input) === null || _d === void 0 ? void 0 : _d.border)) {
926
+ if (isConnected && ((_k = styles.input) === null || _k === void 0 ? void 0 : _k.border)) {
697
927
  css.push(`input#ccnumfield:focus{border:${styles.input.border};${focusStyles.join(';')};}`);
698
928
  css.push(`select#ccexpirymonth:focus{border:${styles.input.border};${focusStyles.join(';')};}`);
699
929
  css.push(`select#ccexpiryyear:focus{border:${styles.input.border};${focusStyles.join(';')};}`);
@@ -708,20 +938,39 @@ export class CardConnectTokenizer extends Tokenizer {
708
938
  errorStyles.push(`background-color:${styles.error.backgroundColor}`);
709
939
  if (errorStyles.length > 0) {
710
940
  css.push(`.error{${errorStyles.join(';')};}`);
941
+ if (isConnected && styles.error.borderColor) {
942
+ const ec = styles.error.borderColor;
943
+ css.push(`#ccnumfield.error{border-right-color:${ec};}`);
944
+ css.push(`#ccexpirymonth.error{border-right-color:${ec};}`);
945
+ css.push(`#ccexpiryyear.error{border-right-color:${ec};}`);
946
+ }
711
947
  }
712
948
  }
713
949
  return encodeURIComponent(css.join(''));
714
950
  }
715
- static tokenizeBankAccount(routingNumber, accountNumber, config) {
716
- return fetch(`${config.cardConnectBaseUrl}/cardsecure/api/v1/ccn/tokenize`, {
717
- method: 'POST',
718
- headers: {
719
- 'Content-Type': 'application/json',
720
- },
721
- body: JSON.stringify({
722
- account: `${routingNumber}/${accountNumber}`,
723
- unique: true,
724
- }),
725
- }).then((response) => response.json());
951
+ static tokenizeBankAccount(routingNumber, accountNumber, configOrBaseUrl) {
952
+ return __awaiter(this, void 0, void 0, function* () {
953
+ const baseUrl = typeof configOrBaseUrl === 'string'
954
+ ? configOrBaseUrl
955
+ : configOrBaseUrl.cardConnectBaseUrl;
956
+ const response = yield fetch(`${baseUrl}/cardsecure/api/v1/ccn/tokenize`, {
957
+ method: 'POST',
958
+ headers: {
959
+ 'Content-Type': 'application/json',
960
+ },
961
+ body: JSON.stringify({
962
+ account: `${routingNumber}/${accountNumber}`,
963
+ unique: true,
964
+ }),
965
+ });
966
+ if (!response.ok) {
967
+ throw new TokenizationError(`Bank account tokenization failed: HTTP ${response.status}`, 'API_ERROR');
968
+ }
969
+ const result = yield response.json();
970
+ if (!result.token) {
971
+ throw new TokenizationError(result.message || 'Bank account tokenization failed: no token returned', 'API_ERROR');
972
+ }
973
+ return result;
974
+ });
726
975
  }
727
976
  }