@idonatedev/idonate-sdk 1.0.16 → 1.1.0-dev1

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 (50) hide show
  1. package/README.md +27 -0
  2. package/dist/apple-pay.d.ts +0 -1
  3. package/dist/apple-pay.js +32 -62
  4. package/dist/cloudflare-challenge-handler.d.ts +2 -2
  5. package/dist/cloudflare-challenge-handler.js +65 -92
  6. package/dist/config-handler.d.ts +3 -0
  7. package/dist/config-handler.js +16 -11
  8. package/dist/constants.d.ts +1 -0
  9. package/dist/constants.js +66 -2
  10. package/dist/google-pay.d.ts +0 -1
  11. package/dist/google-pay.js +65 -78
  12. package/dist/idonate-client.d.ts +6 -19
  13. package/dist/idonate-client.js +165 -192
  14. package/dist/index.js +40 -11
  15. package/dist/recaptcha.d.ts +0 -1
  16. package/dist/recaptcha.js +40 -49
  17. package/dist/shared.js +8 -9
  18. package/dist/test-utils.d.ts +81 -0
  19. package/dist/test-utils.js +110 -0
  20. package/dist/tokenize/CardConnectTokenizer.d.ts +43 -0
  21. package/dist/tokenize/CardConnectTokenizer.js +620 -0
  22. package/dist/tokenize/SpreedlyTokenizer.d.ts +85 -0
  23. package/dist/tokenize/SpreedlyTokenizer.js +952 -0
  24. package/dist/tokenize/Tokenizer.d.ts +32 -0
  25. package/dist/tokenize/Tokenizer.js +183 -0
  26. package/dist/tokenize/iats.js +11 -13
  27. package/dist/tokenize/index.d.ts +5 -3
  28. package/dist/tokenize/index.js +43 -6
  29. package/dist/tokenize/spreedly-secure.d.ts +8 -0
  30. package/dist/tokenize/spreedly-secure.js +43 -0
  31. package/dist/tokenize/styles.d.ts +3 -0
  32. package/dist/tokenize/styles.js +42 -0
  33. package/dist/tokenize/tokenizer-constants.d.ts +62 -0
  34. package/dist/tokenize/tokenizer-constants.js +65 -0
  35. package/dist/tokenize/tokenizer-utils.d.ts +19 -0
  36. package/dist/tokenize/tokenizer-utils.js +156 -0
  37. package/dist/tokenize/types.d.ts +149 -0
  38. package/dist/tokenize/types.js +30 -0
  39. package/dist/typeAdapters.d.ts +3 -2
  40. package/dist/typeAdapters.js +39 -48
  41. package/dist/types.d.ts +48 -41
  42. package/dist/types.js +12 -24
  43. package/dist/util.d.ts +2 -2
  44. package/dist/util.js +30 -33
  45. package/package.json +48 -18
  46. package/umd/idonate-sdk.js +1 -1
  47. package/dist/tokenize/cardconnect.d.ts +0 -3
  48. package/dist/tokenize/cardconnect.js +0 -16
  49. package/dist/tokenize/spreedly.d.ts +0 -48
  50. package/dist/tokenize/spreedly.js +0 -208
