@idonatedev/idonate-sdk 1.1.0-dev12 → 1.1.0-dev14

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 (54) hide show
  1. package/README.md +27 -0
  2. package/dist/esm/apple-pay.d.ts +12 -0
  3. package/dist/esm/apple-pay.js +74 -0
  4. package/dist/esm/cloudflare-challenge-handler.d.ts +5 -0
  5. package/dist/esm/cloudflare-challenge-handler.js +77 -0
  6. package/dist/esm/config-handler.d.ts +22 -0
  7. package/dist/esm/config-handler.js +47 -0
  8. package/dist/esm/constants.d.ts +18 -0
  9. package/dist/esm/constants.js +82 -0
  10. package/dist/esm/google-pay.d.ts +18 -0
  11. package/dist/esm/google-pay.js +140 -0
  12. package/dist/esm/idonate-client.d.ts +28 -0
  13. package/dist/esm/idonate-client.js +256 -0
  14. package/dist/esm/index.d.ts +11 -0
  15. package/dist/esm/index.js +11 -0
  16. package/dist/esm/recaptcha.d.ts +12 -0
  17. package/dist/esm/recaptcha.js +89 -0
  18. package/dist/esm/shared.d.ts +3 -0
  19. package/dist/esm/shared.js +13 -0
  20. package/dist/esm/test-utils.d.ts +81 -0
  21. package/dist/esm/test-utils.js +94 -0
  22. package/dist/esm/tokenize/CardConnectTokenizer.d.ts +51 -0
  23. package/dist/esm/tokenize/CardConnectTokenizer.js +706 -0
  24. package/dist/esm/tokenize/SpreedlyTokenizer.d.ts +92 -0
  25. package/dist/esm/tokenize/SpreedlyTokenizer.js +1140 -0
  26. package/dist/esm/tokenize/Tokenizer.d.ts +37 -0
  27. package/dist/esm/tokenize/Tokenizer.js +146 -0
  28. package/dist/esm/tokenize/iats.d.ts +3 -0
  29. package/dist/esm/tokenize/iats.js +48 -0
  30. package/dist/esm/tokenize/index.d.ts +6 -0
  31. package/dist/esm/tokenize/index.js +6 -0
  32. package/dist/esm/tokenize/spreedly-secure.d.ts +8 -0
  33. package/dist/esm/tokenize/spreedly-secure.js +40 -0
  34. package/dist/esm/tokenize/styles.d.ts +4 -0
  35. package/dist/esm/tokenize/styles.js +46 -0
  36. package/dist/esm/tokenize/tokenizer-constants.d.ts +62 -0
  37. package/dist/esm/tokenize/tokenizer-constants.js +62 -0
  38. package/dist/esm/tokenize/tokenizer-utils.d.ts +19 -0
  39. package/dist/esm/tokenize/tokenizer-utils.js +139 -0
  40. package/dist/esm/tokenize/types.d.ts +144 -0
  41. package/dist/esm/tokenize/types.js +26 -0
  42. package/dist/esm/typeAdapters.d.ts +29 -0
  43. package/dist/esm/typeAdapters.js +188 -0
  44. package/dist/esm/types.d.ts +367 -0
  45. package/dist/esm/types.js +14 -0
  46. package/dist/esm/util.d.ts +17 -0
  47. package/dist/esm/util.js +113 -0
  48. package/dist/typeAdapters.js +0 -9
  49. package/package.json +52 -23
  50. package/umd/idonate-sdk.js +1 -1
  51. package/dist/tokenize/cardconnect.d.ts +0 -3
  52. package/dist/tokenize/cardconnect.js +0 -16
  53. package/dist/tokenize/spreedly.d.ts +0 -48
  54. package/dist/tokenize/spreedly.js +0 -208
