@funnelfox/billing 0.2.1 → 0.3.1

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.
@@ -9,6 +9,7 @@
9
9
 
10
10
  Object.defineProperty(exports, '__esModule', { value: true });
11
11
 
12
+ /* eslint-disable @typescript-eslint/no-explicit-any */
12
13
  /**
13
14
  * @fileoverview Lightweight event emitter for Funnefox SDK
14
15
  */
@@ -65,7 +66,7 @@ class EventEmitter {
65
66
  }
66
67
  catch (error) {
67
68
  // eslint-disable-next-line no-console
68
- console.warn(`Error in event handler for "${eventName}":`, error);
69
+ console.warn(`Error in event handler for "${String(eventName)}":`, error);
69
70
  }
70
71
  }
71
72
  return true;
@@ -150,6 +151,7 @@ class NetworkError extends FunnefoxSDKError {
150
151
  }
151
152
  }
152
153
 
154
+ /* eslint-disable @typescript-eslint/no-explicit-any */
153
155
  /**
154
156
  * @fileoverview Helper utilities for Funnefox SDK
155
157
  */
@@ -215,7 +217,7 @@ var PaymentMethod;
215
217
  /**
216
218
  * @fileoverview Constants for Funnefox SDK
217
219
  */
218
- const SDK_VERSION = '0.2.1';
220
+ const SDK_VERSION = '0.3.1';
219
221
  const DEFAULTS = {
220
222
  BASE_URL: 'https://billing.funnelfox.com',
221
223
  REGION: 'default',
@@ -242,6 +244,9 @@ const EVENTS = {
242
244
  INPUT_ERROR: 'input-error',
243
245
  LOADER_CHANGE: 'loader-change',
244
246
  METHOD_RENDER: 'method-render',
247
+ START_PURCHASE: 'start-purchase',
248
+ PURCHASE_FAILURE: 'purchase-failure',
249
+ PURCHASE_COMPLETED: 'purchase-completed',
245
250
  };
246
251
  const API_ENDPOINTS = {
247
252
  CREATE_CLIENT_SESSION: '/v1/checkout/create_client_session',
@@ -390,7 +395,8 @@ class PrimerWrapper {
390
395
  await pmManager.submit();
391
396
  }
392
397
  catch (error) {
393
- throw new PrimerError('Failed to submit payment', error);
398
+ const primerError = new PrimerError('Failed to submit payment', error);
399
+ throw primerError;
394
400
  }
395
401
  finally {
396
402
  onSubmit(false);
@@ -463,14 +469,6 @@ class PrimerWrapper {
463
469
  throw new PrimerError('No allowed payment methods found');
464
470
  }
465
471
  },
466
- onCheckoutFail: error => {
467
- // eslint-disable-next-line no-console
468
- console.error(error);
469
- },
470
- onTokenizeError: error => {
471
- // eslint-disable-next-line no-console
472
- console.error(error);
473
- },
474
472
  });
