@idonatedev/idonate-sdk 1.2.0-dev13 → 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.
@@ -69,7 +69,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
69
69
  }
70
70
  static create(gateway, container, config) {
71
71
  return __awaiter(this, void 0, void 0, function* () {
72
- var _a;
72
+ var _a, _b;
73
73
  if (container.mode !== 'bank_account') {
74
74
  yield SpreedlyTokenizer.ensureSpreedlyLoaded();
75
75
  }
@@ -78,9 +78,9 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
78
78
  environmentKey = config.clientConfig.spreedlyEnvironmentKey;
79
79
  }
80
80
  if (!environmentKey) {
81
- environmentKey = '';
81
+ throw new Error('Spreedly environment key is required. Provide it via gateway.config.environmentKey or client config.');
82
82
  }
83
- 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 || tokenizer_constants_1.RESPONSIVE_BREAKPOINT);
83
+ 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 : tokenizer_constants_1.RESPONSIVE_BREAKPOINT);
84
84
  tokenizer.organizationId = config.organizationId;
85
85
  tokenizer.embedId = config.embedId;
86
86
  tokenizer.clientConfig = config.clientConfig;
@@ -161,8 +161,29 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
161
161
  this.emit('ready');
162
162
  resolve();
163
163
  });
164
+ this.spreedly.on('paymentMethod', (token, pmData) => {
165
+ const paymentToken = {
166
+ token,
167
+ lastFour: pmData.last_four_digits,
168
+ cardType: this.normalizeCardType(pmData.card_type),
169
+ provider: 'spreedly',
170
+ };
171
+ if (this.tokenizationResolve) {
172
+ const resolveTokenization = this.tokenizationResolve;
173
+ this.clearTokenizationState();
174
+ this.emit('tokenReady', paymentToken);
175
+ resolveTokenization(paymentToken);
176
+ }
177
+ });
164
178
  this.spreedly.on('errors', (errors) => {
165
- this.emit('error', new types_1.TokenizationError(errors));
179
+ if (this.tokenizationReject) {
180
+ const rejectTokenization = this.tokenizationReject;
181
+ this.clearTokenizationState();
182
+ rejectTokenization(new types_1.TokenizationError(errors));
183
+ }
184
+ else {
185
+ this.emit('error', new types_1.TokenizationError(errors));
186
+ }
166
187
  });
