@idonatedev/idonate-sdk 1.2.0-dev14 → 1.2.0-dev15

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.
@@ -66,7 +66,7 @@ export class SpreedlyTokenizer extends Tokenizer {
66
66
  }
67
67
  static create(gateway, container, config) {
68
68
  return __awaiter(this, void 0, void 0, function* () {
69
- var _a;
69
+ var _a, _b;
70
70
  if (container.mode !== 'bank_account') {
71
71
  yield SpreedlyTokenizer.ensureSpreedlyLoaded();
72
72
  }
@@ -75,9 +75,9 @@ export class SpreedlyTokenizer extends Tokenizer {
75
75
  environmentKey = config.clientConfig.spreedlyEnvironmentKey;
76
76
  }
77
77
  if (!environmentKey) {
78
- environmentKey = '';
78
+ throw new Error('Spreedly environment key is required. Provide it via gateway.config.environmentKey or client config.');
79
79
  }
80
- const tokenizer = new SpreedlyTokenizer(environmentKey, container.containerId, container.styling, container.mode || 'credit_card', container.bankCountry || 'US', container.enableTestMode || false, container.layout || 'single-line', container.responsiveBreakpoint || RESPONSIVE_BREAKPOINT);
80
+ const tokenizer = new SpreedlyTokenizer(environmentKey, container.containerId, container.styling, container.mode || 'credit_card', container.bankCountry || 'US', container.enableTestMode || false, container.layout || 'single-line', (_b = container.responsiveBreakpoint) !== null && _b !== void 0 ? _b : RESPONSIVE_BREAKPOINT);
81
81
  tokenizer.organizationId = config.organizationId;
82
82
  tokenizer.embedId = config.embedId;
83
83
  tokenizer.clientConfig = config.clientConfig;
@@ -158,8 +158,29 @@ export class SpreedlyTokenizer extends Tokenizer {
158
158
  this.emit('ready');
159
159
  resolve();
160
160
  });
161
+ this.spreedly.on('paymentMethod', (token, pmData) => {
162
+ const paymentToken = {
163
+ token,
164
+ lastFour: pmData.last_four_digits,
165
+ cardType: this.normalizeCardType(pmData.card_type),
166
+ provider: 'spreedly',
167
+ };
168
+ if (this.tokenizationResolve) {
169
+ const resolveTokenization = this.tokenizationResolve;
170
+ this.clearTokenizationState();
171
+ this.emit('tokenReady', paymentToken);
172
+ resolveTokenization(paymentToken);
173
+ }
174
+ });
161
175
  this.spreedly.on('errors', (errors) => {
162
- this.emit('error', new TokenizationError(errors));
176
+ if (this.tokenizationReject) {
177
+ const rejectTokenization = this.tokenizationReject;
178
+ this.clearTokenizationState();
179
+ rejectTokenization(new TokenizationError(errors));
180
+ }
181
+ else {
182
+ this.emit('error', new TokenizationError(errors));
183
+ }
163
184
  });
164
185
  this.spreedly.on('fieldEvent', (fieldName, eventType, activeEl, inputProperties) => {
165
186
  this.handleFieldEvent(fieldName, eventType, inputProperties);
@@ -187,6 +208,13 @@ export class SpreedlyTokenizer extends Tokenizer {
187
208
  tokenizeCardInternal(cardData) {
188
209
  return __awaiter(this, void 0, void 0, function* () {
189
210
  var _a;
211
+ if (this.tokenizationPromise) {
212
+ return this.tokenizationPromise;
213
+ }
214
+ const expiry = this.parseExpiry(this.expiryEl);
215
+ if (!expiry) {
216
+ throw new TokenizationError('Expiration date is required', 'VALIDATION_ERROR');
217
+ }
190
218
  let securityFields = {};
191
219
  if ((_a = this.clientConfig) === null || _a === void 0 ? void 0 : _a.enableSpreedlySecureTokenization) {
192
220
  try {
@@ -194,7 +222,7 @@ export class SpreedlyTokenizer extends Tokenizer {
194
222
  securityFields = {
195
223
  nonce: args.nonce,
196
224
  timestamp: args.timestamp,
197
- certificateToken: args.certificate_token,
225
+ certificate_token: args.certificate_token,
198
226
  signature: args.signature,
199
227
  };
200
228
  }
@@ -202,45 +230,19 @@ export class SpreedlyTokenizer extends Tokenizer {
202
230
  throw new TokenizationError(`Secure tokenization failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'SECURE_TOKENIZATION_ERROR');
203
231
  }
204
232
  }
205
- return new Promise((resolve, reject) => {
233
+ this.tokenizationPromise = new Promise((resolve, reject) => {
206
234
  var _a, _b, _c, _d, _e, _f;
207
- const timeout = setTimeout(() => {
208
- if (this.spreedly && this.spreedly.removeHandlers) {
209
- this.spreedly.removeHandlers();
210
- }
235
+ this.tokenizationResolve = resolve;
236
+ this.tokenizationReject = reject;
237
+ this.tokenizationTimeout = setTimeout(() => {
238
+ this.clearTokenizationState();
211
239
  reject(new TokenizationError('Tokenization timeout', 'TIMEOUT'));
212
240
  }, TOKENIZE_TIMEOUT);
213
- const cleanup = () => {
214
- clearTimeout(timeout);
215
- if (this.spreedly && this.spreedly.removeHandlers) {
216
- this.spreedly.removeHandlers();
217
- }
218
- };
219
- this.spreedly.on('paymentMethod', (token, pmData) => {
220
- cleanup();
221
- const paymentToken = {
222
- token,
223
- lastFour: pmData.last_four_digits,
224
- cardType: this.normalizeCardType(pmData.card_type),
225
- provider: 'spreedly',
226
- };
227
- this.emit('tokenReady', paymentToken);
228
- resolve(paymentToken);
229
- });
230
- this.spreedly.on('errors', (errors) => {
231
- cleanup();
232
- reject(new TokenizationError(errors));
233
- });
234
- const expiry = this.parseExpiry(this.expiryEl);
235
- if (!expiry) {
236
- cleanup();
237
- reject(new TokenizationError('Expiration date is required', 'VALIDATION_ERROR'));
238
- return;
239
- }
240
- const expiryMonth = expiry.month;
241
- const expiryYear = expiry.year;
242
- this.spreedly.tokenizeCreditCard(Object.assign({ first_name: cardData.firstName, last_name: cardData.lastName, month: expiryMonth, year: expiryYear, email: cardData.email, phone_number: cardData.phone, address1: (_a = cardData.address) === null || _a === void 0 ? void 0 : _a.address1, address2: (_b = cardData.address) === null || _b === void 0 ? void 0 : _b.address2, city: (_c = cardData.address) === null || _c === void 0 ? void 0 : _c.city, state: (_d = cardData.address) === null || _d === void 0 ? void 0 : _d.state, zip: (_e = cardData.address) === null || _e === void 0 ? void 0 : _e.zip, country: (_f = cardData.address) === null || _f === void 0 ? void 0 : _f.country }, securityFields));
241
+ this.spreedly.tokenizeCreditCard(Object.assign({ first_name: cardData.firstName, last_name: cardData.lastName, month: expiry.month, year: expiry.year, email: cardData.email, phone_number: cardData.phone, address1: (_a = cardData.address) === null || _a === void 0 ? void 0 : _a.address1, address2: (_b = cardData.address) === null || _b === void 0 ? void 0 : _b.address2, city: (_c = cardData.address) === null || _c === void 0 ? void 0 : _c.city, state: (_d = cardData.address) === null || _d === void 0 ? void 0 : _d.state, zip: (_e = cardData.address) === null || _e === void 0 ? void 0 : _e.zip, country: (_f = cardData.address) === null || _f === void 0 ? void 0 : _f.country }, securityFields));
242
+ }).finally(() => {
243
+ this.tokenizationPromise = undefined;
243
244
  });
245
+ return this.tokenizationPromise;
244
246
  });
245
247
  }
246
248
  createCanadianBankAccountFields(container) {
@@ -443,6 +445,9 @@ export class SpreedlyTokenizer extends Tokenizer {
443
445
  }
444
446
  validate() {
445
447
  return __awaiter(this, void 0, void 0, function* () {
448
+ if (!this.isReady) {
449
+ throw new Error('Tokenizer not initialized');
450
+ }
446
451
  if (this.mode === 'bank_account') {
447
452
  return this.validateBankAccountInternal();
448
453
  }
@@ -630,13 +635,26 @@ export class SpreedlyTokenizer extends Tokenizer {
630
635
  this.accountNumberEl.value = '';
631
636
  if (this.accountTypeEl)
632
637
  this.accountTypeEl.selectedIndex = 0;
638
+ this.currentValidationState = {
639
+ isValid: false,
640
+ routingNumber: { isValid: false, isEmpty: true },
641
+ accountNumber: { isValid: false, isEmpty: true },
642
+ };
633
643
  }
634
644
  else {
635
645
  this.spreedly.reload();
636
646
  if (this.expiryEl) {
637
647
  this.expiryEl.value = '';
638
648
  }
649
+ this.currentValidationState = {
650
+ isValid: false,
651
+ cardNumber: { isValid: false, isEmpty: true },
652
+ cvv: { isValid: false, isEmpty: true },
653
+ expiry: { isValid: false, isEmpty: true },
654
+ };
639
655
  }
656
+ this.clearTokenizationState();
657
+ this.emit('validation', this.currentValidationState);
640
658
  }
641
659
  focus(field) {
642
660
  if (this.mode === 'bank_account') {
@@ -727,14 +745,28 @@ export class SpreedlyTokenizer extends Tokenizer {
727
745
  }
728
746
  }
729
747
  }
748
+ clearTokenizationState() {
749
+ if (this.tokenizationTimeout) {
750
+ clearTimeout(this.tokenizationTimeout);
751
+ this.tokenizationTimeout = undefined;
752
+ }
753
+ this.tokenizationResolve = undefined;
754
+ this.tokenizationReject = undefined;
755
+ }
730
756
  destroy() {
731
757
  var _a;
732
758
  (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
759
+ if (this.tokenizationReject) {
760
+ const reject = this.tokenizationReject;
761
+ this.clearTokenizationState();
762
+ reject(new TokenizationError('Tokenizer destroyed', 'DESTROYED'));
763
+ }
733
764
  if (this.spreedly && this.spreedly.removeHandlers) {
734
765
  this.spreedly.removeHandlers();
735
766
  }
736
767
  this.eventHandlers.clear();
737
- if (this.expiryEl) {
768
+ if (this.containerEl) {
769
+ this.containerEl.innerHTML = '';
738
770
  }
739
771
  }
740
772
  hasToken() {
@@ -781,15 +813,6 @@ export class SpreedlyTokenizer extends Tokenizer {
781
813
  }
782
814
  this.updateOverallValidation();
783
815
  }
784
- if (eventType === 'focus') {
785
- this.emit('focus', { field: fieldName });
786
- }
787
- else if (eventType === 'blur') {
788
- this.emit('blur', { field: fieldName });
789
- }
790
- else if (eventType === 'input') {
791
- this.emit('change', { field: fieldName });
792
- }
793
816
  }
794
817
  createInternalElements() {
795
818
  const container = document.getElementById(this.containerId);
@@ -835,6 +858,10 @@ export class SpreedlyTokenizer extends Tokenizer {
835
858
  };
836
859
  this.updateOverallValidation();
837
860
  });
861
+ this.expiryEl.addEventListener('blur', () => {
862
+ this.validateExpiry();
863
+ this.updateOverallValidation();
864
+ });
838
865
  container.appendChild(this.expiryEl);
839
866
  const cvvDiv = createFieldContainer(this.cvvEl, '1', FIELD_FLEX.cvv.minWidth);
840
867
  cvvDiv.style.height = this.mergedStyles.input.height;
@@ -862,6 +889,10 @@ export class SpreedlyTokenizer extends Tokenizer {
862
889
  };
863
890
  this.updateOverallValidation();
864
891
  });
892
+ this.expiryEl.addEventListener('blur', () => {
893
+ this.validateExpiry();
894
+ this.updateOverallValidation();
895
+ });
865
896
  container.appendChild(this.expiryEl);
866
897
  const cvvDiv = createFieldContainer(this.cvvEl, FIELD_FLEX.cvv.flex, FIELD_FLEX.cvv.minWidth);
867
898
  cvvDiv.style.height = this.mergedStyles.input.height;
@@ -1024,23 +1055,31 @@ export class SpreedlyTokenizer extends Tokenizer {
1024
1055
  if (!this.expiryEl)
1025
1056
  return;
1026
1057
  const expiry = this.parseExpiry(this.expiryEl);
1058
+ const isEmpty = !this.expiryEl.value;
1059
+ let isValid = false;
1027
1060
  if (!expiry) {
1028
- this.applyErrorStyles(this.expiryEl);
1029
- return;
1030
- }
1031
- const month = parseInt(expiry.month, 10);
1032
- const year = parseInt(expiry.year, 10);
1033
- if (month < 1 || month > 12) {
1034
- this.applyErrorStyles(this.expiryEl);
1035
- return;
1061
+ if (!isEmpty)
1062
+ this.applyErrorStyles(this.expiryEl);
1036
1063
  }
1037
- const now = new Date();
1038
- const expiryDate = new Date(year, month - 1);
1039
- if (expiryDate < now) {
1040
- this.applyErrorStyles(this.expiryEl);
1041
- return;
1064
+ else {
1065
+ const month = parseInt(expiry.month, 10);
1066
+ const year = parseInt(expiry.year, 10);
1067
+ if (month < 1 || month > 12) {
1068
+ this.applyErrorStyles(this.expiryEl);
1069
+ }
1070
+ else {
1071
+ const now = new Date();
1072
+ const expiryDate = new Date(year, month - 1);
1073
+ if (expiryDate < now) {
1074
+ this.applyErrorStyles(this.expiryEl);
1075
+ }
1076
+ else {
1077
+ isValid = true;
1078
+ this.applyInputStyles(this.expiryEl, this.mergedStyles, 'middle');
1079
+ }
1080
+ }
1042
1081
  }
1043
- this.applyInputStyles(this.expiryEl, this.mergedStyles, 'middle');
1082
+ this.currentValidationState.expiry = { isValid, isEmpty };
1044
1083
  }
1045
1084
  applyErrorStyles(element) {
1046
1085
  if (this.mergedStyles.error) {
@@ -2,7 +2,7 @@ import { CardData, BankAccountData, PaymentData, PaymentToken, ValidationResult,
2
2
  import ConfigHandler from '../config-handler';
3
3
  export declare abstract class Tokenizer {
4
4
  protected mode: PaymentMethodMode;
5
- protected eventHandlers: Map<TokenizerEvent, Set<(data?: any) => void>>;
5
+ protected eventHandlers: Map<string, Set<(data?: any) => void>>;
6
6
  private _isReady;
7
7
  get isReady(): boolean;
8
8
  abstract tokenize(paymentData: PaymentData): Promise<PaymentToken>;
@@ -16,7 +16,7 @@ export declare abstract class Tokenizer {
16
16
  getMode(): PaymentMethodMode;
17
17
  on(event: TokenizerEvent, handler: (data?: any) => void): void;
18
18
  off(event: TokenizerEvent, handler: (data?: any) => void): void;
19
- protected emit(event: TokenizerEvent, data?: any): void;
19
+ protected emit(event: string, data?: any): void;
20
20
  protected isCardData(data: PaymentData): data is CardData;
21
21
  protected isBankAccountData(data: PaymentData): data is BankAccountData;
22
22
  protected normalizeCardType(providerCardType: string): CardType;
@@ -82,7 +82,6 @@ export class Tokenizer {
82
82
  expiryEl.addEventListener('input', (e) => {
83
83
  const target = e.target;
84
84
  target.value = formatExpiryInput(target.value);
85
- this.emit('change', { field: 'expiry' });
86
85
  });
87
86
  expiryEl.addEventListener('keypress', (e) => {
88
87
  const char = String.fromCharCode(e.which);
@@ -10,8 +10,9 @@ var __rest = (this && this.__rest) || function (s, e) {
10
10
  return t;
11
11
  };
12
12
  export function normalizeGateway(input) {
13
- if (!input)
14
- return input;
13
+ if (!input) {
14
+ throw new Error('Gateway is required but received ' + String(input));
15
+ }
15
16
  if (input.backendName !== undefined)
16
17
  return input;
17
18
  const _a = input.config || {}, { environment_key, base_url, merchant_id } = _a, extraConfigKeys = __rest(_a, ["environment_key", "base_url", "merchant_id"]);
@@ -3,6 +3,6 @@ export { Tokenizer } from './Tokenizer';
3
3
  export { SpreedlyTokenizer } from './SpreedlyTokenizer';
4
4
  export { CardConnectTokenizer } from './CardConnectTokenizer';
5
5
  export { PayPalTokenizer, PayPalCreateOrderData } from './PayPalTokenizer';
6
- export { PaymentMethodType, PaymentGateway, TokenizerContainer, TokenizerStyling, CardData, PaymentData, PaymentToken, CardType, TokenizerEvent, ValidationState, ValidationResult, ValidationError, TokenizationError, } from './types';
6
+ export { PaymentMethodType, PaymentMethodMode, PaymentGateway, TokenizerContainer, TokenizerStyling, CardData, BankAccountData, PaymentData, PaymentToken, CardType, TokenizerEvent, ValidationState, ValidationResult, ValidationError, TokenizationError, } from './types';
7
7
  export { normalizeGateway } from './gateway-utils';
8
8
  export { iats };
@@ -30,7 +30,9 @@ export function fetchSpreedlySecurityArgs(config, organizationId, embedId) {
30
30
  return yield makeRequest();
31
31
  }
32
32
  catch (error) {
33
- if (error instanceof TypeError && error.message.includes('fetch')) {
33
+ const isNetworkError = error instanceof TypeError && error.message.includes('fetch');
34
+ const isServerError = error instanceof Error && error.message.includes('failed: 5');
35
+ if (isNetworkError || isServerError) {
34
36
  yield new Promise((resolve) => setTimeout(resolve, 1000));
35
37
  return makeRequest();
36
38
  }
@@ -48,8 +48,8 @@ export function addFocusHandlers(element, styles, onFocus, onBlur) {
48
48
  });
49
49
  element.addEventListener('blur', () => {
50
50
  element.style.border = styles.input.border;
51
- element.style.outline = 'none';
52
- element.style.boxShadow = 'none';
51
+ element.style.outline = '';
52
+ element.style.boxShadow = '';
53
53
  onBlur === null || onBlur === void 0 ? void 0 : onBlur();
54
54
  });
55
55
  }
@@ -1,5 +1,5 @@
1
1
  export type PaymentMethodType = 'credit_card' | 'bank_account' | 'paypal' | 'apple_pay' | 'google_pay';
2
- export type PaymentMethodMode = 'credit_card' | 'bank_account';
2
+ export type PaymentMethodMode = 'credit_card' | 'bank_account' | 'paypal';
3
3
  export interface PaymentGateway {
4
4
  id: string;
5
5
  backendName: string;
@@ -106,9 +106,10 @@ export interface PaymentToken {
106
106
  paymentTransactionId?: string;
107
107
  }
108
108
  export type CardType = 'visa' | 'mastercard' | 'amex' | 'discover' | 'diners' | 'jcb' | 'unknown';
109
- export type TokenizerEvent = 'ready' | 'focus' | 'blur' | 'change' | 'validation' | 'cardTypeChange' | 'error' | 'tokenization' | 'tokenReady';
109
+ export type TokenizerEvent = 'ready' | 'validation' | 'cardTypeChange' | 'error' | 'tokenReady';
110
110
  export interface ValidationState {
111
111
  isValid: boolean;
112
+ hasToken?: boolean;
112
113
  cardNumber?: {
113
114
  isValid: boolean;
114
115
  isEmpty: boolean;
@@ -20,6 +20,8 @@ export declare class CardConnectTokenizer extends Tokenizer {
20
20
  private tokenizationResolve?;
21
21
  private tokenizationReject?;
22
22
  private currentCardType?;
23
+ private tokenizationTimeout?;
24
+ private mergedStyles?;
23
25
  private constructor();
24
26
  static create(gateway: PaymentGateway, container: TokenizerContainer, config: {
25
27
  organizationId: string;
@@ -29,6 +31,8 @@ export declare class CardConnectTokenizer extends Tokenizer {
29
31
  private createInternalElements;
30
32
  private createCreditCardFields;
31
33
  private createBankAccountFields;
34
+ private addBankAccountValidationListeners;
35
+ private updateBankAccountValidation;
32
36
  private init;
33
37
  tokenize(paymentData: PaymentData): Promise<PaymentToken>;
34
38
  private tokenizeCardInternal;
@@ -47,5 +51,5 @@ export declare class CardConnectTokenizer extends Tokenizer {
47
51
  private static generateIframeUrl;
48
52
  private static createIframe;
49
53
  private static generateCardConnectCss;
50
- static tokenizeBankAccount(routingNumber: string, accountNumber: string, config: ConfigHandler): Promise<TokenizeCardConnectBankAccountResult>;
54
+ static tokenizeBankAccount(routingNumber: string, accountNumber: string, configOrBaseUrl: ConfigHandler | string): Promise<TokenizeCardConnectBankAccountResult>;
51
55
  }