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