167
188
  this.spreedly.on('fieldEvent', (fieldName, eventType, activeEl, inputProperties) => {
168
189
  this.handleFieldEvent(fieldName, eventType, inputProperties);
@@ -190,6 +211,13 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
190
211
  tokenizeCardInternal(cardData) {
191
212
  return __awaiter(this, void 0, void 0, function* () {
192
213
  var _a;
214
+ if (this.tokenizationPromise) {
215
+ return this.tokenizationPromise;
216
+ }
217
+ const expiry = this.parseExpiry(this.expiryEl);
218
+ if (!expiry) {
219
+ throw new types_1.TokenizationError('Expiration date is required', 'VALIDATION_ERROR');
220
+ }
193
221
  let securityFields = {};
194
222
  if ((_a = this.clientConfig) === null || _a === void 0 ? void 0 : _a.enableSpreedlySecureTokenization) {
195
223
  try {
@@ -197,7 +225,7 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
197
225
  securityFields = {
198
226
  nonce: args.nonce,
199
227
  timestamp: args.timestamp,
200
- certificateToken: args.certificate_token,
228
+ certificate_token: args.certificate_token,
201
229
  signature: args.signature,
202
230
  };
203
231
  }
@@ -205,45 +233,19 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
205
233
  throw new types_1.TokenizationError(`Secure tokenization failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'SECURE_TOKENIZATION_ERROR');
206
234
  }
207
235
  }
208
- return new Promise((resolve, reject) => {
236
+ this.tokenizationPromise = new Promise((resolve, reject) => {
209
237
  var _a, _b, _c, _d, _e, _f;
210
- const timeout = setTimeout(() => {
211
- if (this.spreedly && this.spreedly.removeHandlers) {
212
- this.spreedly.removeHandlers();
213
- }
238
+ this.tokenizationResolve = resolve;
239
+ this.tokenizationReject = reject;
240
+ this.tokenizationTimeout = setTimeout(() => {
241
+ this.clearTokenizationState();
214
242
  reject(new types_1.TokenizationError('Tokenization timeout', 'TIMEOUT'));
215
243
  }, tokenizer_constants_1.TOKENIZE_TIMEOUT);
216
- const cleanup = () => {
217
- clearTimeout(timeout);
218
- if (this.spreedly && this.spreedly.removeHandlers) {
219
- this.spreedly.removeHandlers();
220
- }
221
- };
222
- this.spreedly.on('paymentMethod', (token, pmData) => {
223
- cleanup();
224
- const paymentToken = {
225
- token,
226
- lastFour: pmData.last_four_digits,
227
- cardType: this.normalizeCardType(pmData.card_type),
228
- provider: 'spreedly',
229
- };
230
- this.emit('tokenReady', paymentToken);
231
- resolve(paymentToken);
232
- });
233
- this.spreedly.on('errors', (errors) => {
234
- cleanup();
235
- reject(new types_1.TokenizationError(errors));
236
- });
237
- const expiry = this.parseExpiry(this.expiryEl);
238
- if (!expiry) {
239
- cleanup();
240
- reject(new types_1.TokenizationError('Expiration date is required', 'VALIDATION_ERROR'));
241
- return;
242
- }
243
- const expiryMonth = expiry.month;
244
- const expiryYear = expiry.year;
245
- 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));
244
+ 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));
245
+ }).finally(() => {
246
+ this.tokenizationPromise = undefined;
246
247
  });
248
+ return this.tokenizationPromise;
247
249
  });
248
250
  }
249
251
  createCanadianBankAccountFields(container) {
@@ -446,6 +448,9 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
446
448
  }
447
449
  validate() {
448
450
  return __awaiter(this, void 0, void 0, function* () {
451
+ if (!this.isReady) {
452
+ throw new Error('Tokenizer not initialized');
453
+ }
449
454
  if (this.mode === 'bank_account') {
450
455
  return this.validateBankAccountInternal();
451
456
  }
@@ -633,13 +638,26 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
633
638
  this.accountNumberEl.value = '';
634
639
  if (this.accountTypeEl)
635
640
  this.accountTypeEl.selectedIndex = 0;
641
+ this.currentValidationState = {
642
+ isValid: false,
643
+ routingNumber: { isValid: false, isEmpty: true },
644
+ accountNumber: { isValid: false, isEmpty: true },
645
+ };
636
646
  }
637
647
  else {
638
648
  this.spreedly.reload();
639
649
  if (this.expiryEl) {
640
650
  this.expiryEl.value = '';
641
651
  }
652
+ this.currentValidationState = {
653
+ isValid: false,
654
+ cardNumber: { isValid: false, isEmpty: true },
655
+ cvv: { isValid: false, isEmpty: true },
656
+ expiry: { isValid: false, isEmpty: true },
657
+ };
642
658
  }
659
+ this.clearTokenizationState();
660
+ this.emit('validation', this.currentValidationState);
643
661
  }
644
662
  focus(field) {
645
663
  if (this.mode === 'bank_account') {
@@ -730,14 +748,28 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
730
748
  }
731
749
  }
732
750
  }
751
+ clearTokenizationState() {
752
+ if (this.tokenizationTimeout) {
753
+ clearTimeout(this.tokenizationTimeout);
754
+ this.tokenizationTimeout = undefined;
755
+ }
756
+ this.tokenizationResolve = undefined;
757
+ this.tokenizationReject = undefined;
758
+ }
733
759
  destroy() {
734
760
  var _a;
735
761
  (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
762
+ if (this.tokenizationReject) {
763
+ const reject = this.tokenizationReject;
764
+ this.clearTokenizationState();
765
+ reject(new types_1.TokenizationError('Tokenizer destroyed', 'DESTROYED'));
766
+ }
736
767
  if (this.spreedly && this.spreedly.removeHandlers) {
737
768
  this.spreedly.removeHandlers();
738
769
  }
739
770
  this.eventHandlers.clear();
740
- if (this.expiryEl) {
771
+ if (this.containerEl) {
772
+ this.containerEl.innerHTML = '';
741
773
  }
742
774
  }
743
775
  hasToken() {
@@ -784,15 +816,6 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
784
816
  }
785
817
  this.updateOverallValidation();
786
818
  }
787
- if (eventType === 'focus') {
788
- this.emit('focus', { field: fieldName });
789
- }
790
- else if (eventType === 'blur') {
791
- this.emit('blur', { field: fieldName });
792
- }
793
- else if (eventType === 'input') {
794
- this.emit('change', { field: fieldName });
795
- }
796
819
  }
797
820
  createInternalElements() {
798
821
  const container = document.getElementById(this.containerId);
@@ -838,6 +861,10 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
838
861
  };
839
862
  this.updateOverallValidation();
840
863
  });
864
+ this.expiryEl.addEventListener('blur', () => {
865
+ this.validateExpiry();
866
+ this.updateOverallValidation();
867
+ });
841
868
  container.appendChild(this.expiryEl);
842
869
  const cvvDiv = (0, tokenizer_utils_1.createFieldContainer)(this.cvvEl, '1', tokenizer_constants_1.FIELD_FLEX.cvv.minWidth);
843
870
  cvvDiv.style.height = this.mergedStyles.input.height;
@@ -865,6 +892,10 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
865
892
  };
866
893
  this.updateOverallValidation();
867
894
  });
895
+ this.expiryEl.addEventListener('blur', () => {
896
+ this.validateExpiry();
897
+ this.updateOverallValidation();
898
+ });
868
899
  container.appendChild(this.expiryEl);
869
900
  const cvvDiv = (0, tokenizer_utils_1.createFieldContainer)(this.cvvEl, tokenizer_constants_1.FIELD_FLEX.cvv.flex, tokenizer_constants_1.FIELD_FLEX.cvv.minWidth);
870
901
  cvvDiv.style.height = this.mergedStyles.input.height;
@@ -1027,23 +1058,31 @@ class SpreedlyTokenizer extends Tokenizer_1.Tokenizer {
1027
1058
  if (!this.expiryEl)
1028
1059
  return;
1029
1060
  const expiry = this.parseExpiry(this.expiryEl);
1061
+ const isEmpty = !this.expiryEl.value;
1062
+ let isValid = false;
1030
1063
  if (!expiry) {
1031
- this.applyErrorStyles(this.expiryEl);
1032
- return;
1033
- }
1034
- const month = parseInt(expiry.month, 10);
1035
- const year = parseInt(expiry.year, 10);
1036
- if (month < 1 || month > 12) {
1037
- this.applyErrorStyles(this.expiryEl);
1038
- return;
1064
+ if (!isEmpty)
1065
+ this.applyErrorStyles(this.expiryEl);
1039
1066
  }
1040
- const now = new Date();
1041
- const expiryDate = new Date(year, month - 1);
1042
- if (expiryDate < now) {
1043
- this.applyErrorStyles(this.expiryEl);
1044
- return;
1067
+ else {
1068
+ const month = parseInt(expiry.month, 10);
1069
+ const year = parseInt(expiry.year, 10);
1070
+ if (month < 1 || month > 12) {
1071
+ this.applyErrorStyles(this.expiryEl);
1072
+ }
1073
+ else {
1074
+ const now = new Date();
1075
+ const expiryDate = new Date(year, month - 1);
1076
+ if (expiryDate < now) {
1077
+ this.applyErrorStyles(this.expiryEl);
1078
+ }
1079
+ else {
1080
+ isValid = true;
1081
+ this.applyInputStyles(this.expiryEl, this.mergedStyles, 'middle');
1082
+ }
1083
+ }
1045
1084
  }
1046
- this.applyInputStyles(this.expiryEl, this.mergedStyles, 'middle');
1085
+ this.currentValidationState.expiry = { isValid, isEmpty };
1047
1086
  }
1048
1087
  applyErrorStyles(element) {
1049
1088
  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;
@@ -118,7 +118,6 @@ class Tokenizer {
118
118
  expiryEl.addEventListener('input', (e) => {
119
119
  const target = e.target;
120
120
  target.value = (0, tokenizer_utils_1.formatExpiryInput)(target.value);
121
- this.emit('change', { field: 'expiry' });
122
121
  });
123
122
  expiryEl.addEventListener('keypress', (e) => {
124
123
  const char = String.fromCharCode(e.which);
@@ -13,8 +13,9 @@ var __rest = (this && this.__rest) || function (s, e) {
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.normalizeGateway = normalizeGateway;
15
15
  function normalizeGateway(input) {
16
- if (!input)
17
- return input;
16
+ if (!input) {
17
+ throw new Error('Gateway is required but received ' + String(input));
18
+ }
18
19
  if (input.backendName !== undefined)
19
20
  return input;
20
21
  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 };
@@ -33,7 +33,9 @@ function fetchSpreedlySecurityArgs(config, organizationId, embedId) {
33
33
  return yield makeRequest();
34
34
  }
35
35
  catch (error) {
36
- if (error instanceof TypeError && error.message.includes('fetch')) {
36
+ const isNetworkError = error instanceof TypeError && error.message.includes('fetch');
37
+ const isServerError = error instanceof Error && error.message.includes('failed: 5');
38
+ if (isNetworkError || isServerError) {
37
39
  yield new Promise((resolve) => setTimeout(resolve, 1000));
38
40
  return makeRequest();
39
41
  }
@@ -65,8 +65,8 @@ function addFocusHandlers(element, styles, onFocus, onBlur) {
65
65
  });
66
66
  element.addEventListener('blur', () => {
67
67
  element.style.border = styles.input.border;
68
- element.style.outline = 'none';
69
- element.style.boxShadow = 'none';
68
+ element.style.outline = '';
69
+ element.style.boxShadow = '';
70
70
  onBlur === null || onBlur === void 0 ? void 0 : onBlur();
71
71
  });
72
72
  }
@@ -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;
package/dist/types.d.ts CHANGED
@@ -53,8 +53,8 @@ export type ACHAccount = {
53
53
  accountHolderType: 'personal' | 'business';
54
54
  accountType: 'checking' | 'savings';
55
55
  };
56
- import type { PaymentMethodType } from './tokenize/types';
57
- export type { PaymentMethodType } from './tokenize/types';
56
+ import { PaymentMethodType } from './tokenize/types';
57
+ export { PaymentMethodType } from './tokenize/types';
58
58
  export type RecaptchaType = 'v2' | 'v3' | 'invisible' | 'organization' | 'nonce' | 'bypass';
59
59
  export type RecaptchaSecuredRequest = {
60
60
  recaptchaType: RecaptchaType;
@@ -275,8 +275,8 @@ export type TokenizeCardConnectResult = {
275
275
  errorcode: number;
276
276
  };
277
277
  export type BackendName = 'spreedly' | 'card_connect' | 'iats' | 'paypal_checkout';
278
- import type { PaymentGateway } from './tokenize/types';
279
- export type { PaymentGateway } from './tokenize/types';
278
+ import { PaymentGateway } from './tokenize/types';
279
+ export { PaymentGateway } from './tokenize/types';
280
280
  export interface EmbedConfig {
281
281
  embed_id?: string;
282
282
  organization_id?: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@idonatedev/idonate-sdk",
3
3
  "author": "iDonate",
4
- "version": "1.2.0-dev13",
4
+ "version": "1.2.0-dev15",
5
5
  "sideEffects": false,
6
6
  "description": "iDonate Web SDK",
7
7
  "engines": {