475
473
  const methodOptions = {
476
474
  cardSelectors,
@@ -552,7 +550,7 @@ class PrimerWrapper {
552
550
  return this.destroyCallbacks;
553
551
  }
554
552
  isActive() {
555
- return this.isInitialized && this.destroyCallbacks.length;
553
+ return this.isInitialized && this.destroyCallbacks.length > 0;
556
554
  }
557
555
  validateContainer(selector) {
558
556
  const element = document.querySelector(selector);
@@ -572,10 +570,7 @@ class PrimerWrapper {
572
570
  * @fileoverview Input validation utilities for Funnefox SDK
573
571
  */
574
572
  function sanitizeString(input) {
575
- if (input === null || input === undefined) {
576
- return '';
577
- }
578
- return String(input).trim();
573
+ return input?.trim() || '';
579
574
  }
580
575
  function requireString(value, fieldName) {
581
576
  const sanitized = sanitizeString(value);
@@ -611,7 +606,7 @@ class APIClient {
611
606
  }, this.retryAttempts);
612
607
  }
613
608
  catch (error) {
614
- if (error.name === 'APIError') {
609
+ if (error instanceof Error && error.name === 'APIError') {
615
610
  throw error;
616
611
  }
617
612
  throw new NetworkError('Network request failed', error);
@@ -623,6 +618,9 @@ class APIClient {
623
618
  response = await fetch(url, options);
624
619
  }
625
620
  catch (error) {
621
+ if (error instanceof Error && error.name === 'NetworkError') {
622
+ throw error;
623
+ }
626
624
  throw new NetworkError('Network request failed', error);
627
625
  }
628
626
  let data;
@@ -633,8 +631,9 @@ class APIClient {
633
631
  throw new APIError('Invalid JSON response', response.status, {});
634
632
  }
635
633
  if (!response.ok) {
636
- const message = data.message || data.message || data.error || `HTTP ${response.status}`;
637
- throw new APIError(message?.[0]?.msg || message, response.status, {
634
+ const d = data;
635
+ const message = d.error?.[0]?.msg || 'Failed to create payment';
636
+ throw new APIError(message, response.status, {
638
637
  response: data,
639
638
  });
640
639
  }
@@ -652,10 +651,10 @@ class APIClient {
652
651
  if (params.countryCode !== undefined) {
653
652
  payload.country_code = params.countryCode;
654
653
  }
655
- return await this.request(API_ENDPOINTS.CREATE_CLIENT_SESSION, {
654
+ return (await this.request(API_ENDPOINTS.CREATE_CLIENT_SESSION, {
656
655
  method: 'POST',
657
656
  body: JSON.stringify(payload),
658
- });
657
+ }));
659
658
  }
660
659
  async updateClientSession(params) {
661
660
  const payload = {
@@ -673,20 +672,20 @@ class APIClient {
673
672
  order_id: params.orderId,
674
673
  payment_method_token: params.paymentMethodToken,
675
674
  };
676
- return await this.request(API_ENDPOINTS.CREATE_PAYMENT, {
675
+ return (await this.request(API_ENDPOINTS.CREATE_PAYMENT, {
677
676
  method: 'POST',
678
677
  body: JSON.stringify(payload),
679
- });
678
+ }));
680
679
  }
681
680
  async resumePayment(params) {
682
681
  const payload = {
683
682
  order_id: params.orderId,
684
683
  resume_token: params.resumeToken,
685
684
  };
686
- return await this.request(API_ENDPOINTS.RESUME_PAYMENT, {
685
+ return (await this.request(API_ENDPOINTS.RESUME_PAYMENT, {
687
686
  method: 'POST',
688
687
  body: JSON.stringify(payload),
689
- });
688
+ }));
690
689
  }
691
690
  processSessionResponse(response) {
692
691
  if (response.status === 'error') {
@@ -699,7 +698,7 @@ class APIClient {
699
698
  response,
700
699
  });
701
700
  }
702
- const data = response.data || response;
701
+ const data = response.data;
703
702
  return {
704
703
  type: 'session_created',
705
704
  orderId: data.order_id,
@@ -713,11 +712,10 @@ class APIClient {
713
712
  throw new APIError(message, null, {
714
713
  errorCode: firstError?.code,
715
714
  errorType: firstError?.type,
716
- requestId: response.req_id,
717
715
  response,
718
716
  });
719
717
  }
720
- const data = response.data || response;
718
+ const data = response.data;
721
719
  if (data.action_required_token) {
722
720
  return {
723
721
  type: 'action_required',
@@ -732,12 +730,13 @@ class APIClient {
732
730
  type: 'success',
733
731
  orderId: data.order_id,
734
732
  status: 'succeeded',
735
- transactionId: data.transaction_id,
736
733
  };
737
734
  case 'failed':
738
- throw new APIError(data.failed_message_for_user || 'Payment failed', null, data);
735
+ throw new APIError(data.failed_message_for_user || 'Payment failed', null, { response });
739
736
  case 'cancelled':
740
- throw new APIError('Payment was cancelled by user', null, data);
737
+ throw new APIError('Payment was cancelled by user', null, {
738
+ response,
739
+ });
741
740
  case 'processing':
742
741
  return {
743
742
  type: 'processing',
@@ -745,20 +744,10 @@ class APIClient {
745
744
  status: 'processing',
746
745
  };
747
746
  default:
748
- throw new APIError(`Unhandled checkout status: ${data.checkout_status}`, null, data);
747
+ throw new APIError(`Unhandled checkout status: ${data.checkout_status}`, null, { response });
749
748
  }
750
749
  }
751
- throw new APIError('Invalid payment response format', null, data);
752
- }
753
- processResponse(response) {
754
- const data = response.data || response;
755
- if (data.client_token && data.order_id && !data.checkout_status) {
756
- return this.processSessionResponse(response);
757
- }
758
- if (data.checkout_status || data.action_required_token) {
759
- return this.processPaymentResponse(response);
760
- }
761
- throw new APIError('Unknown response format', null, response);
750
+ throw new APIError('Invalid payment response format', null, { response });
762
751
  }
763
752
  }
764
753
 
@@ -777,8 +766,6 @@ class CheckoutInstance extends EventEmitter {
777
766
  };
778
767
  this.handleSubmit = (isSubmitting) => {
779
768
  this.onLoaderChangeWithRace(isSubmitting);
780
- // Clear any previous errors
781
- this.emit(EVENTS.ERROR);
782
769
  this._setState(isSubmitting ? 'processing' : 'ready');
783
770
  };
784
771
  this.handleTokenizeSuccess = async (paymentMethodTokenData, primerHandler) => {
@@ -794,7 +781,7 @@ class CheckoutInstance extends EventEmitter {
794
781
  }
795
782
  catch (error) {
796
783
  this._setState('error');
797
- this.emit(EVENTS.ERROR, error);
784
+ this.emit(EVENTS.PURCHASE_FAILURE, new Error(error.message || 'Payment processing failed'));
798
785
  primerHandler.handleFailure(error.message || 'Payment processing failed');
799
786
  }
800
787
  finally {
@@ -815,10 +802,11 @@ class CheckoutInstance extends EventEmitter {
815
802
  }
816
803
  catch (error) {
817
804
  this._setState('error');
818
- this.emit(EVENTS.ERROR, error);
805
+ this.emit(EVENTS.PURCHASE_FAILURE, new Error(error.message || 'Payment processing failed'));
819
806
  primerHandler.handleFailure(error.message || 'Payment processing failed');
820
807
  }
821
808
  finally {
809
+ this.emit(EVENTS.PURCHASE_COMPLETED);
822
810
  this.onLoaderChangeWithRace(false);
823
811
  this._setState('ready');
824
812
  }
@@ -863,18 +851,6 @@ class CheckoutInstance extends EventEmitter {
863
851
  this.on(EVENTS.DESTROY, this.callbacks.onDestroy);
864
852
  }
865
853
  }
866
- on(eventName, handler) {
867
- return super.on(eventName, handler);
868
- }
869
- once(eventName, handler) {
870
- return super.once(eventName, handler);
871
- }
872
- off(eventName, handler = null) {
873
- return super.off(eventName, handler);
874
- }
875
- emit(eventName, ...args) {
876
- return super.emit(eventName, ...args);
877
- }
878
854
  removeAllListeners() {
879
855
  return super.removeAllListeners();
880
856
  }
@@ -916,10 +892,30 @@ class CheckoutInstance extends EventEmitter {
916
892
  onSubmit: this.handleSubmit,
917
893
  onInputChange: this.handleInputChange,
918
894
  onMethodRender: this.handleMethodRender,
895
+ onResumeError: error => {
896
+ if (error.stack?.includes('PROCESSOR_3DS') &&
897
+ error.code === 'RESUME_ERROR' &&
898
+ error.message?.includes('fetch resume key')) {
899
+ // Ignore 3DS close error, because it is not understandable by user
900
+ return;
901
+ }
902
+ this.emit(EVENTS.PURCHASE_FAILURE, error);
903
+ },
904
+ onCheckoutFail: error => {
905
+ this.emit(EVENTS.PURCHASE_FAILURE, error);
906
+ },
907
+ onTokenizeError: error => {
908
+ this.emit(EVENTS.PURCHASE_FAILURE, error);
909
+ },
910
+ onTokenizeShouldStart: data => {
911
+ this.emit(EVENTS.ERROR, undefined);
912
+ this.emit(EVENTS.START_PURCHASE, data.paymentMethodType);
913
+ return true;
914
+ },
919
915
  };
920
916
  if (!this.checkoutConfig.cardSelectors ||
921
917
  !this.checkoutConfig.paymentButtonSelectors) {
922
- const cardSelectors = await this.createCardElements(this.checkoutConfig.container);
918
+ const cardSelectors = await this.createCardElements();
923
919
  const paymentButtonSelectors = {
924
920
  paypal: '#paypalButton',
925
921
  googlePay: '#googlePayButton',
@@ -955,8 +951,6 @@ class CheckoutInstance extends EventEmitter {
955
951
  this.emit(EVENTS.SUCCESS, {
956
952
  orderId: result.orderId,
957
953
  status: result.status,
958
- transactionId: result.transactionId,
959
- metadata: result.metadata,
960
954
  });
961
955
  primerHandler.handleSuccess();
962
956
  break;
@@ -990,7 +984,6 @@ class CheckoutInstance extends EventEmitter {
990
984
  });
991
985
  this.checkoutConfig.priceId = newPriceId;
992
986
  this._setState('ready');
993
- this.emit(EVENTS.STATUS_CHANGE, 'price-updated');
994
987
  }
995
988
  catch (error) {
996
989
  this._setState('error');
@@ -1050,71 +1043,21 @@ class CheckoutInstance extends EventEmitter {
1050
1043
  }
1051
1044
  // Creates containers to render hosted inputs with labels and error messages,
1052
1045
  // a card holder input with label and error, and a submit button.
1053
- async createCardElements(container) {
1054
- await Promise.resolve().then(function () { return require('./chunk-index.cjs.js'); })
1055
- .then(module => module.default)
1056
- .then(init => init(this.checkoutConfig.container));
1057
- const cardNumberContainer = document.querySelector(`${container} #cardNumberInput`);
1058
- const cardExpiryContainer = document.querySelector(`${container} #expiryInput`);
1059
- const cardCvvContainer = document.querySelector(`${container} #cvvInput`);
1060
- const elementsMap = {
1061
- cardNumber: cardNumberContainer.parentElement,
1062
- expiryDate: cardExpiryContainer.parentElement,
1063
- cvv: cardCvvContainer.parentElement,
1064
- };
1065
- const onLoaderChange = (isLoading) => {
1066
- this.primerWrapper.disableButtons(isLoading);
1067
- document
1068
- .querySelectorAll(`${container} .loader-container`)
1069
- ?.forEach(loaderEl => {
1070
- loaderEl.style.display = isLoading ? 'flex' : 'none';
1071
- });
1072
- };
1073
- this.on(EVENTS.INPUT_ERROR, event => {
1074
- const { name, error } = event;
1075
- const errorContainer = elementsMap[name]?.querySelector('.errorContainer');
1076
- if (errorContainer) {
1077
- errorContainer.textContent = error || '';
1078
- }
1079
- });
1080
- this.on(EVENTS.STATUS_CHANGE, (state, oldState) => {
1081
- const isLoading = ['initializing'].includes(state);
1082
- if (!isLoading && oldState === 'initializing') {
1083
- onLoaderChange(false);
1084
- }
1085
- });
1086
- function setError(error) {
1087
- const errorContainer = document.querySelector('.payment-errors-container');
1088
- if (errorContainer) {
1089
- errorContainer.textContent = error?.message || '';
1090
- }
1091
- }
1092
- this.on(EVENTS.ERROR, (error) => {
1093
- setError(error);
1094
- });
1095
- this.on(EVENTS.LOADER_CHANGE, onLoaderChange);
1096
- this.on(EVENTS.DESTROY, () => {
1097
- this.primerWrapper.validateContainer(container)?.remove();
1098
- });
1099
- this.on(EVENTS.METHOD_RENDER, (method) => {
1100
- const methodContainer = document.querySelector(`.ff-payment-method-${method.replace('_', '-').toLowerCase()}`);
1101
- methodContainer.classList.add('visible');
1102
- });
1103
- this.on(EVENTS.SUCCESS, () => {
1104
- const successScreenString = document.querySelector('#success-screen')?.innerHTML;
1105
- const containers = document.querySelectorAll('.ff-payment-container');
1106
- containers.forEach(container => {
1107
- container.innerHTML = successScreenString;
1108
- });
1109
- onLoaderChange(false);
1110
- });
1111
- return {
1112
- cardNumber: '#cardNumberInput',
1113
- expiryDate: '#expiryInput',
1114
- cvv: '#cvvInput',
1115
- cardholderName: '#cardHolderInput',
1116
- button: '#submitButton',
1117
- };
1046
+ async createCardElements() {
1047
+ const skinFactory = (await Promise.resolve().then(function () { return require('./chunk-index.cjs.js'); }))
1048
+ .default;
1049
+ const skin = await skinFactory(this.primerWrapper, this.checkoutConfig.container);
1050
+ this.on(EVENTS.INPUT_ERROR, skin.onInputError);
1051
+ this.on(EVENTS.STATUS_CHANGE, skin.onStatusChange);
1052
+ this.on(EVENTS.ERROR, (error) => skin.onError(error));
1053
+ this.on(EVENTS.LOADER_CHANGE, skin.onLoaderChange);
1054
+ this.on(EVENTS.DESTROY, skin.onDestroy);
1055
+ this.on(EVENTS.METHOD_RENDER, skin.onMethodRender);
1056
+ this.on(EVENTS.SUCCESS, skin.onSuccess);
1057
+ this.on(EVENTS.START_PURCHASE, skin.onStartPurchase);
1058
+ this.on(EVENTS.PURCHASE_FAILURE, skin.onPurchaseFailure);
1059
+ this.on(EVENTS.PURCHASE_COMPLETED, skin.onPurchaseCompleted);
1060
+ return skin.getCardInputSelectors();
1118
1061
  }
1119
1062
  }
1120
1063