@@ -0,0 +1,952 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SpreedlyTokenizer = void 0;
16
+ const Tokenizer_1 = require("./Tokenizer");
17
+ const types_1 = require("./types");
18
+ const constants_1 = require("../constants");
19
+ const typeAdapters_1 = require("../typeAdapters");
20
+ const config_handler_1 = __importDefault(require("../config-handler"));
21
+ const spreedly_secure_1 = require("./spreedly-secure");
22
+ const styles_1 = require("./styles");
23
+ const tokenizer_constants_1 = require("./tokenizer-constants");
24
+ const tokenizer_utils_1 = require("./tokenizer-utils");
25
+ const SPREEDLY_SCRIPT_URL = 'https://core.spreedly.com/iframe/iframe-v1.min.js';
26
+ const SPREEDLY_SCRIPT_ID = 'spreedly-iframe-script';
27
+ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
28
+ constructor(environmentKey, containerId, styles, mode = 'credit_card', bankCountry = 'US', enableTestMode = false) {
29
+ super();
30
+ this.environmentKey = environmentKey;
31
+ this.containerId = containerId;
32
+ this.isReady = false;
33
+ this.bankCountry = 'US';
34
+ this.enableTestMode = false;
35
+ this.mode = mode;
36
+ this.bankCountry = bankCountry;
37
+ this.enableTestMode = enableTestMode;
38
+ if (mode === 'credit_card') {
39
+ const SpreedlyFrame = window.SpreedlyPaymentFrame;
40
+ if (SpreedlyFrame) {
41
+ this.spreedly = new SpreedlyFrame();
42
+ }
43
+ else {
44
+ this.spreedly = window.Spreedly;
45
+ }
46
+ }
47
+ else {
48
+ this.spreedly = window.Spreedly;
49
+ }
50
+ if (mode === 'bank_account') {
51
+ this.currentValidationState = {
52
+ isValid: false,
53
+ routingNumber: { isValid: false, isEmpty: true },
54
+ accountNumber: { isValid: false, isEmpty: true },
55
+ };
56
+ }
57
+ else {
58
+ this.currentValidationState = {
59
+ isValid: false,
60
+ cardNumber: { isValid: false, isEmpty: true },
61
+ cvv: { isValid: false, isEmpty: true },
62
+ };
63
+ }
64
+ this.mergedStyles = (0, styles_1.mergeStyles)(styles_1.DEFAULT_UNIFIED_STYLES, styles);
65
+ const fieldIds = this.generateFieldIds(containerId);
66
+ this.numberEl = fieldIds.numberId;
67
+ this.cvvEl = fieldIds.cvvId;
68
+ this.expiryId = fieldIds.expiryId;
69
+ }
70
+ static create(gateway, container, config) {
71
+ return __awaiter(this, void 0, void 0, function* () {
72
+ var _a;
73
+ if (container.mode !== 'bank_account') {
74
+ yield SpreedlyTokenizer.ensureSpreedlyLoaded();
75
+ }
76
+ const environmentKey = (_a = gateway.config) === null || _a === void 0 ? void 0 : _a.environment_key;
77
+ if (!environmentKey) {
78
+ throw new Error('Spreedly environment key not configured');
79
+ }
80
+ const tokenizer = new SpreedlyTokenizer(environmentKey, container.containerId, container.styling, container.mode || 'credit_card', container.bankCountry || 'US', container.enableTestMode || false);
81
+ tokenizer.organizationId = config === null || config === void 0 ? void 0 : config.organizationId;
82
+ tokenizer.embedId = config === null || config === void 0 ? void 0 : config.embedId;
83
+ tokenizer.createInternalElements();
84
+ let securityArgs;
85
+ if (container.mode === 'credit_card' &&
86
+ (config === null || config === void 0 ? void 0 : config.organizationId) &&
87
+ (config === null || config === void 0 ? void 0 : config.embedId)) {
88
+ try {
89
+ const configHandler = new config_handler_1.default({});
90
+ securityArgs = yield (0, spreedly_secure_1.fetchSpreedlySecurityArgs)(configHandler, config.organizationId, config.embedId);
91
+ }
92
+ catch (error) {
93
+ console.warn('Failed to fetch security args, using standard tokenization');
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
+ }, tokenizer_constants_1.INIT_TIMEOUT);
144
+ this.spreedly.on('ready', () => {
145
+ clearTimeout(timeout);
146
+ this.isReady = true;
147
+ this.spreedly.setPlaceholder('number', tokenizer_constants_1.PLACEHOLDERS.cardNumber);
148
+ this.spreedly.setFieldType('number', 'text');
149
+ this.spreedly.setNumberFormat('prettyFormat');
150
+ this.spreedly.setPlaceholder('cvv', tokenizer_constants_1.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 types_1.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
+ this.spreedly.removeHandlers();
207
+ reject(new types_1.TokenizationError('Tokenization timeout', 'TIMEOUT'));
208
+ }, tokenizer_constants_1.TOKENIZE_TIMEOUT);
209
+ const cleanup = () => {
210
+ clearTimeout(timeout);
211
+ this.spreedly.removeHandlers();
212
+ };
213
+ this.spreedly.on('paymentMethod', (token, pmData) => {
214
+ cleanup();
215
+ resolve({
216
+ token,
217
+ lastFour: pmData.last_four_digits,
218
+ cardType: this.normalizeCardType(pmData.card_type),
219
+ provider: 'spreedly',
220
+ });
221
+ });
222
+ this.spreedly.on('errors', (errors) => {
223
+ cleanup();
224
+ reject(new types_1.TokenizationError(errors));
225
+ });
226
+ let expiryMonth = cardData.expiryMonth || '';
227
+ let expiryYear = cardData.expiryYear || '';
228
+ if (!expiryMonth || !expiryYear) {
229
+ const expiry = this.parseExpiry(this.expiryEl);
230
+ if (expiry) {
231
+ expiryMonth = expiry.month;
232
+ expiryYear = expiry.year;
233
+ }
234
+ else {
235
+ cleanup();
236
+ reject(new types_1.TokenizationError('Expiration date is required', 'VALIDATION_ERROR'));
237
+ return;
238
+ }
239
+ }
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
+ this.institutionNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-institution`, 'text', 'Inst *', 3);
259
+ Object.assign(this.institutionNumberEl.style, {
260
+ flex: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.institutionNumber.flex,
261
+ minWidth: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.institutionNumber.minWidth,
262
+ });
263
+ this.applyInputStyles(this.institutionNumberEl, this.mergedStyles, 'left');
264
+ this.institutionNumberEl.addEventListener('blur', () => {
265
+ const isValid = (0, tokenizer_utils_1.validateInstitutionNumber)(this.institutionNumberEl.value);
266
+ if (!isValid && this.institutionNumberEl.value) {
267
+ this.applyErrorStyles(this.institutionNumberEl);
268
+ }
269
+ else {
270
+ this.applyInputStyles(this.institutionNumberEl, this.mergedStyles, 'left');
271
+ }
272
+ this.updateBankAccountValidation();
273
+ });
274
+ container.appendChild(this.institutionNumberEl);
275
+ this.transitNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-transit`, 'text', 'Transit *', 5);
276
+ Object.assign(this.transitNumberEl.style, {
277
+ flex: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.transitNumber.flex,
278
+ minWidth: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.transitNumber.minWidth,
279
+ });
280
+ this.applyInputStyles(this.transitNumberEl, this.mergedStyles, 'middle');
281
+ this.transitNumberEl.addEventListener('blur', () => {
282
+ const isValid = (0, tokenizer_utils_1.validateTransitNumber)(this.transitNumberEl.value);
283
+ if (!isValid && this.transitNumberEl.value) {
284
+ this.applyErrorStyles(this.transitNumberEl);
285
+ }
286
+ else {
287
+ this.applyInputStyles(this.transitNumberEl, this.mergedStyles, 'middle');
288
+ }
289
+ this.updateBankAccountValidation();
290
+ });
291
+ container.appendChild(this.transitNumberEl);
292
+ this.accountNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-account`, 'text', 'Account Number *');
293
+ Object.assign(this.accountNumberEl.style, {
294
+ flex: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.accountNumber.flex,
295
+ minWidth: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.accountNumber.minWidth,
296
+ });
297
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles, 'middle');
298
+ this.accountNumberEl.addEventListener('blur', () => {
299
+ const isValid = (0, tokenizer_utils_1.validateCanadianAccountNumber)(this.accountNumberEl.value);
300
+ if (!isValid && this.accountNumberEl.value) {
301
+ this.applyErrorStyles(this.accountNumberEl);
302
+ }
303
+ else {
304
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles, 'middle');
305
+ }
306
+ this.updateBankAccountValidation();
307
+ });
308
+ container.appendChild(this.accountNumberEl);
309
+ this.accountTypeEl = (0, tokenizer_utils_1.createAccountTypeSelect)(`${this.containerId}-account-type`);
310
+ Object.assign(this.accountTypeEl.style, {
311
+ flex: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.accountType.flex,
312
+ minWidth: tokenizer_constants_1.CANADIAN_BANK_FIELD_FLEX.accountType.minWidth,
313
+ });
314
+ this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'right');
315
+ container.appendChild(this.accountTypeEl);
316
+ }
317
+ tokenizeBankAccountInternal(bankData) {
318
+ return __awaiter(this, void 0, void 0, function* () {
319
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
320
+ if (!this.isReady) {
321
+ throw new Error('Tokenizer not initialized');
322
+ }
323
+ if (this.mode !== 'bank_account') {
324
+ throw new Error('Tokenizer not in bank account mode');
325
+ }
326
+ let routingNumber;
327
+ let accountNumber;
328
+ const accountType = ((_a = this.accountTypeEl) === null || _a === void 0 ? void 0 : _a.value) || bankData.accountType;
329
+ const accountHolderType = 'personal';
330
+ if (this.bankCountry === 'CA') {
331
+ const institutionNumber = ((_b = this.institutionNumberEl) === null || _b === void 0 ? void 0 : _b.value) || bankData.institutionNumber;
332
+ const transitNumber = ((_c = this.transitNumberEl) === null || _c === void 0 ? void 0 : _c.value) || bankData.transitNumber;
333
+ accountNumber = ((_d = this.accountNumberEl) === null || _d === void 0 ? void 0 : _d.value) || bankData.accountNumber;
334
+ if (!institutionNumber || !transitNumber || !accountNumber) {
335
+ throw new types_1.TokenizationError('Institution number, transit number, and account number are required', 'VALIDATION_ERROR');
336
+ }
337
+ if (!(0, tokenizer_utils_1.validateInstitutionNumber)(institutionNumber)) {
338
+ throw new types_1.TokenizationError('Institution number must be 3 digits', 'VALIDATION_ERROR');
339
+ }
340
+ if (!(0, tokenizer_utils_1.validateTransitNumber)(transitNumber)) {
341
+ throw new types_1.TokenizationError('Transit number must be 5 digits', 'VALIDATION_ERROR');
342
+ }
343
+ if (!(0, tokenizer_utils_1.validateCanadianAccountNumber)(accountNumber)) {
344
+ throw new types_1.TokenizationError('Invalid account number', 'VALIDATION_ERROR');
345
+ }
346
+ routingNumber = (0, tokenizer_utils_1.formatCanadianRoutingNumber)(institutionNumber, transitNumber);
347
+ }
348
+ else {
349
+ routingNumber = ((_e = this.routingNumberEl) === null || _e === void 0 ? void 0 : _e.value) || bankData.routingNumber;
350
+ accountNumber = ((_f = this.accountNumberEl) === null || _f === void 0 ? void 0 : _f.value) || bankData.accountNumber;
351
+ if (!routingNumber || !accountNumber) {
352
+ throw new types_1.TokenizationError('Routing number and account number are required', 'VALIDATION_ERROR');
353
+ }
354
+ if (!(0, tokenizer_utils_1.validateRoutingNumber)(routingNumber)) {
355
+ throw new types_1.TokenizationError('Invalid routing number', 'VALIDATION_ERROR');
356
+ }
357
+ if (!(0, tokenizer_utils_1.validateAccountNumber)(accountNumber)) {
358
+ throw new types_1.TokenizationError('Invalid account number', 'VALIDATION_ERROR');
359
+ }
360
+ }
361
+ const result = yield SpreedlyTokenizer.tokenizeBankAccount({
362
+ contact: {
363
+ firstName: bankData.firstName,
364
+ lastName: bankData.lastName,
365
+ email: bankData.email,
366
+ primaryPhone: bankData.phone,
367
+ },
368
+ address: {
369
+ address1: (_g = bankData.address) === null || _g === void 0 ? void 0 : _g.line1,
370
+ address2: (_h = bankData.address) === null || _h === void 0 ? void 0 : _h.line2,
371
+ city: (_j = bankData.address) === null || _j === void 0 ? void 0 : _j.city,
372
+ state: (_k = bankData.address) === null || _k === void 0 ? void 0 : _k.state,
373
+ zip: (_l = bankData.address) === null || _l === void 0 ? void 0 : _l.postalCode,
374
+ country: (_m = bankData.address) === null || _m === void 0 ? void 0 : _m.country,
375
+ },
376
+ account: {
377
+ accountNumber,
378
+ routingNumber,
379
+ accountType: accountType,
380
+ accountHolderType: accountHolderType,
381
+ },
382
+ }, new config_handler_1.default({ spreedlyEnvironmentKey: this.environmentKey }));
383
+ return {
384
+ token: result,
385
+ lastFour: accountNumber.slice(-4),
386
+ accountType: accountType,
387
+ paymentMethodType: 'bank_account',
388
+ provider: 'spreedly',
389
+ };
390
+ });
391
+ }
392
+ validate() {
393
+ return __awaiter(this, void 0, void 0, function* () {
394
+ if (this.mode === 'bank_account') {
395
+ return this.validateBankAccountInternal();
396
+ }
397
+ else {
398
+ return this.validateCardInternal();
399
+ }
400
+ });
401
+ }
402
+ validateCardInternal() {
403
+ return __awaiter(this, void 0, void 0, function* () {
404
+ return new Promise((resolve) => {
405
+ this.spreedly.on('validation', (isValid, validationData) => {
406
+ this.spreedly.removeHandlers();
407
+ const errors = [];
408
+ if (!validationData.validNumber) {
409
+ errors.push({
410
+ field: 'cardNumber',
411
+ message: 'Invalid card number',
412
+ });
413
+ }
414
+ if (!validationData.validCvv) {
415
+ errors.push({
416
+ field: 'cvv',
417
+ message: 'Invalid CVV',
418
+ });
419
+ }
420
+ resolve({
421
+ isValid: isValid && errors.length === 0,
422
+ errors,
423
+ });
424
+ });
425
+ this.spreedly.validate();
426
+ });
427
+ });
428
+ }
429
+ validateBankAccountInternal() {
430
+ return __awaiter(this, void 0, void 0, function* () {
431
+ var _a, _b, _c, _d, _e;
432
+ const errors = [];
433
+ if (this.bankCountry === 'CA') {
434
+ if (!((_a = this.institutionNumberEl) === null || _a === void 0 ? void 0 : _a.value)) {
435
+ errors.push({
436
+ field: 'routingNumber',
437
+ message: 'Institution number is required',
438
+ });
439
+ }
440
+ else if (!(0, tokenizer_utils_1.validateInstitutionNumber)(this.institutionNumberEl.value)) {
441
+ errors.push({
442
+ field: 'routingNumber',
443
+ message: 'Institution number must be 3 digits',
444
+ });
445
+ }
446
+ if (!((_b = this.transitNumberEl) === null || _b === void 0 ? void 0 : _b.value)) {
447
+ errors.push({
448
+ field: 'routingNumber',
449
+ message: 'Transit number is required',
450
+ });
451
+ }
452
+ else if (!(0, tokenizer_utils_1.validateTransitNumber)(this.transitNumberEl.value)) {
453
+ errors.push({
454
+ field: 'routingNumber',
455
+ message: 'Transit number must be 5 digits',
456
+ });
457
+ }
458
+ if (!((_c = this.accountNumberEl) === null || _c === void 0 ? void 0 : _c.value)) {
459
+ errors.push({
460
+ field: 'accountNumber',
461
+ message: 'Account number is required',
462
+ });
463
+ }
464
+ else if (!(0, tokenizer_utils_1.validateCanadianAccountNumber)(this.accountNumberEl.value)) {
465
+ errors.push({
466
+ field: 'accountNumber',
467
+ message: 'Account number must be at least 7 digits',
468
+ });
469
+ }
470
+ }
471
+ else {
472
+ if (!((_d = this.routingNumberEl) === null || _d === void 0 ? void 0 : _d.value)) {
473
+ errors.push({
474
+ field: 'routingNumber',
475
+ message: 'Routing number is required',
476
+ });
477
+ }
478
+ else if (!(0, tokenizer_utils_1.validateRoutingNumber)(this.routingNumberEl.value)) {
479
+ errors.push({
480
+ field: 'routingNumber',
481
+ message: 'Invalid routing number',
482
+ });
483
+ }
484
+ if (!((_e = this.accountNumberEl) === null || _e === void 0 ? void 0 : _e.value)) {
485
+ errors.push({
486
+ field: 'accountNumber',
487
+ message: 'Account number is required',
488
+ });
489
+ }
490
+ else if (!(0, tokenizer_utils_1.validateAccountNumber)(this.accountNumberEl.value)) {
491
+ errors.push({
492
+ field: 'accountNumber',
493
+ message: 'Invalid account number',
494
+ });
495
+ }
496
+ }
497
+ return {
498
+ isValid: errors.length === 0,
499
+ errors,
500
+ };
501
+ });
502
+ }
503
+ applyUnifiedStyles() {
504
+ var _a;
505
+ if (!this.isReady)
506
+ return;
507
+ const isConnected = ((_a = this.mergedStyles.container) === null || _a === void 0 ? void 0 : _a.gap) === '0';
508
+ if (this.mergedStyles.input) {
509
+ if (isConnected) {
510
+ const numberStyles = Object.assign(Object.assign({}, this.mergedStyles.input), { borderRight: 'none', borderRadius: this.mergedStyles.input.borderRadius
511
+ ? `${this.mergedStyles.input.borderRadius} 0 0 ${this.mergedStyles.input.borderRadius}`
512
+ : '0' });
513
+ const numberCss = this.stylesToCssString(numberStyles);
514
+ this.spreedly.setStyle('number', numberCss);
515
+ const cvvStyles = Object.assign(Object.assign({}, this.mergedStyles.input), { borderRadius: this.mergedStyles.input.borderRadius
516
+ ? `0 ${this.mergedStyles.input.borderRadius} ${this.mergedStyles.input.borderRadius} 0`
517
+ : '0' });
518
+ const cvvCss = this.stylesToCssString(cvvStyles);
519
+ this.spreedly.setStyle('cvv', cvvCss);
520
+ }
521
+ else {
522
+ const baseCss = this.stylesToCssString(this.mergedStyles.input);
523
+ this.spreedly.setStyle('number', baseCss);
524
+ this.spreedly.setStyle('cvv', baseCss);
525
+ }
526
+ }
527
+ if (this.mergedStyles.focus) {
528
+ const focusCss = this.stylesToCssString(this.mergedStyles.focus);
529
+ this.spreedly.setStyle('number:focus', focusCss);
530
+ this.spreedly.setStyle('cvv:focus', focusCss);
531
+ }
532
+ if (this.mergedStyles.error) {
533
+ const errorCss = this.stylesToCssString(this.mergedStyles.error);
534
+ this.spreedly.setStyle('number.invalid', errorCss);
535
+ this.spreedly.setStyle('cvv.invalid', errorCss);
536
+ }
537
+ }
538
+ stylesToCssString(styles) {
539
+ return Object.entries(styles)
540
+ .filter(([_, value]) => value !== undefined && value !== null)
541
+ .map(([key, value]) => {
542
+ const cssKey = key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
543
+ return `${cssKey}: ${value}`;
544
+ })
545
+ .join('; ');
546
+ }
547
+ clear() {
548
+ if (this.mode === 'bank_account') {
549
+ if (this.bankCountry === 'CA') {
550
+ if (this.institutionNumberEl)
551
+ this.institutionNumberEl.value = '';
552
+ if (this.transitNumberEl)
553
+ this.transitNumberEl.value = '';
554
+ }
555
+ else {
556
+ if (this.routingNumberEl)
557
+ this.routingNumberEl.value = '';
558
+ }
559
+ if (this.accountNumberEl)
560
+ this.accountNumberEl.value = '';
561
+ if (this.accountTypeEl)
562
+ this.accountTypeEl.selectedIndex = 0;
563
+ }
564
+ else {
565
+ this.spreedly.reload();
566
+ if (this.expiryEl) {
567
+ this.expiryEl.value = '';
568
+ }
569
+ }
570
+ }
571
+ focus(field) {
572
+ if (this.mode === 'bank_account') {
573
+ if (field === 'routingNumber') {
574
+ if (this.bankCountry === 'CA' && this.institutionNumberEl) {
575
+ this.institutionNumberEl.focus();
576
+ }
577
+ else if (this.routingNumberEl) {
578
+ this.routingNumberEl.focus();
579
+ }
580
+ return;
581
+ }
582
+ if (field === 'accountNumber' && this.accountNumberEl) {
583
+ this.accountNumberEl.focus();
584
+ return;
585
+ }
586
+ return;
587
+ }
588
+ if (field === 'expiry' && this.expiryEl) {
589
+ this.expiryEl.focus();
590
+ return;
591
+ }
592
+ const fieldMap = {
593
+ cardNumber: this.numberEl,
594
+ cvv: this.cvvEl,
595
+ expiry: null,
596
+ routingNumber: null,
597
+ accountNumber: null,
598
+ };
599
+ const elementId = fieldMap[field];
600
+ if (elementId) {
601
+ const element = document.getElementById(elementId);
602
+ element === null || element === void 0 ? void 0 : element.focus();
603
+ }
604
+ }
605
+ destroy() {
606
+ this.spreedly.removeHandlers();
607
+ this.eventHandlers.clear();
608
+ if (this.expiryEl) {
609
+ }
610
+ }
611
+ handleFieldEvent(fieldName, eventType, inputProperties) {
612
+ var _a, _b, _c, _d;
613
+ if (fieldName === 'number') {
614
+ this.currentValidationState.cardNumber = {
615
+ isValid: inputProperties.validNumber || false,
616
+ isEmpty: inputProperties.numberLength === 0,
617
+ };
618
+ if (inputProperties.cardType && inputProperties.cardType !== 'unknown') {
619
+ this.emit('cardTypeChange', {
620
+ cardType: this.normalizeCardType(inputProperties.cardType),
621
+ });
622
+ }
623
+ }
624
+ else if (fieldName === 'cvv') {
625
+ this.currentValidationState.cvv = {
626
+ isValid: inputProperties.validCvv || false,
627
+ isEmpty: inputProperties.cvvLength === 0,
628
+ };
629
+ }
630
+ this.currentValidationState.isValid =
631
+ ((_b = (_a = this.currentValidationState.cardNumber) === null || _a === void 0 ? void 0 : _a.isValid) !== null && _b !== void 0 ? _b : false) &&
632
+ ((_d = (_c = this.currentValidationState.cvv) === null || _c === void 0 ? void 0 : _c.isValid) !== null && _d !== void 0 ? _d : false);
633
+ if (eventType === 'focus') {
634
+ this.emit('focus', { field: fieldName });
635
+ }
636
+ else if (eventType === 'blur') {
637
+ this.emit('blur', { field: fieldName });
638
+ }
639
+ else if (eventType === 'input') {
640
+ this.emit('change', { field: fieldName });
641
+ this.emit('validation', this.currentValidationState);
642
+ }
643
+ }
644
+ createInternalElements() {
645
+ const container = document.getElementById(this.containerId);
646
+ if (!container) {
647
+ throw new Error(`Container element not found: ${this.containerId}`);
648
+ }
649
+ container.innerHTML = '';
650
+ Object.assign(container.style, this.mergedStyles.container);
651
+ if (this.mode === 'bank_account') {
652
+ this.createBankAccountFields(container);
653
+ }
654
+ else {
655
+ this.createCreditCardFields(container);
656
+ }
657
+ }
658
+ createCreditCardFields(container) {
659
+ const cardNumberDiv = (0, tokenizer_utils_1.createFieldContainer)(this.numberEl, tokenizer_constants_1.FIELD_FLEX.cardNumber.flex, tokenizer_constants_1.FIELD_FLEX.cardNumber.minWidth);
660
+ cardNumberDiv.style.height = this.mergedStyles.input.height;
661
+ container.appendChild(cardNumberDiv);
662
+ this.expiryEl = (0, tokenizer_utils_1.createInputElement)(this.expiryId, 'text', tokenizer_constants_1.PLACEHOLDERS.expiry, tokenizer_constants_1.FIELD_CONSTRAINTS.expiry.maxLength);
663
+ Object.assign(this.expiryEl.style, {
664
+ flex: tokenizer_constants_1.FIELD_FLEX.expiry.flex,
665
+ minWidth: tokenizer_constants_1.FIELD_FLEX.expiry.minWidth,
666
+ });
667
+ this.applyInputStyles(this.expiryEl, this.mergedStyles, 'middle');
668
+ this.addExpiryFormatter(this.expiryEl);
669
+ container.appendChild(this.expiryEl);
670
+ const cvvDiv = (0, tokenizer_utils_1.createFieldContainer)(this.cvvEl, tokenizer_constants_1.FIELD_FLEX.cvv.flex, tokenizer_constants_1.FIELD_FLEX.cvv.minWidth);
671
+ cvvDiv.style.height = this.mergedStyles.input.height;
672
+ container.appendChild(cvvDiv);
673
+ }
674
+ createBankAccountFields(container) {
675
+ if (this.bankCountry === 'CA') {
676
+ this.createCanadianBankAccountFields(container);
677
+ }
678
+ else {
679
+ this.createUSBankAccountFields(container);
680
+ }
681
+ }
682
+ createUSBankAccountFields(container) {
683
+ this.routingNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-routing`, 'text', 'Routing *', 9);
684
+ Object.assign(this.routingNumberEl.style, {
685
+ flex: tokenizer_constants_1.BANK_FIELD_FLEX.routingNumber.flex,
686
+ minWidth: tokenizer_constants_1.BANK_FIELD_FLEX.routingNumber.minWidth,
687
+ });
688
+ this.applyInputStyles(this.routingNumberEl, this.mergedStyles, 'left');
689
+ this.routingNumberEl.addEventListener('blur', () => {
690
+ const isValid = (0, tokenizer_utils_1.validateRoutingNumber)(this.routingNumberEl.value);
691
+ if (!isValid && this.routingNumberEl.value) {
692
+ this.applyErrorStyles(this.routingNumberEl);
693
+ }
694
+ else {
695
+ this.applyInputStyles(this.routingNumberEl, this.mergedStyles, 'left');
696
+ }
697
+ this.updateBankAccountValidation();
698
+ });
699
+ container.appendChild(this.routingNumberEl);
700
+ this.accountNumberEl = (0, tokenizer_utils_1.createInputElement)(`${this.containerId}-account`, 'text', 'Account Number *', 17);
701
+ Object.assign(this.accountNumberEl.style, {
702
+ flex: tokenizer_constants_1.BANK_FIELD_FLEX.accountNumber.flex,
703
+ minWidth: tokenizer_constants_1.BANK_FIELD_FLEX.accountNumber.minWidth,
704
+ });
705
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles, 'middle');
706
+ this.accountNumberEl.addEventListener('blur', () => {
707
+ const isValid = (0, tokenizer_utils_1.validateAccountNumber)(this.accountNumberEl.value);
708
+ if (!isValid && this.accountNumberEl.value) {
709
+ this.applyErrorStyles(this.accountNumberEl);
710
+ }
711
+ else {
712
+ this.applyInputStyles(this.accountNumberEl, this.mergedStyles, 'middle');
713
+ }
714
+ this.updateBankAccountValidation();
715
+ });
716
+ container.appendChild(this.accountNumberEl);
717
+ this.accountTypeEl = (0, tokenizer_utils_1.createAccountTypeSelect)(`${this.containerId}-account-type`);
718
+ Object.assign(this.accountTypeEl.style, {
719
+ flex: tokenizer_constants_1.BANK_FIELD_FLEX.accountType.flex,
720
+ minWidth: tokenizer_constants_1.BANK_FIELD_FLEX.accountType.minWidth,
721
+ });
722
+ this.applyInputStyles(this.accountTypeEl, this.mergedStyles, 'right');
723
+ container.appendChild(this.accountTypeEl);
724
+ }
725
+ updateBankAccountValidation() {
726
+ if (this.bankCountry === 'CA') {
727
+ if (!this.institutionNumberEl ||
728
+ !this.transitNumberEl ||
729
+ !this.accountNumberEl)
730
+ return;
731
+ const institutionValid = (0, tokenizer_utils_1.validateInstitutionNumber)(this.institutionNumberEl.value);
732
+ const transitValid = (0, tokenizer_utils_1.validateTransitNumber)(this.transitNumberEl.value);
733
+ const accountValid = (0, tokenizer_utils_1.validateCanadianAccountNumber)(this.accountNumberEl.value);
734
+ this.currentValidationState = {
735
+ isValid: institutionValid && transitValid && accountValid,
736
+ routingNumber: {
737
+ isValid: institutionValid && transitValid,
738
+ isEmpty: !this.institutionNumberEl.value && !this.transitNumberEl.value,
739
+ error: !institutionValid
740
+ ? 'Invalid institution number'
741
+ : !transitValid
742
+ ? 'Invalid transit number'
743
+ : undefined,
744
+ },
745
+ accountNumber: {
746
+ isValid: accountValid,
747
+ isEmpty: !this.accountNumberEl.value,
748
+ error: accountValid
749
+ ? undefined
750
+ : 'Account number must be at least 7 digits',
751
+ },
752
+ };
753
+ }
754
+ else {
755
+ if (!this.routingNumberEl || !this.accountNumberEl)
756
+ return;
757
+ const routingValid = (0, tokenizer_utils_1.validateRoutingNumber)(this.routingNumberEl.value);
758
+ const accountValid = (0, tokenizer_utils_1.validateAccountNumber)(this.accountNumberEl.value);
759
+ this.currentValidationState = {
760
+ isValid: routingValid && accountValid,
761
+ routingNumber: {
762
+ isValid: routingValid,
763
+ isEmpty: !this.routingNumberEl.value,
764
+ error: routingValid ? undefined : 'Invalid routing number',
765
+ },
766
+ accountNumber: {
767
+ isValid: accountValid,
768
+ isEmpty: !this.accountNumberEl.value,
769
+ error: accountValid ? undefined : 'Invalid account number',
770
+ },
771
+ };
772
+ }
773
+ this.emit('validation', this.currentValidationState);
774
+ }
775
+ validateExpiry() {
776
+ if (!this.expiryEl)
777
+ return;
778
+ const expiry = this.parseExpiry(this.expiryEl);
779
+ if (!expiry) {
780
+ this.applyErrorStyles(this.expiryEl);
781
+ return;
782
+ }
783
+ const month = parseInt(expiry.month, 10);
784
+ const year = parseInt(expiry.year, 10);
785
+ if (month < 1 || month > 12) {
786
+ this.applyErrorStyles(this.expiryEl);
787
+ return;
788
+ }
789
+ const now = new Date();
790
+ const expiryDate = new Date(year, month - 1);
791
+ if (expiryDate < now) {
792
+ this.applyErrorStyles(this.expiryEl);
793
+ return;
794
+ }
795
+ this.applyInputStyles(this.expiryEl, this.mergedStyles, 'middle');
796
+ }
797
+ applyErrorStyles(element) {
798
+ if (this.mergedStyles.error) {
799
+ Object.assign(element.style, this.mergedStyles.error);
800
+ }
801
+ else {
802
+ element.style.borderColor = '#dc3545';
803
+ }
804
+ }
805
+ static ensureSpreedlyLoaded() {
806
+ return __awaiter(this, void 0, void 0, function* () {
807
+ if (window.Spreedly) {
808
+ return;
809
+ }
810
+ yield SpreedlyTokenizer.loadScript(SPREEDLY_SCRIPT_URL, SPREEDLY_SCRIPT_ID);
811
+ yield SpreedlyTokenizer.waitForGlobal('Spreedly', 5000);
812
+ });
813
+ }
814
+ static loadScript(src, id) {
815
+ return __awaiter(this, void 0, void 0, function* () {
816
+ if (id && document.getElementById(id)) {
817
+ return;
818
+ }
819
+ const existingScript = Array.from(document.getElementsByTagName('script')).find((script) => script.src === src);
820
+ if (existingScript) {
821
+ return;
822
+ }
823
+ return new Promise((resolve, reject) => {
824
+ const script = document.createElement('script');
825
+ script.src = src;
826
+ script.async = true;
827
+ if (id) {
828
+ script.id = id;
829
+ }
830
+ script.onload = () => resolve();
831
+ script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
832
+ document.body.appendChild(script);
833
+ });
834
+ });
835
+ }
836
+ static waitForGlobal(globalName_1) {
837
+ return __awaiter(this, arguments, void 0, function* (globalName, timeout = 10000) {
838
+ const startTime = Date.now();
839
+ while (Date.now() - startTime < timeout) {
840
+ const global = window[globalName];
841
+ if (global) {
842
+ return global;
843
+ }
844
+ yield new Promise((resolve) => setTimeout(resolve, 100));
845
+ }
846
+ throw new Error(`Timeout waiting for global: ${globalName}`);
847
+ });
848
+ }
849
+ static tokenizeBankAccount(data, config) {
850
+ if (config.spreedlyEnvironmentKey === undefined) {
851
+ return Promise.reject(new Error('Spreedly is not configured.'));
852
+ }
853
+ return fetch(constants_1.SPREEDLY_TOKENIZER_URL +
854
+ '?' +
855
+ new URLSearchParams({ environment_key: config.spreedlyEnvironmentKey }), {
856
+ method: 'POST',
857
+ headers: {
858
+ 'Content-Type': 'application/json',
859
+ },
860
+ body: JSON.stringify({
861
+ payment_method: {
862
+ bank_account: {
863
+ first_name: data.contact.firstName || undefined,
864
+ last_name: data.contact.lastName || undefined,
865
+ full_name: data.contact.fullName || undefined,
866
+ email: data.contact.email,
867
+ address1: data.address.address1,
868
+ address2: data.address.address2,
869
+ city: data.address.city,
870
+ state: data.address.state,
871
+ zip: data.address.zip,
872
+ country: data.address.country,
873
+ bank_account_number: data.account.accountNumber.replace(/\s+/g, ''),
874
+ bank_routing_number: data.account.routingNumber.replace(/\s+/g, ''),
875
+ bank_account_holder_type: data.account.accountHolderType,
876
+ bank_account_type: data.account.accountType,
877
+ },
878
+ },
879
+ retained: false,
880
+ }),
881
+ })
882
+ .then(typeAdapters_1.unpackSpreedlyResponse)
883
+ .then(typeAdapters_1.extractSpreedlyToken);
884
+ }
885
+ static tokenizeCreditCard(data, config) {
886
+ if (config.spreedlyEnvironmentKey === undefined) {
887
+ return Promise.reject(new Error('Spreedly is not configured.'));
888
+ }
889
+ return fetch(constants_1.SPREEDLY_TOKENIZER_URL +
890
+ '?' +
891
+ new URLSearchParams({ environment_key: config.spreedlyEnvironmentKey }), {
892
+ method: 'POST',
893
+ headers: {
894
+ 'Content-Type': 'application/json',
895
+ },
896
+ body: JSON.stringify({
897
+ payment_method: {
898
+ credit_card: {
899
+ first_name: data.contact.firstName,
900
+ last_name: data.contact.lastName,
901
+ full_name: data.contact.fullName,
902
+ email: data.contact.email,
903
+ address1: data.address.address1,
904
+ address2: data.address.address2,
905
+ city: data.address.city,
906
+ state: data.address.state,
907
+ zip: data.address.zip,
908
+ country: data.address.country,
909
+ number: data.card.cardNumber.replace(/\s+/g, ''),
910
+ verification_value: data.card.verificationValue,
911
+ year: data.card.expirationYear,
912
+ month: data.card.expirationMonth,
913
+ },
914
+ },
915
+ retained: false,
916
+ }),
917
+ })
918
+ .then(typeAdapters_1.unpackSpreedlyResponse)
919
+ .then(typeAdapters_1.extractSpreedlyToken);
920
+ }
921
+ static tokenizePayPal(data, config) {
922
+ if (config.spreedlyEnvironmentKey === undefined) {
923
+ throw new Error('Spreedly is not configured.');
924
+ }
925
+ return fetch(constants_1.SPREEDLY_TOKENIZER_URL +
926
+ '?' +
927
+ new URLSearchParams({ environment_key: config.spreedlyEnvironmentKey }), {
928
+ method: 'POST',
929
+ headers: {
930
+ 'Content-Type': 'application/json',
931
+ },
932
+ body: JSON.stringify({
933
+ payment_method: {
934
+ payment_method_type: 'paypal',
935
+ first_name: data.contact.firstName,
936
+ last_name: data.contact.lastName,
937
+ email: data.contact.email,
938
+ address1: data.address.address1,
939
+ address2: data.address.address2,
940
+ city: data.address.city,
941
+ state: data.address.state,
942
+ zip: data.address.zip,
943
+ country: data.address.country,
944
+ },
945
+ retained: false,
946
+ }),
947
+ })
948
+ .then(typeAdapters_1.unpackSpreedlyResponse)
949
+ .then(typeAdapters_1.extractSpreedlyToken);
950
+ }
951
+ }
952
+ exports.SpreedlyTokenizer = SpreedlyTokenizer;