@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
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @author Funnelfox
|
|
6
6
|
* @license MIT
|
|
7
7
|
*/
|
|
8
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
8
9
|
/**
|
|
9
10
|
* @fileoverview Lightweight event emitter for Funnefox SDK
|
|
10
11
|
*/
|
|
@@ -61,7 +62,7 @@ class EventEmitter {
|
|
|
61
62
|
}
|
|
62
63
|
catch (error) {
|
|
63
64
|
// eslint-disable-next-line no-console
|
|
64
|
-
console.warn(`Error in event handler for "${eventName}":`, error);
|
|
65
|
+
console.warn(`Error in event handler for "${String(eventName)}":`, error);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
return true;
|
|
@@ -146,6 +147,7 @@ class NetworkError extends FunnefoxSDKError {
|
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
149
151
|
/**
|
|
150
152
|
* @fileoverview Helper utilities for Funnefox SDK
|
|
151
153
|
*/
|
|
@@ -211,7 +213,7 @@ var PaymentMethod;
|
|
|
211
213
|
/**
|
|
212
214
|
* @fileoverview Constants for Funnefox SDK
|
|
213
215
|
*/
|
|
214
|
-
const SDK_VERSION = '0.
|
|
216
|
+
const SDK_VERSION = '0.3.1';
|
|
215
217
|
const DEFAULTS = {
|
|
216
218
|
BASE_URL: 'https://billing.funnelfox.com',
|
|
217
219
|
REGION: 'default',
|
|
@@ -238,6 +240,9 @@ const EVENTS = {
|
|
|
238
240
|
INPUT_ERROR: 'input-error',
|
|
239
241
|
LOADER_CHANGE: 'loader-change',
|
|
240
242
|
METHOD_RENDER: 'method-render',
|
|
243
|
+
START_PURCHASE: 'start-purchase',
|
|
244
|
+
PURCHASE_FAILURE: 'purchase-failure',
|
|
245
|
+
PURCHASE_COMPLETED: 'purchase-completed',
|
|
241
246
|
};
|
|
242
247
|
const API_ENDPOINTS = {
|
|
243
248
|
CREATE_CLIENT_SESSION: '/v1/checkout/create_client_session',
|
|
@@ -386,7 +391,8 @@ class PrimerWrapper {
|
|
|
386
391
|
await pmManager.submit();
|
|
387
392
|
}
|
|
388
393
|
catch (error) {
|
|
389
|
-
|
|
394
|
+
const primerError = new PrimerError('Failed to submit payment', error);
|
|
395
|
+
throw primerError;
|
|
390
396
|
}
|
|
391
397
|
finally {
|
|
392
398
|
onSubmit(false);
|
|
@@ -459,14 +465,6 @@ class PrimerWrapper {
|
|
|
459
465
|
throw new PrimerError('No allowed payment methods found');
|
|
460
466
|
}
|
|
461
467
|
},
|
|
462
|
-
onCheckoutFail: error => {
|
|
463
|
-
// eslint-disable-next-line no-console
|
|
464
|
-
console.error(error);
|
|
465
|
-
},
|
|
466
|
-
onTokenizeError: error => {
|
|
467
|
-
// eslint-disable-next-line no-console
|
|
468
|
-
console.error(error);
|
|
469
|
-
},
|
|
470
468
|
});
|
|
471
469
|
const methodOptions = {
|
|
472
470
|
cardSelectors,
|
|
@@ -548,7 +546,7 @@ class PrimerWrapper {
|
|
|
548
546
|
return this.destroyCallbacks;
|
|
549
547
|
}
|
|
550
548
|
isActive() {
|
|
551
|
-
return this.isInitialized && this.destroyCallbacks.length;
|
|
549
|
+
return this.isInitialized && this.destroyCallbacks.length > 0;
|
|
552
550
|
}
|
|
553
551
|
validateContainer(selector) {
|
|
554
552
|
const element = document.querySelector(selector);
|
|
@@ -568,10 +566,7 @@ class PrimerWrapper {
|
|
|
568
566
|
* @fileoverview Input validation utilities for Funnefox SDK
|
|
569
567
|
*/
|
|
570
568
|
function sanitizeString(input) {
|
|
571
|
-
|
|
572
|
-
return '';
|
|
573
|
-
}
|
|
574
|
-
return String(input).trim();
|
|
569
|
+
return input?.trim() || '';
|
|
575
570
|
}
|
|
576
571
|
function requireString(value, fieldName) {
|
|
577
572
|
const sanitized = sanitizeString(value);
|
|
@@ -607,7 +602,7 @@ class APIClient {
|
|
|
607
602
|
}, this.retryAttempts);
|
|
608
603
|
}
|
|
609
604
|
catch (error) {
|
|
610
|
-
if (error.name === 'APIError') {
|
|
605
|
+
if (error instanceof Error && error.name === 'APIError') {
|
|
611
606
|
throw error;
|
|
612
607
|
}
|
|
613
608
|
throw new NetworkError('Network request failed', error);
|
|
@@ -619,6 +614,9 @@ class APIClient {
|
|
|
619
614
|
response = await fetch(url, options);
|
|
620
615
|
}
|
|
621
616
|
catch (error) {
|
|
617
|
+
if (error instanceof Error && error.name === 'NetworkError') {
|
|
618
|
+
throw error;
|
|
619
|
+
}
|
|
622
620
|
throw new NetworkError('Network request failed', error);
|
|
623
621
|
}
|
|
624
622
|
let data;
|
|
@@ -629,8 +627,9 @@ class APIClient {
|
|
|
629
627
|
throw new APIError('Invalid JSON response', response.status, {});
|
|
630
628
|
}
|
|
631
629
|
if (!response.ok) {
|
|
632
|
-
const
|
|
633
|
-
|
|
630
|
+
const d = data;
|
|
631
|
+
const message = d.error?.[0]?.msg || 'Failed to create payment';
|
|
632
|
+
throw new APIError(message, response.status, {
|
|
634
633
|
response: data,
|
|
635
634
|
});
|
|
636
635
|
}
|
|
@@ -648,10 +647,10 @@ class APIClient {
|
|
|
648
647
|
if (params.countryCode !== undefined) {
|
|
649
648
|
payload.country_code = params.countryCode;
|
|
650
649
|
}
|
|
651
|
-
return await this.request(API_ENDPOINTS.CREATE_CLIENT_SESSION, {
|
|
650
|
+
return (await this.request(API_ENDPOINTS.CREATE_CLIENT_SESSION, {
|
|
652
651
|
method: 'POST',
|
|
653
652
|
body: JSON.stringify(payload),
|
|
654
|
-
});
|
|
653
|
+
}));
|
|
655
654
|
}
|
|
656
655
|
async updateClientSession(params) {
|
|
657
656
|
const payload = {
|
|
@@ -669,20 +668,20 @@ class APIClient {
|
|
|
669
668
|
order_id: params.orderId,
|
|
670
669
|
payment_method_token: params.paymentMethodToken,
|
|
671
670
|
};
|
|
672
|
-
return await this.request(API_ENDPOINTS.CREATE_PAYMENT, {
|
|
671
|
+
return (await this.request(API_ENDPOINTS.CREATE_PAYMENT, {
|
|
673
672
|
method: 'POST',
|
|
674
673
|
body: JSON.stringify(payload),
|
|
675
|
-
});
|
|
674
|
+
}));
|
|
676
675
|
}
|
|
677
676
|
async resumePayment(params) {
|
|
678
677
|
const payload = {
|
|
679
678
|
order_id: params.orderId,
|
|
680
679
|
resume_token: params.resumeToken,
|
|
681
680
|
};
|
|
682
|
-
return await this.request(API_ENDPOINTS.RESUME_PAYMENT, {
|
|
681
|
+
return (await this.request(API_ENDPOINTS.RESUME_PAYMENT, {
|
|
683
682
|
method: 'POST',
|
|
684
683
|
body: JSON.stringify(payload),
|
|
685
|
-
});
|
|
684
|
+
}));
|
|
686
685
|
}
|
|
687
686
|
processSessionResponse(response) {
|
|
688
687
|
if (response.status === 'error') {
|
|
@@ -695,7 +694,7 @@ class APIClient {
|
|
|
695
694
|
response,
|
|
696
695
|
});
|
|
697
696
|
}
|
|
698
|
-
const data = response.data
|
|
697
|
+
const data = response.data;
|
|
699
698
|
return {
|
|
700
699
|
type: 'session_created',
|
|
701
700
|
orderId: data.order_id,
|
|
@@ -709,11 +708,10 @@ class APIClient {
|
|
|
709
708
|
throw new APIError(message, null, {
|
|
710
709
|
errorCode: firstError?.code,
|
|
711
710
|
errorType: firstError?.type,
|
|
712
|
-
requestId: response.req_id,
|
|
713
711
|
response,
|
|
714
712
|
});
|
|
715
713
|
}
|
|
716
|
-
const data = response.data
|
|
714
|
+
const data = response.data;
|
|
717
715
|
if (data.action_required_token) {
|
|
718
716
|
return {
|
|
719
717
|
type: 'action_required',
|
|
@@ -728,12 +726,13 @@ class APIClient {
|
|
|
728
726
|
type: 'success',
|
|
729
727
|
orderId: data.order_id,
|
|
730
728
|
status: 'succeeded',
|
|
731
|
-
transactionId: data.transaction_id,
|
|
732
729
|
};
|
|
733
730
|
case 'failed':
|
|
734
|
-
throw new APIError(data.failed_message_for_user || 'Payment failed', null,
|
|
731
|
+
throw new APIError(data.failed_message_for_user || 'Payment failed', null, { response });
|
|
735
732
|
case 'cancelled':
|
|
736
|
-
throw new APIError('Payment was cancelled by user', null,
|
|
733
|
+
throw new APIError('Payment was cancelled by user', null, {
|
|
734
|
+
response,
|
|
735
|
+
});
|
|
737
736
|
case 'processing':
|
|
738
737
|
return {
|
|
739
738
|
type: 'processing',
|
|
@@ -741,20 +740,10 @@ class APIClient {
|
|
|
741
740
|
status: 'processing',
|
|
742
741
|
};
|
|
743
742
|
default:
|
|
744
|
-
throw new APIError(`Unhandled checkout status: ${data.checkout_status}`, null,
|
|
743
|
+
throw new APIError(`Unhandled checkout status: ${data.checkout_status}`, null, { response });
|
|
745
744
|
}
|
|
746
745
|
}
|
|
747
|
-
throw new APIError('Invalid payment response format', null,
|
|
748
|
-
}
|
|
749
|
-
processResponse(response) {
|
|
750
|
-
const data = response.data || response;
|
|
751
|
-
if (data.client_token && data.order_id && !data.checkout_status) {
|
|
752
|
-
return this.processSessionResponse(response);
|
|
753
|
-
}
|
|
754
|
-
if (data.checkout_status || data.action_required_token) {
|
|
755
|
-
return this.processPaymentResponse(response);
|
|
756
|
-
}
|
|
757
|
-
throw new APIError('Unknown response format', null, response);
|
|
746
|
+
throw new APIError('Invalid payment response format', null, { response });
|
|
758
747
|
}
|
|
759
748
|
}
|
|
760
749
|
|
|
@@ -773,8 +762,6 @@ class CheckoutInstance extends EventEmitter {
|
|
|
773
762
|
};
|
|
774
763
|
this.handleSubmit = (isSubmitting) => {
|
|
775
764
|
this.onLoaderChangeWithRace(isSubmitting);
|
|
776
|
-
// Clear any previous errors
|
|
777
|
-
this.emit(EVENTS.ERROR);
|
|
778
765
|
this._setState(isSubmitting ? 'processing' : 'ready');
|
|
779
766
|
};
|
|
780
767
|
this.handleTokenizeSuccess = async (paymentMethodTokenData, primerHandler) => {
|
|
@@ -790,7 +777,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
790
777
|
}
|
|
791
778
|
catch (error) {
|
|
792
779
|
this._setState('error');
|
|
793
|
-
this.emit(EVENTS.
|
|
780
|
+
this.emit(EVENTS.PURCHASE_FAILURE, new Error(error.message || 'Payment processing failed'));
|
|
794
781
|
primerHandler.handleFailure(error.message || 'Payment processing failed');
|
|
795
782
|
}
|
|
796
783
|
finally {
|
|
@@ -811,10 +798,11 @@ class CheckoutInstance extends EventEmitter {
|
|
|
811
798
|
}
|
|
812
799
|
catch (error) {
|
|
813
800
|
this._setState('error');
|
|
814
|
-
this.emit(EVENTS.
|
|
801
|
+
this.emit(EVENTS.PURCHASE_FAILURE, new Error(error.message || 'Payment processing failed'));
|
|
815
802
|
primerHandler.handleFailure(error.message || 'Payment processing failed');
|
|
816
803
|
}
|
|
817
804
|
finally {
|
|
805
|
+
this.emit(EVENTS.PURCHASE_COMPLETED);
|
|
818
806
|
this.onLoaderChangeWithRace(false);
|
|
819
807
|
this._setState('ready');
|
|
820
808
|
}
|
|
@@ -859,18 +847,6 @@ class CheckoutInstance extends EventEmitter {
|
|
|
859
847
|
this.on(EVENTS.DESTROY, this.callbacks.onDestroy);
|
|
860
848
|
}
|
|
861
849
|
}
|
|
862
|
-
on(eventName, handler) {
|
|
863
|
-
return super.on(eventName, handler);
|
|
864
|
-
}
|
|
865
|
-
once(eventName, handler) {
|
|
866
|
-
return super.once(eventName, handler);
|
|
867
|
-
}
|
|
868
|
-
off(eventName, handler = null) {
|
|
869
|
-
return super.off(eventName, handler);
|
|
870
|
-
}
|
|
871
|
-
emit(eventName, ...args) {
|
|
872
|
-
return super.emit(eventName, ...args);
|
|
873
|
-
}
|
|
874
850
|
removeAllListeners() {
|
|
875
851
|
return super.removeAllListeners();
|
|
876
852
|
}
|
|
@@ -912,10 +888,30 @@ class CheckoutInstance extends EventEmitter {
|
|
|
912
888
|
onSubmit: this.handleSubmit,
|
|
913
889
|
onInputChange: this.handleInputChange,
|
|
914
890
|
onMethodRender: this.handleMethodRender,
|
|
891
|
+
onResumeError: error => {
|
|
892
|
+
if (error.stack?.includes('PROCESSOR_3DS') &&
|
|
893
|
+
error.code === 'RESUME_ERROR' &&
|
|
894
|
+
error.message?.includes('fetch resume key')) {
|
|
895
|
+
// Ignore 3DS close error, because it is not understandable by user
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
this.emit(EVENTS.PURCHASE_FAILURE, error);
|
|
899
|
+
},
|
|
900
|
+
onCheckoutFail: error => {
|
|
901
|
+
this.emit(EVENTS.PURCHASE_FAILURE, error);
|
|
902
|
+
},
|
|
903
|
+
onTokenizeError: error => {
|
|
904
|
+
this.emit(EVENTS.PURCHASE_FAILURE, error);
|
|
905
|
+
},
|
|
906
|
+
onTokenizeShouldStart: data => {
|
|
907
|
+
this.emit(EVENTS.ERROR, undefined);
|
|
908
|
+
this.emit(EVENTS.START_PURCHASE, data.paymentMethodType);
|
|
909
|
+
return true;
|
|
910
|
+
},
|
|
915
911
|
};
|
|
916
912
|
if (!this.checkoutConfig.cardSelectors ||
|
|
917
913
|
!this.checkoutConfig.paymentButtonSelectors) {
|
|
918
|
-
const cardSelectors = await this.createCardElements(
|
|
914
|
+
const cardSelectors = await this.createCardElements();
|
|
919
915
|
const paymentButtonSelectors = {
|
|
920
916
|
paypal: '#paypalButton',
|
|
921
917
|
googlePay: '#googlePayButton',
|
|
@@ -951,8 +947,6 @@ class CheckoutInstance extends EventEmitter {
|
|
|
951
947
|
this.emit(EVENTS.SUCCESS, {
|
|
952
948
|
orderId: result.orderId,
|
|
953
949
|
status: result.status,
|
|
954
|
-
transactionId: result.transactionId,
|
|
955
|
-
metadata: result.metadata,
|
|
956
950
|
});
|
|
957
951
|
primerHandler.handleSuccess();
|
|
958
952
|
break;
|
|
@@ -986,7 +980,6 @@ class CheckoutInstance extends EventEmitter {
|
|
|
986
980
|
});
|
|
987
981
|
this.checkoutConfig.priceId = newPriceId;
|
|
988
982
|
this._setState('ready');
|
|
989
|
-
this.emit(EVENTS.STATUS_CHANGE, 'price-updated');
|
|
990
983
|
}
|
|
991
984
|
catch (error) {
|
|
992
985
|
this._setState('error');
|
|
@@ -1046,71 +1039,21 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1046
1039
|
}
|
|
1047
1040
|
// Creates containers to render hosted inputs with labels and error messages,
|
|
1048
1041
|
// a card holder input with label and error, and a submit button.
|
|
1049
|
-
async createCardElements(
|
|
1050
|
-
await import('./chunk-index.es.js')
|
|
1051
|
-
.
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
.querySelectorAll(`${container} .loader-container`)
|
|
1065
|
-
?.forEach(loaderEl => {
|
|
1066
|
-
loaderEl.style.display = isLoading ? 'flex' : 'none';
|
|
1067
|
-
});
|
|
1068
|
-
};
|
|
1069
|
-
this.on(EVENTS.INPUT_ERROR, event => {
|
|
1070
|
-
const { name, error } = event;
|
|
1071
|
-
const errorContainer = elementsMap[name]?.querySelector('.errorContainer');
|
|
1072
|
-
if (errorContainer) {
|
|
1073
|
-
errorContainer.textContent = error || '';
|
|
1074
|
-
}
|
|
1075
|
-
});
|
|
1076
|
-
this.on(EVENTS.STATUS_CHANGE, (state, oldState) => {
|
|
1077
|
-
const isLoading = ['initializing'].includes(state);
|
|
1078
|
-
if (!isLoading && oldState === 'initializing') {
|
|
1079
|
-
onLoaderChange(false);
|
|
1080
|
-
}
|
|
1081
|
-
});
|
|
1082
|
-
function setError(error) {
|
|
1083
|
-
const errorContainer = document.querySelector('.payment-errors-container');
|
|
1084
|
-
if (errorContainer) {
|
|
1085
|
-
errorContainer.textContent = error?.message || '';
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
this.on(EVENTS.ERROR, (error) => {
|
|
1089
|
-
setError(error);
|
|
1090
|
-
});
|
|
1091
|
-
this.on(EVENTS.LOADER_CHANGE, onLoaderChange);
|
|
1092
|
-
this.on(EVENTS.DESTROY, () => {
|
|
1093
|
-
this.primerWrapper.validateContainer(container)?.remove();
|
|
1094
|
-
});
|
|
1095
|
-
this.on(EVENTS.METHOD_RENDER, (method) => {
|
|
1096
|
-
const methodContainer = document.querySelector(`.ff-payment-method-${method.replace('_', '-').toLowerCase()}`);
|
|
1097
|
-
methodContainer.classList.add('visible');
|
|
1098
|
-
});
|
|
1099
|
-
this.on(EVENTS.SUCCESS, () => {
|
|
1100
|
-
const successScreenString = document.querySelector('#success-screen')?.innerHTML;
|
|
1101
|
-
const containers = document.querySelectorAll('.ff-payment-container');
|
|
1102
|
-
containers.forEach(container => {
|
|
1103
|
-
container.innerHTML = successScreenString;
|
|
1104
|
-
});
|
|
1105
|
-
onLoaderChange(false);
|
|
1106
|
-
});
|
|
1107
|
-
return {
|
|
1108
|
-
cardNumber: '#cardNumberInput',
|
|
1109
|
-
expiryDate: '#expiryInput',
|
|
1110
|
-
cvv: '#cvvInput',
|
|
1111
|
-
cardholderName: '#cardHolderInput',
|
|
1112
|
-
button: '#submitButton',
|
|
1113
|
-
};
|
|
1042
|
+
async createCardElements() {
|
|
1043
|
+
const skinFactory = (await import('./chunk-index.es.js'))
|
|
1044
|
+
.default;
|
|
1045
|
+
const skin = await skinFactory(this.primerWrapper, this.checkoutConfig.container);
|
|
1046
|
+
this.on(EVENTS.INPUT_ERROR, skin.onInputError);
|
|
1047
|
+
this.on(EVENTS.STATUS_CHANGE, skin.onStatusChange);
|
|
1048
|
+
this.on(EVENTS.ERROR, (error) => skin.onError(error));
|
|
1049
|
+
this.on(EVENTS.LOADER_CHANGE, skin.onLoaderChange);
|
|
1050
|
+
this.on(EVENTS.DESTROY, skin.onDestroy);
|
|
1051
|
+
this.on(EVENTS.METHOD_RENDER, skin.onMethodRender);
|
|
1052
|
+
this.on(EVENTS.SUCCESS, skin.onSuccess);
|
|
1053
|
+
this.on(EVENTS.START_PURCHASE, skin.onStartPurchase);
|
|
1054
|
+
this.on(EVENTS.PURCHASE_FAILURE, skin.onPurchaseFailure);
|
|
1055
|
+
this.on(EVENTS.PURCHASE_COMPLETED, skin.onPurchaseCompleted);
|
|
1056
|
+
return skin.getCardInputSelectors();
|
|
1114
1057
|
}
|
|
1115
1058
|
}
|
|
1116
1059
|
|