@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.
- package/dist/chunk-index.cjs.js +171 -39
- package/dist/chunk-index.es.js +171 -39
- package/dist/funnelfox-billing.cjs.js +71 -128
- package/dist/funnelfox-billing.esm.js +71 -128
- package/dist/funnelfox-billing.js +242 -167
- package/dist/funnelfox-billing.min.js +1 -1
- package/package.json +2 -2
- package/src/types.d.ts +104 -11
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
637
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
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(
|
|
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(
|
|
1054
|
-
await Promise.resolve().then(function () { return require('./chunk-index.cjs.js'); })
|
|
1055
|
-
.
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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
|
|