@@ -0,0 +1,1140 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { Tokenizer } from './Tokenizer';
11
+ import { TokenizationError, } from './types';
12
+ import { SPREEDLY_TOKENIZER_URL } from '../constants';
13
+ import { extractSpreedlyToken, unpackSpreedlyResponse } from '../typeAdapters';
14
+ import { fetchSpreedlySecurityArgs, } from './spreedly-secure';
15
+ import { DEFAULT_UNIFIED_STYLES, mergeStyles, getContainerStylesForLayout, } from './styles';
16
+ import { INIT_TIMEOUT, TOKENIZE_TIMEOUT, FIELD_FLEX, BANK_FIELD_FLEX, CANADIAN_BANK_FIELD_FLEX, PLACEHOLDERS, FIELD_CONSTRAINTS, } from './tokenizer-constants';
17
+ import { createFieldContainer, createInputElement, validateRoutingNumber, validateAccountNumber, createAccountTypeSelect, validateInstitutionNumber, validateTransitNumber, validateCanadianAccountNumber, formatCanadianRoutingNumber, } from './tokenizer-utils';
18
+ const SPREEDLY_SCRIPT_URL = 'https://core.spreedly.com/iframe/iframe-v1.min.js';
19
+ const SPREEDLY_SCRIPT_ID = 'spreedly-iframe-script';
20
+ export class SpreedlyTokenizer extends Tokenizer {
21
+ constructor(environmentKey, containerId, styles, mode = 'credit_card', bankCountry = 'US', enableTestMode = false, layout = 'single-line') {
22
+ super();
23
+ this.environmentKey = environmentKey;
24
+ this.containerId = containerId;
25
+ this.layout = layout;
26
+ this.isReady = false;
27
+ this.bankCountry = 'US';
28
+ this.enableTestMode = false;
29
+ this.mode = mode;
30
+ this.bankCountry = bankCountry;
31
+ this.enableTestMode = enableTestMode;
32
+ if (mode === 'credit_card') {
33
+ const SpreedlyFrame = window.SpreedlyPaymentFrame;
34
+ if (SpreedlyFrame) {
35
+ this.spreedly = new SpreedlyFrame();
36
+ }
37
+ else {
38
+ this.spreedly = window.Spreedly;
39
+ }
40
+ }
41
+ else {
42
+ this.spreedly = window.Spreedly;
43
+ }
44
+ if (mode === 'bank_account') {
45
+ this.currentValidationState = {
46
+ isValid: false,
47
+ routingNumber: { isValid: false, isEmpty: true },
48
+ accountNumber: { isValid: false, isEmpty: true },
49
+ };
50
+ }
51
+ else {
52
+ this.currentValidationState = {
53
+ isValid: false,
54
+ cardNumber: { isValid: false, isEmpty: true },
55
+ cvv: { isValid: false, isEmpty: true },
56
+ expiry: { isValid: false, isEmpty: true },
57
+ };
58
+ }
59
+ this.mergedStyles = getContainerStylesForLayout(mergeStyles(DEFAULT_UNIFIED_STYLES, styles), this.layout);
60
+ const fieldIds = this.generateFieldIds(containerId);
61
+ this.numberEl = fieldIds.numberId;
62
+ this.cvvEl = fieldIds.cvvId;
63
+ this.expiryId = fieldIds.expiryId;
64
+ }
65
+ static create(gateway, container, config) {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ var _a;
68
+ if (container.mode !== 'bank_account') {
69
+ yield SpreedlyTokenizer.ensureSpreedlyLoaded();
70
+ }
71
+ let environmentKey = (_a = gateway.config) === null || _a === void 0 ? void 0 : _a.environment_key;
72
+ if (!environmentKey && config.clientConfig.spreedlyEnvironmentKey) {
73
+ environmentKey = config.clientConfig.spreedlyEnvironmentKey;
74
+ }
75
+ if (!environmentKey) {
76
+ environmentKey = '';
77
+ }
78
+ const tokenizer = new SpreedlyTokenizer(environmentKey, container.containerId, container.styling, container.mode || 'credit_card', container.bankCountry || 'US', container.enableTestMode || false, container.layout || 'single-line');
79
+ tokenizer.organizationId = config.organizationId;
80
+ tokenizer.embedId = config.embedId;
81
+ tokenizer.clientConfig = config.clientConfig;
82
+ tokenizer.createInternalElements();
83
+ let securityArgs;
84
+ if (container.mode === 'credit_card' &&
85
+ config.clientConfig.enableSpreedlySecureTokenization) {
86
+ if (!config.organizationId || !config.embedId) {
87
+ throw new Error('Secure tokenization is enabled but organizationId and embedId are required');
88
+ }
89
+ try {
90
+ securityArgs = yield fetchSpreedlySecurityArgs(config.clientConfig, config.organizationId, config.embedId);
91
+ }
92
+ catch (error) {
93
+ throw new Error(`Secure tokenization is enabled but failed to initialize: ${error instanceof Error ? error.message : 'Unknown error'}`);
94
+ }
95
+ }
96
+ yield tokenizer.init(securityArgs);
97
+ return tokenizer;
98
+ });
99
+ }
100
+ init(securityArgs) {
101
+ return __awaiter(this, void 0, void 0, function* () {
102
+ this.containerEl = document.getElementById(this.containerId);
103
+ if (this.mode === 'bank_account') {
104
+ this.isReady = true;
105
+ if (this.enableTestMode) {
106
+ setTimeout(() => {
107
+ if (this.bankCountry === 'CA') {
108
+ if (this.institutionNumberEl) {
109
+ this.institutionNumberEl.value = '004';
110
+ this.institutionNumberEl.dispatchEvent(new Event('input', { bubbles: true }));
111
+ }
112
+ if (this.transitNumberEl) {
113
+ this.transitNumberEl.value = '12345';
114
+ this.transitNumberEl.dispatchEvent(new Event('input', { bubbles: true }));
115
+ }
116
+ if (this.accountNumberEl) {
117
+ this.accountNumberEl.value = '1234567';
118
+ this.accountNumberEl.dispatchEvent(new Event('input', { bubbles: true }));
119
+ }
120
+ }
121
+ else {
122
+ if (this.routingNumberEl) {
123
+ this.routingNumberEl.value = '021000021';
124
+ this.routingNumberEl.dispatchEvent(new Event('input', { bubbles: true }));
125
+ }
126
+ if (this.accountNumberEl) {
127
+ this.accountNumberEl.value = '9876543210';
128
+ this.accountNumberEl.dispatchEvent(new Event('input', { bubbles: true }));
129
+ }
130
+ if (this.accountTypeEl) {
131
+ this.accountTypeEl.value = 'checking';
132
+ this.accountTypeEl.dispatchEvent(new Event('change', { bubbles: true }));
133
+ }
134
+ }
135
+ }, 100);
136
+ }
137
+ this.emit('ready');
138
+ return Promise.resolve();
139
+ }
140
+ return new Promise((resolve, reject) => {
141
+ const timeout = setTimeout(() => {
142
+ reject(new Error('Spreedly initialization timeout'));
143
+ }, INIT_TIMEOUT);
144
+ this.spreedly.on('ready', () => {
145
+ clearTimeout(timeout);
146
+ this.isReady = true;
147
+ this.spreedly.setPlaceholder('number', PLACEHOLDERS.cardNumber);
148
+ this.spreedly.setFieldType('number', 'text');
149
+ this.spreedly.setNumberFormat('prettyFormat');
150
+ this.spreedly.setPlaceholder('cvv', PLACEHOLDERS.cvv);
151
+ this.spreedly.setFieldType('cvv', 'text');
152
+ this.applyUnifiedStyles();
153
+ if (this.enableTestMode &&
154
+ this.mode === 'credit_card' &&
155
+ this.spreedly.setValue) {
156
+ this.spreedly.setValue('number', '4111111111111111');
157
+ this.spreedly.setValue('cvv', '123');
158
+ setTimeout(() => {
159
+ if (this.expiryEl) {
160
+ this.expiryEl.value = '12/34';
161
+ this.expiryEl.dispatchEvent(new Event('input', { bubbles: true }));
162
+ }
163
+ }, 100);
164
+ }
165
+ this.emit('ready');
166
+ resolve();
167
+ });
168
+ this.spreedly.on('errors', (errors) => {
169
+ this.emit('error', new TokenizationError(errors));
170
+ });
171
+ this.spreedly.on('fieldEvent', (fieldName, eventType, activeEl, inputProperties) => {
172
+ this.handleFieldEvent(fieldName, eventType, inputProperties);
173
+ });
174
+ const initOptions = {
175
+ numberEl: this.numberEl,
176
+ 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);
185
+ });
186
+ });
187
+ }
188
+ tokenize(paymentData) {
189
+ return __awaiter(this, void 0, void 0, function* () {
190
+ if (!this.isReady) {
191
+ throw new Error('Tokenizer not initialized');
192
+ }
193
+ if (this.mode === 'bank_account' || this.isBankAccountData(paymentData)) {
194
+ return this.tokenizeBankAccountInternal(paymentData);
195
+ }
196
+ else {
197
+ return this.tokenizeCardInternal(paymentData);
198
+ }
199
+ });
200
+ }
201
+ tokenizeCardInternal(cardData) {
202
+ return __awaiter(this, void 0, void 0, function* () {
203
+ return new Promise((resolve, reject) => {
204
+ var _a, _b, _c, _d, _e, _f;
205
+ const timeout = setTimeout(() => {
206
+ if (this.spreedly && this.spreedly.removeHandlers) {
207
+ this.spreedly.removeHandlers();
208
+ }
209
+ reject(new TokenizationError('Tokenization timeout', 'TIMEOUT'));
210
+ }, 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 TokenizationError(errors));
231
+ });
232
+ const expiry = this.parseExpiry(this.expiryEl);
233
+ if (!expiry) {
234
+ cleanup();
235
+ reject(new 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.line1,
248
+ address2: (_b = cardData.address) === null || _b === void 0 ? void 0 : _b.line2,
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.postalCode,
252
+ country: (_f = cardData.address) === null || _f === void 0 ? void 0 : _f.country,
253
+ });
254
+ });
255
+ });
256
+ }
257
+ createCanadianBankAccountFields(container) {
258
+ if (this.layout === 'two-line') {
259
+ this.accountTypeEl = createAccountTypeSelect(`${this.containerId}-account-type`);
260
+ Object.assign(this.accountTypeEl.style, {
261
+ flex: '2',
262
+ minWidth: CANADIAN_BANK_FIELD_FLEX.accountType.minWidth,
263
+ });
264
+ this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'left');
265
+ container.appendChild(this.accountTypeEl);
266
+ this.institutionNumberEl = createInputElement(`${this.containerId}-institution`, 'text', 'Inst *', 3);
267
+ Object.assign(this.institutionNumberEl.style, {
268
+ flex: '1',
269
+ minWidth: CANADIAN_BANK_FIELD_FLEX.institutionNumber.minWidth,
270
+ });
271
+ this.applyInputStyles(this.institutionNumberEl, this.mergedStyles, 'middle');
272
+ this.institutionNumberEl.addEventListener('blur', () => {
273
+ const isValid = validateInstitutionNumber(this.institutionNumberEl.value);
274
+ if (!isValid && this.institutionNumberEl.value) {
275
+ this.applyErrorStyles(this.institutionNumberEl);
276
+ }
277
+ else {
278
+ this.applyInputStyles(this.institutionNumberEl, this.mergedStyles, 'middle');
279
+ }
280
+ this.updateBankAccountValidation();
281
+ });
282
+ container.appendChild(this.institutionNumberEl);
283
+ this.transitNumberEl = createInputElement(`${this.containerId}-transit`, 'text', 'Transit *', 5);
284
+ Object.assign(this.transitNumberEl.style, {
285
+ flex: '2',
286
+ minWidth: CANADIAN_BANK_FIELD_FLEX.transitNumber.minWidth,
287
+ });
288
+ this.applyInputStyles(this.transitNumberEl, this.mergedStyles, 'right');
289
+ this.transitNumberEl.addEventListener('blur', () => {
290
+ const isValid = validateTransitNumber(this.transitNumberEl.value);
291
+ if (!isValid && this.transitNumberEl.value) {
292
+ this.applyErrorStyles(this.transitNumberEl);
293
+ }
294
+ else {
295
+ this.applyInputStyles(this.transitNumberEl, this.mergedStyles, 'right');
296
+ }
297
+ this.updateBankAccountValidation();
298
+ });
299
+ container.appendChild(this.transitNumberEl);
300
+ this.accountNumberEl = createInputElement(`${this.containerId}-account`, 'text', 'Account Number *');
301
+ Object.assign(this.accountNumberEl.style, {
302
+ flex: '1',
303
+ flexBasis: '100%',
304
+ minWidth: CANADIAN_BANK_FIELD_FLEX.accountNumber.minWidth,
305
+ });
306
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles);
307
+ this.accountNumberEl.addEventListener('blur', () => {
308
+ const isValid = validateCanadianAccountNumber(this.accountNumberEl.value);
309
+ if (!isValid && this.accountNumberEl.value) {
310
+ this.applyErrorStyles(this.accountNumberEl);
311
+ }
312
+ else {
313
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles);
314
+ }
315
+ this.updateBankAccountValidation();
316
+ });
317
+ container.appendChild(this.accountNumberEl);
318
+ }
319
+ else {
320
+ this.institutionNumberEl = createInputElement(`${this.containerId}-institution`, 'text', 'Inst *', 3);
321
+ Object.assign(this.institutionNumberEl.style, {
322
+ flex: CANADIAN_BANK_FIELD_FLEX.institutionNumber.flex,
323
+ minWidth: CANADIAN_BANK_FIELD_FLEX.institutionNumber.minWidth,
324
+ });
325
+ this.applyInputStyles(this.institutionNumberEl, this.mergedStyles, 'left');
326
+ this.institutionNumberEl.addEventListener('blur', () => {
327
+ const isValid = validateInstitutionNumber(this.institutionNumberEl.value);
328
+ if (!isValid && this.institutionNumberEl.value) {
329
+ this.applyErrorStyles(this.institutionNumberEl);
330
+ }
331
+ else {
332
+ this.applyInputStyles(this.institutionNumberEl, this.mergedStyles, 'left');
333
+ }
334
+ this.updateBankAccountValidation();
335
+ });
336
+ container.appendChild(this.institutionNumberEl);
337
+ this.transitNumberEl = createInputElement(`${this.containerId}-transit`, 'text', 'Transit *', 5);
338
+ Object.assign(this.transitNumberEl.style, {
339
+ flex: CANADIAN_BANK_FIELD_FLEX.transitNumber.flex,
340
+ minWidth: CANADIAN_BANK_FIELD_FLEX.transitNumber.minWidth,
341
+ });
342
+ this.applyInputStyles(this.transitNumberEl, this.mergedStyles, 'middle');
343
+ this.transitNumberEl.addEventListener('blur', () => {
344
+ const isValid = validateTransitNumber(this.transitNumberEl.value);
345
+ if (!isValid && this.transitNumberEl.value) {
346
+ this.applyErrorStyles(this.transitNumberEl);
347
+ }
348
+ else {
349
+ this.applyInputStyles(this.transitNumberEl, this.mergedStyles, 'middle');
350
+ }
351
+ this.updateBankAccountValidation();
352
+ });
353
+ container.appendChild(this.transitNumberEl);
354
+ this.accountNumberEl = createInputElement(`${this.containerId}-account`, 'text', 'Account Number *');
355
+ Object.assign(this.accountNumberEl.style, {
356
+ flex: CANADIAN_BANK_FIELD_FLEX.accountNumber.flex,
357
+ minWidth: CANADIAN_BANK_FIELD_FLEX.accountNumber.minWidth,
358
+ });
359
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles, 'middle');
360
+ this.accountNumberEl.addEventListener('blur', () => {
361
+ const isValid = validateCanadianAccountNumber(this.accountNumberEl.value);
362
+ if (!isValid && this.accountNumberEl.value) {
363
+ this.applyErrorStyles(this.accountNumberEl);
364
+ }
365
+ else {
366
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles, 'middle');
367
+ }
368
+ this.updateBankAccountValidation();
369
+ });
370
+ container.appendChild(this.accountNumberEl);
371
+ this.accountTypeEl = createAccountTypeSelect(`${this.containerId}-account-type`);
372
+ Object.assign(this.accountTypeEl.style, {
373
+ flex: CANADIAN_BANK_FIELD_FLEX.accountType.flex,
374
+ minWidth: CANADIAN_BANK_FIELD_FLEX.accountType.minWidth,
375
+ });
376
+ this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'right');
377
+ container.appendChild(this.accountTypeEl);
378
+ }
379
+ }
380
+ tokenizeBankAccountInternal(bankData) {
381
+ return __awaiter(this, void 0, void 0, function* () {
382
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
383
+ if (!this.isReady) {
384
+ throw new Error('Tokenizer not initialized');
385
+ }
386
+ if (this.mode !== 'bank_account') {
387
+ throw new Error('Tokenizer not in bank account mode');
388
+ }
389
+ let routingNumber;
390
+ let accountNumber;
391
+ const accountType = ((_a = this.accountTypeEl) === null || _a === void 0 ? void 0 : _a.value) || 'checking';
392
+ const accountHolderType = 'personal';
393
+ if (this.bankCountry === 'CA') {
394
+ const institutionNumber = (_b = this.institutionNumberEl) === null || _b === void 0 ? void 0 : _b.value;
395
+ const transitNumber = (_c = this.transitNumberEl) === null || _c === void 0 ? void 0 : _c.value;
396
+ accountNumber = (_d = this.accountNumberEl) === null || _d === void 0 ? void 0 : _d.value;
397
+ if (!institutionNumber || !transitNumber || !accountNumber) {
398
+ throw new TokenizationError('Institution number, transit number, and account number are required', 'VALIDATION_ERROR');
399
+ }
400
+ if (!validateInstitutionNumber(institutionNumber)) {
401
+ throw new TokenizationError('Institution number must be 3 digits', 'VALIDATION_ERROR');
402
+ }
403
+ if (!validateTransitNumber(transitNumber)) {
404
+ throw new TokenizationError('Transit number must be 5 digits', 'VALIDATION_ERROR');
405
+ }
406
+ if (!validateCanadianAccountNumber(accountNumber)) {
407
+ throw new TokenizationError('Invalid account number', 'VALIDATION_ERROR');
408
+ }
409
+ routingNumber = formatCanadianRoutingNumber(institutionNumber, transitNumber);
410
+ }
411
+ else {
412
+ routingNumber = (_e = this.routingNumberEl) === null || _e === void 0 ? void 0 : _e.value;
413
+ accountNumber = (_f = this.accountNumberEl) === null || _f === void 0 ? void 0 : _f.value;
414
+ if (!routingNumber || !accountNumber) {
415
+ throw new TokenizationError('Routing number and account number are required', 'VALIDATION_ERROR');
416
+ }
417
+ if (!validateRoutingNumber(routingNumber)) {
418
+ throw new TokenizationError('Invalid routing number', 'VALIDATION_ERROR');
419
+ }
420
+ if (!validateAccountNumber(accountNumber)) {
421
+ throw new TokenizationError('Invalid account number', 'VALIDATION_ERROR');
422
+ }
423
+ }
424
+ const result = yield SpreedlyTokenizer.tokenizeBankAccount({
425
+ contact: {
426
+ firstName: bankData.firstName,
427
+ lastName: bankData.lastName,
428
+ email: bankData.email,
429
+ primaryPhone: bankData.phone,
430
+ },
431
+ address: {
432
+ address1: (_g = bankData.address) === null || _g === void 0 ? void 0 : _g.line1,
433
+ address2: (_h = bankData.address) === null || _h === void 0 ? void 0 : _h.line2,
434
+ city: (_j = bankData.address) === null || _j === void 0 ? void 0 : _j.city,
435
+ state: (_k = bankData.address) === null || _k === void 0 ? void 0 : _k.state,
436
+ zip: (_l = bankData.address) === null || _l === void 0 ? void 0 : _l.postalCode,
437
+ country: (_m = bankData.address) === null || _m === void 0 ? void 0 : _m.country,
438
+ },
439
+ account: {
440
+ accountNumber,
441
+ routingNumber,
442
+ accountType: accountType,
443
+ accountHolderType: accountHolderType,
444
+ },
445
+ }, this.clientConfig);
446
+ return {
447
+ token: result,
448
+ lastFour: accountNumber.slice(-4),
449
+ accountType: accountType,
450
+ paymentMethodType: 'bank_account',
451
+ provider: 'spreedly',
452
+ };
453
+ });
454
+ }
455
+ validate() {
456
+ return __awaiter(this, void 0, void 0, function* () {
457
+ if (this.mode === 'bank_account') {
458
+ return this.validateBankAccountInternal();
459
+ }
460
+ else {
461
+ return this.validateCardInternal();
462
+ }
463
+ });
464
+ }
465
+ validateCardInternal() {
466
+ return __awaiter(this, void 0, void 0, function* () {
467
+ var _a, _b, _c, _d, _e;
468
+ const errors = [];
469
+ if ((_a = this.currentValidationState.cardNumber) === null || _a === void 0 ? void 0 : _a.isEmpty) {
470
+ errors.push({
471
+ field: 'cardNumber',
472
+ message: 'Card number is required',
473
+ });
474
+ }
475
+ else if (!((_b = this.currentValidationState.cardNumber) === null || _b === void 0 ? void 0 : _b.isValid)) {
476
+ errors.push({
477
+ field: 'cardNumber',
478
+ message: 'Invalid card number',
479
+ });
480
+ }
481
+ if ((_c = this.currentValidationState.cvv) === null || _c === void 0 ? void 0 : _c.isEmpty) {
482
+ errors.push({
483
+ field: 'cvv',
484
+ message: 'CVV is required',
485
+ });
486
+ }
487
+ else if (!((_d = this.currentValidationState.cvv) === null || _d === void 0 ? void 0 : _d.isValid)) {
488
+ errors.push({
489
+ field: 'cvv',
490
+ message: 'Invalid CVV',
491
+ });
492
+ }
493
+ const expiry = this.parseExpiry(this.expiryEl);
494
+ if (!((_e = this.expiryEl) === null || _e === void 0 ? void 0 : _e.value)) {
495
+ errors.push({
496
+ field: 'expiry',
497
+ message: 'Expiration date is required',
498
+ });
499
+ }
500
+ else if (!expiry) {
501
+ errors.push({
502
+ field: 'expiry',
503
+ message: 'Invalid expiration date',
504
+ });
505
+ }
506
+ return {
507
+ isValid: errors.length === 0,
508
+ errors,
509
+ };
510
+ });
511
+ }
512
+ validateBankAccountInternal() {
513
+ return __awaiter(this, void 0, void 0, function* () {
514
+ var _a, _b, _c, _d, _e;
515
+ const errors = [];
516
+ if (this.bankCountry === 'CA') {
517
+ if (!((_a = this.institutionNumberEl) === null || _a === void 0 ? void 0 : _a.value)) {
518
+ errors.push({
519
+ field: 'routingNumber',
520
+ message: 'Institution number is required',
521
+ });
522
+ }
523
+ else if (!validateInstitutionNumber(this.institutionNumberEl.value)) {
524
+ errors.push({
525
+ field: 'routingNumber',
526
+ message: 'Institution number must be 3 digits',
527
+ });
528
+ }
529
+ if (!((_b = this.transitNumberEl) === null || _b === void 0 ? void 0 : _b.value)) {
530
+ errors.push({
531
+ field: 'routingNumber',
532
+ message: 'Transit number is required',
533
+ });
534
+ }
535
+ else if (!validateTransitNumber(this.transitNumberEl.value)) {
536
+ errors.push({
537
+ field: 'routingNumber',
538
+ message: 'Transit number must be 5 digits',
539
+ });
540
+ }
541
+ if (!((_c = this.accountNumberEl) === null || _c === void 0 ? void 0 : _c.value)) {
542
+ errors.push({
543
+ field: 'accountNumber',
544
+ message: 'Account number is required',
545
+ });
546
+ }
547
+ else if (!validateCanadianAccountNumber(this.accountNumberEl.value)) {
548
+ errors.push({
549
+ field: 'accountNumber',
550
+ message: 'Account number must be at least 7 digits',
551
+ });
552
+ }
553
+ }
554
+ else {
555
+ if (!((_d = this.routingNumberEl) === null || _d === void 0 ? void 0 : _d.value)) {
556
+ errors.push({
557
+ field: 'routingNumber',
558
+ message: 'Routing number is required',
559
+ });
560
+ }
561
+ else if (!validateRoutingNumber(this.routingNumberEl.value)) {
562
+ errors.push({
563
+ field: 'routingNumber',
564
+ message: 'Invalid routing number',
565
+ });
566
+ }
567
+ if (!((_e = this.accountNumberEl) === null || _e === void 0 ? void 0 : _e.value)) {
568
+ errors.push({
569
+ field: 'accountNumber',
570
+ message: 'Account number is required',
571
+ });
572
+ }
573
+ else if (!validateAccountNumber(this.accountNumberEl.value)) {
574
+ errors.push({
575
+ field: 'accountNumber',
576
+ message: 'Invalid account number',
577
+ });
578
+ }
579
+ }
580
+ return {
581
+ isValid: errors.length === 0,
582
+ errors,
583
+ };
584
+ });
585
+ }
586
+ applyUnifiedStyles() {
587
+ var _a;
588
+ if (!this.isReady)
589
+ return;
590
+ const isConnected = ((_a = this.mergedStyles.container) === null || _a === void 0 ? void 0 : _a.gap) === '0';
591
+ if (this.mergedStyles.input) {
592
+ if (isConnected) {
593
+ const numberStyles = Object.assign(Object.assign({}, this.mergedStyles.input), { borderRight: 'none', borderRadius: this.mergedStyles.input.borderRadius
594
+ ? `${this.mergedStyles.input.borderRadius} 0 0 ${this.mergedStyles.input.borderRadius}`
595
+ : '0' });
596
+ const numberCss = this.stylesToCssString(numberStyles);
597
+ this.spreedly.setStyle('number', numberCss);
598
+ const cvvStyles = Object.assign(Object.assign({}, this.mergedStyles.input), { borderRadius: this.mergedStyles.input.borderRadius
599
+ ? `0 ${this.mergedStyles.input.borderRadius} ${this.mergedStyles.input.borderRadius} 0`
600
+ : '0' });
601
+ const cvvCss = this.stylesToCssString(cvvStyles);
602
+ this.spreedly.setStyle('cvv', cvvCss);
603
+ }
604
+ else {
605
+ const baseCss = this.stylesToCssString(this.mergedStyles.input);
606
+ this.spreedly.setStyle('number', baseCss);
607
+ this.spreedly.setStyle('cvv', baseCss);
608
+ }
609
+ }
610
+ if (this.mergedStyles.focus) {
611
+ const focusCss = this.stylesToCssString(this.mergedStyles.focus);
612
+ this.spreedly.setStyle('number:focus', focusCss);
613
+ this.spreedly.setStyle('cvv:focus', focusCss);
614
+ }
615
+ if (this.mergedStyles.error) {
616
+ const errorCss = this.stylesToCssString(this.mergedStyles.error);
617
+ this.spreedly.setStyle('number.invalid', errorCss);
618
+ this.spreedly.setStyle('cvv.invalid', errorCss);
619
+ }
620
+ }
621
+ stylesToCssString(styles) {
622
+ return Object.entries(styles)
623
+ .filter(([_, value]) => value !== undefined && value !== null)
624
+ .map(([key, value]) => {
625
+ const cssKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
626
+ return `${cssKey}: ${value}`;
627
+ })
628
+ .join('; ');
629
+ }
630
+ clear() {
631
+ if (this.mode === 'bank_account') {
632
+ if (this.bankCountry === 'CA') {
633
+ if (this.institutionNumberEl)
634
+ this.institutionNumberEl.value = '';
635
+ if (this.transitNumberEl)
636
+ this.transitNumberEl.value = '';
637
+ }
638
+ else {
639
+ if (this.routingNumberEl)
640
+ this.routingNumberEl.value = '';
641
+ }
642
+ if (this.accountNumberEl)
643
+ this.accountNumberEl.value = '';
644
+ if (this.accountTypeEl)
645
+ this.accountTypeEl.selectedIndex = 0;
646
+ }
647
+ else {
648
+ this.spreedly.reload();
649
+ if (this.expiryEl) {
650
+ this.expiryEl.value = '';
651
+ }
652
+ }
653
+ }
654
+ focus(field) {
655
+ if (this.mode === 'bank_account') {
656
+ if (field === 'routingNumber') {
657
+ if (this.bankCountry === 'CA' && this.institutionNumberEl) {
658
+ this.institutionNumberEl.focus();
659
+ }
660
+ else if (this.routingNumberEl) {
661
+ this.routingNumberEl.focus();
662
+ }
663
+ return;
664
+ }
665
+ if (field === 'accountNumber' && this.accountNumberEl) {
666
+ this.accountNumberEl.focus();
667
+ return;
668
+ }
669
+ return;
670
+ }
671
+ if (field === 'expiry' && this.expiryEl) {
672
+ this.expiryEl.focus();
673
+ return;
674
+ }
675
+ const fieldMap = {
676
+ cardNumber: this.numberEl,
677
+ cvv: this.cvvEl,
678
+ expiry: null,
679
+ routingNumber: null,
680
+ accountNumber: null,
681
+ };
682
+ const elementId = fieldMap[field];
683
+ if (elementId) {
684
+ const element = document.getElementById(elementId);
685
+ element === null || element === void 0 ? void 0 : element.focus();
686
+ }
687
+ }
688
+ destroy() {
689
+ if (this.spreedly && this.spreedly.removeHandlers) {
690
+ this.spreedly.removeHandlers();
691
+ }
692
+ this.eventHandlers.clear();
693
+ if (this.expiryEl) {
694
+ }
695
+ }
696
+ hasToken() {
697
+ return false;
698
+ }
699
+ getToken() {
700
+ return null;
701
+ }
702
+ get tokenizationMode() {
703
+ return 'manual';
704
+ }
705
+ updateOverallValidation() {
706
+ var _a, _b, _c, _d, _e, _f;
707
+ if (this.mode === 'credit_card') {
708
+ this.currentValidationState.isValid =
709
+ ((_b = (_a = this.currentValidationState.cardNumber) === null || _a === void 0 ? void 0 : _a.isValid) !== null && _b !== void 0 ? _b : false) &&
710
+ ((_d = (_c = this.currentValidationState.cvv) === null || _c === void 0 ? void 0 : _c.isValid) !== null && _d !== void 0 ? _d : false) &&
711
+ ((_f = (_e = this.currentValidationState.expiry) === null || _e === void 0 ? void 0 : _e.isValid) !== null && _f !== void 0 ? _f : false);
712
+ }
713
+ else {
714
+ this.currentValidationState.isValid = true;
715
+ }
716
+ this.emit('validation', this.currentValidationState);
717
+ }
718
+ handleFieldEvent(fieldName, eventType, inputProperties) {
719
+ if (eventType === 'input') {
720
+ if (fieldName === 'number') {
721
+ this.currentValidationState.cardNumber = {
722
+ isValid: inputProperties.validNumber || false,
723
+ isEmpty: inputProperties.numberLength === 0,
724
+ };
725
+ if (inputProperties.cardType &&
726
+ inputProperties.cardType !== 'unknown') {
727
+ this.emit('cardTypeChange', {
728
+ cardType: this.normalizeCardType(inputProperties.cardType),
729
+ });
730
+ }
731
+ }
732
+ else if (fieldName === 'cvv') {
733
+ this.currentValidationState.cvv = {
734
+ isValid: inputProperties.validCvv || false,
735
+ isEmpty: inputProperties.cvvLength === 0,
736
+ };
737
+ }
738
+ this.updateOverallValidation();
739
+ }
740
+ if (eventType === 'focus') {
741
+ this.emit('focus', { field: fieldName });
742
+ }
743
+ else if (eventType === 'blur') {
744
+ this.emit('blur', { field: fieldName });
745
+ }
746
+ else if (eventType === 'input') {
747
+ this.emit('change', { field: fieldName });
748
+ }
749
+ }
750
+ createInternalElements() {
751
+ const container = document.getElementById(this.containerId);
752
+ if (!container) {
753
+ throw new Error(`Container element not found: ${this.containerId}`);
754
+ }
755
+ container.innerHTML = '';
756
+ Object.assign(container.style, this.mergedStyles.container);
757
+ if (this.mode === 'bank_account') {
758
+ this.createBankAccountFields(container);
759
+ }
760
+ else {
761
+ this.createCreditCardFields(container);
762
+ }
763
+ }
764
+ createCreditCardFields(container) {
765
+ if (this.layout === 'two-line') {
766
+ const cardNumberDiv = createFieldContainer(this.numberEl, '1', FIELD_FLEX.cardNumber.minWidth);
767
+ cardNumberDiv.style.height = this.mergedStyles.input.height;
768
+ cardNumberDiv.style.flexBasis = '100%';
769
+ container.appendChild(cardNumberDiv);
770
+ this.expiryEl = createInputElement(this.expiryId, 'text', PLACEHOLDERS.expiry, FIELD_CONSTRAINTS.expiry.maxLength);
771
+ Object.assign(this.expiryEl.style, {
772
+ flex: '1',
773
+ minWidth: FIELD_FLEX.expiry.minWidth,
774
+ });
775
+ this.applyInputStyles(this.expiryEl, this.mergedStyles, 'left');
776
+ this.addExpiryFormatter(this.expiryEl);
777
+ this.expiryEl.addEventListener('input', () => {
778
+ var _a;
779
+ const expiry = this.parseExpiry(this.expiryEl);
780
+ this.currentValidationState.expiry = {
781
+ isValid: !!expiry,
782
+ isEmpty: !((_a = this.expiryEl) === null || _a === void 0 ? void 0 : _a.value),
783
+ };
784
+ this.updateOverallValidation();
785
+ });
786
+ container.appendChild(this.expiryEl);
787
+ const cvvDiv = createFieldContainer(this.cvvEl, '1', FIELD_FLEX.cvv.minWidth);
788
+ cvvDiv.style.height = this.mergedStyles.input.height;
789
+ container.appendChild(cvvDiv);
790
+ }
791
+ else {
792
+ const cardNumberDiv = createFieldContainer(this.numberEl, FIELD_FLEX.cardNumber.flex, FIELD_FLEX.cardNumber.minWidth);
793
+ cardNumberDiv.style.height = this.mergedStyles.input.height;
794
+ container.appendChild(cardNumberDiv);
795
+ this.expiryEl = createInputElement(this.expiryId, 'text', PLACEHOLDERS.expiry, FIELD_CONSTRAINTS.expiry.maxLength);
796
+ Object.assign(this.expiryEl.style, {
797
+ flex: FIELD_FLEX.expiry.flex,
798
+ minWidth: FIELD_FLEX.expiry.minWidth,
799
+ });
800
+ this.applyInputStyles(this.expiryEl, this.mergedStyles, 'middle');
801
+ this.addExpiryFormatter(this.expiryEl);
802
+ this.expiryEl.addEventListener('input', () => {
803
+ var _a;
804
+ const expiry = this.parseExpiry(this.expiryEl);
805
+ this.currentValidationState.expiry = {
806
+ isValid: !!expiry,
807
+ isEmpty: !((_a = this.expiryEl) === null || _a === void 0 ? void 0 : _a.value),
808
+ };
809
+ this.updateOverallValidation();
810
+ });
811
+ container.appendChild(this.expiryEl);
812
+ const cvvDiv = createFieldContainer(this.cvvEl, FIELD_FLEX.cvv.flex, FIELD_FLEX.cvv.minWidth);
813
+ cvvDiv.style.height = this.mergedStyles.input.height;
814
+ container.appendChild(cvvDiv);
815
+ }
816
+ }
817
+ createBankAccountFields(container) {
818
+ if (this.bankCountry === 'CA') {
819
+ this.createCanadianBankAccountFields(container);
820
+ }
821
+ else {
822
+ this.createUSBankAccountFields(container);
823
+ }
824
+ }
825
+ createUSBankAccountFields(container) {
826
+ if (this.layout === 'two-line') {
827
+ this.accountTypeEl = createAccountTypeSelect(`${this.containerId}-account-type`);
828
+ Object.assign(this.accountTypeEl.style, {
829
+ flex: '1',
830
+ minWidth: BANK_FIELD_FLEX.accountType.minWidth,
831
+ });
832
+ this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'left');
833
+ container.appendChild(this.accountTypeEl);
834
+ this.routingNumberEl = createInputElement(`${this.containerId}-routing`, 'text', 'Routing *', 9);
835
+ Object.assign(this.routingNumberEl.style, {
836
+ flex: '1',
837
+ minWidth: BANK_FIELD_FLEX.routingNumber.minWidth,
838
+ });
839
+ this.applyInputStyles(this.routingNumberEl, this.mergedStyles, 'right');
840
+ this.routingNumberEl.addEventListener('blur', () => {
841
+ const isValid = validateRoutingNumber(this.routingNumberEl.value);
842
+ if (!isValid && this.routingNumberEl.value) {
843
+ this.applyErrorStyles(this.routingNumberEl);
844
+ }
845
+ else {
846
+ this.applyInputStyles(this.routingNumberEl, this.mergedStyles, 'right');
847
+ }
848
+ this.updateBankAccountValidation();
849
+ });
850
+ container.appendChild(this.routingNumberEl);
851
+ this.accountNumberEl = createInputElement(`${this.containerId}-account`, 'text', 'Account Number *', 17);
852
+ Object.assign(this.accountNumberEl.style, {
853
+ flex: '1',
854
+ flexBasis: '100%',
855
+ minWidth: BANK_FIELD_FLEX.accountNumber.minWidth,
856
+ });
857
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles);
858
+ this.accountNumberEl.addEventListener('blur', () => {
859
+ const isValid = validateAccountNumber(this.accountNumberEl.value);
860
+ if (!isValid && this.accountNumberEl.value) {
861
+ this.applyErrorStyles(this.accountNumberEl);
862
+ }
863
+ else {
864
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles);
865
+ }
866
+ this.updateBankAccountValidation();
867
+ });
868
+ container.appendChild(this.accountNumberEl);
869
+ }
870
+ else {
871
+ this.routingNumberEl = createInputElement(`${this.containerId}-routing`, 'text', 'Routing *', 9);
872
+ Object.assign(this.routingNumberEl.style, {
873
+ flex: BANK_FIELD_FLEX.routingNumber.flex,
874
+ minWidth: BANK_FIELD_FLEX.routingNumber.minWidth,
875
+ });
876
+ this.applyInputStyles(this.routingNumberEl, this.mergedStyles, 'left');
877
+ this.routingNumberEl.addEventListener('blur', () => {
878
+ const isValid = validateRoutingNumber(this.routingNumberEl.value);
879
+ if (!isValid && this.routingNumberEl.value) {
880
+ this.applyErrorStyles(this.routingNumberEl);
881
+ }
882
+ else {
883
+ this.applyInputStyles(this.routingNumberEl, this.mergedStyles, 'left');
884
+ }
885
+ this.updateBankAccountValidation();
886
+ });
887
+ container.appendChild(this.routingNumberEl);
888
+ this.accountNumberEl = createInputElement(`${this.containerId}-account`, 'text', 'Account Number *', 17);
889
+ Object.assign(this.accountNumberEl.style, {
890
+ flex: BANK_FIELD_FLEX.accountNumber.flex,
891
+ minWidth: BANK_FIELD_FLEX.accountNumber.minWidth,
892
+ });
893
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles, 'middle');
894
+ this.accountNumberEl.addEventListener('blur', () => {
895
+ const isValid = validateAccountNumber(this.accountNumberEl.value);
896
+ if (!isValid && this.accountNumberEl.value) {
897
+ this.applyErrorStyles(this.accountNumberEl);
898
+ }
899
+ else {
900
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles, 'middle');
901
+ }
902
+ this.updateBankAccountValidation();
903
+ });
904
+ container.appendChild(this.accountNumberEl);
905
+ this.accountTypeEl = createAccountTypeSelect(`${this.containerId}-account-type`);
906
+ Object.assign(this.accountTypeEl.style, {
907
+ flex: BANK_FIELD_FLEX.accountType.flex,
908
+ minWidth: BANK_FIELD_FLEX.accountType.minWidth,
909
+ });
910
+ this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'right');
911
+ container.appendChild(this.accountTypeEl);
912
+ }
913
+ }
914
+ updateBankAccountValidation() {
915
+ if (this.bankCountry === 'CA') {
916
+ if (!this.institutionNumberEl ||
917
+ !this.transitNumberEl ||
918
+ !this.accountNumberEl)
919
+ return;
920
+ const institutionValid = validateInstitutionNumber(this.institutionNumberEl.value);
921
+ const transitValid = validateTransitNumber(this.transitNumberEl.value);
922
+ const accountValid = validateCanadianAccountNumber(this.accountNumberEl.value);
923
+ this.currentValidationState = {
924
+ isValid: institutionValid && transitValid && accountValid,
925
+ routingNumber: {
926
+ isValid: institutionValid && transitValid,
927
+ isEmpty: !this.institutionNumberEl.value && !this.transitNumberEl.value,
928
+ error: !institutionValid
929
+ ? 'Invalid institution number'
930
+ : !transitValid
931
+ ? 'Invalid transit number'
932
+ : undefined,
933
+ },
934
+ accountNumber: {
935
+ isValid: accountValid,
936
+ isEmpty: !this.accountNumberEl.value,
937
+ error: accountValid
938
+ ? undefined
939
+ : 'Account number must be at least 7 digits',
940
+ },
941
+ };
942
+ }
943
+ else {
944
+ if (!this.routingNumberEl || !this.accountNumberEl)
945
+ return;
946
+ const routingValid = validateRoutingNumber(this.routingNumberEl.value);
947
+ const accountValid = validateAccountNumber(this.accountNumberEl.value);
948
+ this.currentValidationState = {
949
+ isValid: routingValid && accountValid,
950
+ routingNumber: {
951
+ isValid: routingValid,
952
+ isEmpty: !this.routingNumberEl.value,
953
+ error: routingValid ? undefined : 'Invalid routing number',
954
+ },
955
+ accountNumber: {
956
+ isValid: accountValid,
957
+ isEmpty: !this.accountNumberEl.value,
958
+ error: accountValid ? undefined : 'Invalid account number',
959
+ },
960
+ };
961
+ }
962
+ this.emit('validation', this.currentValidationState);
963
+ }
964
+ validateExpiry() {
965
+ if (!this.expiryEl)
966
+ return;
967
+ const expiry = this.parseExpiry(this.expiryEl);
968
+ if (!expiry) {
969
+ this.applyErrorStyles(this.expiryEl);
970
+ return;
971
+ }
972
+ const month = parseInt(expiry.month, 10);
973
+ const year = parseInt(expiry.year, 10);
974
+ if (month < 1 || month > 12) {
975
+ this.applyErrorStyles(this.expiryEl);
976
+ return;
977
+ }
978
+ const now = new Date();
979
+ const expiryDate = new Date(year, month - 1);
980
+ if (expiryDate < now) {
981
+ this.applyErrorStyles(this.expiryEl);
982
+ return;
983
+ }
984
+ this.applyInputStyles(this.expiryEl, this.mergedStyles, 'middle');
985
+ }
986
+ applyErrorStyles(element) {
987
+ if (this.mergedStyles.error) {
988
+ Object.assign(element.style, this.mergedStyles.error);
989
+ }
990
+ else {
991
+ element.style.borderColor = '#dc3545';
992
+ }
993
+ }
994
+ static ensureSpreedlyLoaded() {
995
+ return __awaiter(this, void 0, void 0, function* () {
996
+ if (window.Spreedly) {
997
+ return;
998
+ }
999
+ yield SpreedlyTokenizer.loadScript(SPREEDLY_SCRIPT_URL, SPREEDLY_SCRIPT_ID);
1000
+ yield SpreedlyTokenizer.waitForGlobal('Spreedly', 5000);
1001
+ });
1002
+ }
1003
+ static loadScript(src, id) {
1004
+ return __awaiter(this, void 0, void 0, function* () {
1005
+ if (id && document.getElementById(id)) {
1006
+ return;
1007
+ }
1008
+ const existingScript = Array.from(document.getElementsByTagName('script')).find((script) => script.src === src);
1009
+ if (existingScript) {
1010
+ return;
1011
+ }
1012
+ return new Promise((resolve, reject) => {
1013
+ const script = document.createElement('script');
1014
+ script.src = src;
1015
+ script.async = true;
1016
+ if (id) {
1017
+ script.id = id;
1018
+ }
1019
+ script.onload = () => resolve();
1020
+ script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
1021
+ document.body.appendChild(script);
1022
+ });
1023
+ });
1024
+ }
1025
+ static waitForGlobal(globalName_1) {
1026
+ return __awaiter(this, arguments, void 0, function* (globalName, timeout = 10000) {
1027
+ const startTime = Date.now();
1028
+ while (Date.now() - startTime < timeout) {
1029
+ const global = window[globalName];
1030
+ if (global) {
1031
+ return global;
1032
+ }
1033
+ yield new Promise((resolve) => setTimeout(resolve, 100));
1034
+ }
1035
+ throw new Error(`Timeout waiting for global: ${globalName}`);
1036
+ });
1037
+ }
1038
+ static tokenizeBankAccount(data, config) {
1039
+ if (config.spreedlyEnvironmentKey === undefined) {
1040
+ return Promise.reject(new Error('Spreedly is not configured.'));
1041
+ }
1042
+ return fetch(SPREEDLY_TOKENIZER_URL +
1043
+ '?' +
1044
+ new URLSearchParams({ environment_key: config.spreedlyEnvironmentKey }), {
1045
+ method: 'POST',
1046
+ headers: {
1047
+ 'Content-Type': 'application/json',
1048
+ },
1049
+ body: JSON.stringify({
1050
+ payment_method: {
1051
+ bank_account: {
1052
+ first_name: data.contact.firstName || undefined,
1053
+ last_name: data.contact.lastName || undefined,
1054
+ full_name: data.contact.fullName || undefined,
1055
+ email: data.contact.email,
1056
+ address1: data.address.address1,
1057
+ address2: data.address.address2,
1058
+ city: data.address.city,
1059
+ state: data.address.state,
1060
+ zip: data.address.zip,
1061
+ country: data.address.country,
1062
+ bank_account_number: data.account.accountNumber.replace(/\s+/g, ''),
1063
+ bank_routing_number: data.account.routingNumber.replace(/\s+/g, ''),
1064
+ bank_account_holder_type: data.account.accountHolderType,
1065
+ bank_account_type: data.account.accountType,
1066
+ },
1067
+ },
1068
+ retained: false,
1069
+ }),
1070
+ })
1071
+ .then(unpackSpreedlyResponse)
1072
+ .then(extractSpreedlyToken);
1073
+ }
1074
+ static tokenizeCreditCard(data, config) {
1075
+ if (config.spreedlyEnvironmentKey === undefined) {
1076
+ return Promise.reject(new Error('Spreedly is not configured.'));
1077
+ }
1078
+ return fetch(SPREEDLY_TOKENIZER_URL +
1079
+ '?' +
1080
+ new URLSearchParams({ environment_key: config.spreedlyEnvironmentKey }), {
1081
+ method: 'POST',
1082
+ headers: {
1083
+ 'Content-Type': 'application/json',
1084
+ },
1085
+ body: JSON.stringify({
1086
+ payment_method: {
1087
+ credit_card: {
1088
+ first_name: data.contact.firstName,
1089
+ last_name: data.contact.lastName,
1090
+ full_name: data.contact.fullName,
1091
+ email: data.contact.email,
1092
+ address1: data.address.address1,
1093
+ address2: data.address.address2,
1094
+ city: data.address.city,
1095
+ state: data.address.state,
1096
+ zip: data.address.zip,
1097
+ country: data.address.country,
1098
+ number: data.card.cardNumber.replace(/\s+/g, ''),
1099
+ verification_value: data.card.verificationValue,
1100
+ year: data.card.expirationYear,
1101
+ month: data.card.expirationMonth,
1102
+ },
1103
+ },
1104
+ retained: false,
1105
+ }),
1106
+ })
1107
+ .then(unpackSpreedlyResponse)
1108
+ .then(extractSpreedlyToken);
1109
+ }
1110
+ static tokenizePayPal(data, config) {
1111
+ if (config.spreedlyEnvironmentKey === undefined) {
1112
+ throw new Error('Spreedly is not configured.');
1113
+ }
1114
+ return fetch(SPREEDLY_TOKENIZER_URL +
1115
+ '?' +
1116
+ new URLSearchParams({ environment_key: config.spreedlyEnvironmentKey }), {
1117
+ method: 'POST',
1118
+ headers: {
1119
+ 'Content-Type': 'application/json',
1120
+ },
1121
+ body: JSON.stringify({
1122
+ payment_method: {
1123
+ payment_method_type: 'paypal',
1124
+ first_name: data.contact.firstName,
1125
+ last_name: data.contact.lastName,
1126
+ email: data.contact.email,
1127
+ address1: data.address.address1,
1128
+ address2: data.address.address2,
1129
+ city: data.address.city,
1130
+ state: data.address.state,
1131
+ zip: data.address.zip,
1132
+ country: data.address.country,
1133
+ },
1134
+ retained: false,
1135
+ }),
1136
+ })
1137
+ .then(unpackSpreedlyResponse)
1138
+ .then(extractSpreedlyToken);
1139
+ }
1140
+ }