@funnelfox/billing 0.2.1 → 0.3.0
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 +159 -37
- package/dist/chunk-index.es.js +159 -37
- package/dist/funnelfox-billing.cjs.js +64 -122
- package/dist/funnelfox-billing.esm.js +64 -122
- package/dist/funnelfox-billing.js +224 -160
- package/dist/funnelfox-billing.min.js +1 -1
- package/package.json +2 -2
- package/src/types.d.ts +105 -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.0';
|
|
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',
|
|
@@ -348,7 +353,7 @@ class PrimerWrapper {
|
|
|
348
353
|
this.paymentMethodsInterfaces[method].setDisabled(disabled);
|
|
349
354
|
}
|
|
350
355
|
}
|
|
351
|
-
async renderCardCheckout({ onSubmit, cardSelectors, onInputChange, }) {
|
|
356
|
+
async renderCardCheckout({ onSubmitError, onSubmit, cardSelectors, onInputChange, }) {
|
|
352
357
|
try {
|
|
353
358
|
const elements = this.initializeCardElements(cardSelectors);
|
|
354
359
|
const pmManager = await this.headless.createPaymentMethodManager('PAYMENT_CARD');
|
|
@@ -390,7 +395,9 @@ 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
|
+
onSubmitError(primerError);
|
|
400
|
+
throw primerError;
|
|
394
401
|
}
|
|
395
402
|
finally {
|
|
396
403
|
onSubmit(false);
|
|
@@ -452,7 +459,7 @@ class PrimerWrapper {
|
|
|
452
459
|
}
|
|
453
460
|
}
|
|
454
461
|
async renderCheckout(clientToken, options) {
|
|
455
|
-
const { cardSelectors, paymentButtonSelectors, container, onTokenizeSuccess, onResumeSuccess, onSubmit, onInputChange, onMethodRender, ...restPrimerOptions } = options;
|
|
462
|
+
const { cardSelectors, paymentButtonSelectors, container, onTokenizeSuccess, onResumeSuccess, onSubmit, onInputChange, onMethodRender, onSubmitError, ...restPrimerOptions } = options;
|
|
456
463
|
await this.createHeadlessCheckout(clientToken, {
|
|
457
464
|
...restPrimerOptions,
|
|
458
465
|
onTokenizeSuccess: this.wrapTokenizeHandler(onTokenizeSuccess),
|
|
@@ -477,6 +484,7 @@ class PrimerWrapper {
|
|
|
477
484
|
container,
|
|
478
485
|
onSubmit,
|
|
479
486
|
onInputChange,
|
|
487
|
+
onSubmitError,
|
|
480
488
|
};
|
|
481
489
|
this.availableMethods.forEach(async (method) => {
|
|
482
490
|
if (method === PaymentMethod.PAYMENT_CARD) {
|
|
@@ -552,7 +560,7 @@ class PrimerWrapper {
|
|
|
552
560
|
return this.destroyCallbacks;
|
|
553
561
|
}
|
|
554
562
|
isActive() {
|
|
555
|
-
return this.isInitialized && this.destroyCallbacks.length;
|
|
563
|
+
return this.isInitialized && this.destroyCallbacks.length > 0;
|
|
556
564
|
}
|
|
557
565
|
validateContainer(selector) {
|
|
558
566
|
const element = document.querySelector(selector);
|
|
@@ -572,10 +580,7 @@ class PrimerWrapper {
|
|
|
572
580
|
* @fileoverview Input validation utilities for Funnefox SDK
|
|
573
581
|
*/
|
|
574
582
|
function sanitizeString(input) {
|
|
575
|
-
|
|
576
|
-
return '';
|
|
577
|
-
}
|
|
578
|
-
return String(input).trim();
|
|
583
|
+
return input?.trim() || '';
|
|
579
584
|
}
|
|
580
585
|
function requireString(value, fieldName) {
|
|
581
586
|
const sanitized = sanitizeString(value);
|
|
@@ -611,7 +616,7 @@ class APIClient {
|
|
|
611
616
|
}, this.retryAttempts);
|
|
612
617
|
}
|
|
613
618
|
catch (error) {
|
|
614
|
-
if (error.name === 'APIError') {
|
|
619
|
+
if (error instanceof Error && error.name === 'APIError') {
|
|
615
620
|
throw error;
|
|
616
621
|
}
|
|
617
622
|
throw new NetworkError('Network request failed', error);
|
|
@@ -623,6 +628,9 @@ class APIClient {
|
|
|
623
628
|
response = await fetch(url, options);
|
|
624
629
|
}
|
|
625
630
|
catch (error) {
|
|
631
|
+
if (error instanceof Error && error.name === 'NetworkError') {
|
|
632
|
+
throw error;
|
|
633
|
+
}
|
|
626
634
|
throw new NetworkError('Network request failed', error);
|
|
627
635
|
}
|
|
628
636
|
let data;
|
|
@@ -633,8 +641,11 @@ class APIClient {
|
|
|
633
641
|
throw new APIError('Invalid JSON response', response.status, {});
|
|
634
642
|
}
|
|
635
643
|
if (!response.ok) {
|
|
636
|
-
const
|
|
637
|
-
|
|
644
|
+
const d = data;
|
|
645
|
+
const message = d.message instanceof Array
|
|
646
|
+
? d.message[0].msg
|
|
647
|
+
: d.message || d.error || `HTTP ${response.status}`;
|
|
648
|
+
throw new APIError(message, response.status, {
|
|
638
649
|
response: data,
|
|
639
650
|
});
|
|
640
651
|
}
|
|
@@ -652,10 +663,10 @@ class APIClient {
|
|
|
652
663
|
if (params.countryCode !== undefined) {
|
|
653
664
|
payload.country_code = params.countryCode;
|
|
654
665
|
}
|
|
655
|
-
return await this.request(API_ENDPOINTS.CREATE_CLIENT_SESSION, {
|
|
666
|
+
return (await this.request(API_ENDPOINTS.CREATE_CLIENT_SESSION, {
|
|
656
667
|
method: 'POST',
|
|
657
668
|
body: JSON.stringify(payload),
|
|
658
|
-
});
|
|
669
|
+
}));
|
|
659
670
|
}
|
|
660
671
|
async updateClientSession(params) {
|
|
661
672
|
const payload = {
|
|
@@ -673,20 +684,20 @@ class APIClient {
|
|
|
673
684
|
order_id: params.orderId,
|
|
674
685
|
payment_method_token: params.paymentMethodToken,
|
|
675
686
|
};
|
|
676
|
-
return await this.request(API_ENDPOINTS.CREATE_PAYMENT, {
|
|
687
|
+
return (await this.request(API_ENDPOINTS.CREATE_PAYMENT, {
|
|
677
688
|
method: 'POST',
|
|
678
689
|
body: JSON.stringify(payload),
|
|
679
|
-
});
|
|
690
|
+
}));
|
|
680
691
|
}
|
|
681
692
|
async resumePayment(params) {
|
|
682
693
|
const payload = {
|
|
683
694
|
order_id: params.orderId,
|
|
684
695
|
resume_token: params.resumeToken,
|
|
685
696
|
};
|
|
686
|
-
return await this.request(API_ENDPOINTS.RESUME_PAYMENT, {
|
|
697
|
+
return (await this.request(API_ENDPOINTS.RESUME_PAYMENT, {
|
|
687
698
|
method: 'POST',
|
|
688
699
|
body: JSON.stringify(payload),
|
|
689
|
-
});
|
|
700
|
+
}));
|
|
690
701
|
}
|
|
691
702
|
processSessionResponse(response) {
|
|
692
703
|
if (response.status === 'error') {
|
|
@@ -699,7 +710,7 @@ class APIClient {
|
|
|
699
710
|
response,
|
|
700
711
|
});
|
|
701
712
|
}
|
|
702
|
-
const data = response.data
|
|
713
|
+
const data = response.data;
|
|
703
714
|
return {
|
|
704
715
|
type: 'session_created',
|
|
705
716
|
orderId: data.order_id,
|
|
@@ -713,11 +724,10 @@ class APIClient {
|
|
|
713
724
|
throw new APIError(message, null, {
|
|
714
725
|
errorCode: firstError?.code,
|
|
715
726
|
errorType: firstError?.type,
|
|
716
|
-
requestId: response.req_id,
|
|
717
727
|
response,
|
|
718
728
|
});
|
|
719
729
|
}
|
|
720
|
-
const data = response.data
|
|
730
|
+
const data = response.data;
|
|
721
731
|
if (data.action_required_token) {
|
|
722
732
|
return {
|
|
723
733
|
type: 'action_required',
|
|
@@ -732,12 +742,13 @@ class APIClient {
|
|
|
732
742
|
type: 'success',
|
|
733
743
|
orderId: data.order_id,
|
|
734
744
|
status: 'succeeded',
|
|
735
|
-
transactionId: data.transaction_id,
|
|
736
745
|
};
|
|
737
746
|
case 'failed':
|
|
738
|
-
throw new APIError(data.failed_message_for_user || 'Payment failed', null,
|
|
747
|
+
throw new APIError(data.failed_message_for_user || 'Payment failed', null, { response });
|
|
739
748
|
case 'cancelled':
|
|
740
|
-
throw new APIError('Payment was cancelled by user', null,
|
|
749
|
+
throw new APIError('Payment was cancelled by user', null, {
|
|
750
|
+
response,
|
|
751
|
+
});
|
|
741
752
|
case 'processing':
|
|
742
753
|
return {
|
|
743
754
|
type: 'processing',
|
|
@@ -745,20 +756,10 @@ class APIClient {
|
|
|
745
756
|
status: 'processing',
|
|
746
757
|
};
|
|
747
758
|
default:
|
|
748
|
-
throw new APIError(`Unhandled checkout status: ${data.checkout_status}`, null,
|
|
759
|
+
throw new APIError(`Unhandled checkout status: ${data.checkout_status}`, null, { response });
|
|
749
760
|
}
|
|
750
761
|
}
|
|
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);
|
|
762
|
+
throw new APIError('Invalid payment response format', null, { response });
|
|
762
763
|
}
|
|
763
764
|
}
|
|
764
765
|
|
|
@@ -776,13 +777,17 @@ class CheckoutInstance extends EventEmitter {
|
|
|
776
777
|
this.emit(EVENTS.METHOD_RENDER, method);
|
|
777
778
|
};
|
|
778
779
|
this.handleSubmit = (isSubmitting) => {
|
|
780
|
+
if (isSubmitting) {
|
|
781
|
+
// Clear any previous errors
|
|
782
|
+
this.emit(EVENTS.ERROR, undefined);
|
|
783
|
+
this.emit(EVENTS.START_PURCHASE, PaymentMethod.PAYMENT_CARD);
|
|
784
|
+
}
|
|
779
785
|
this.onLoaderChangeWithRace(isSubmitting);
|
|
780
|
-
// Clear any previous errors
|
|
781
|
-
this.emit(EVENTS.ERROR);
|
|
782
786
|
this._setState(isSubmitting ? 'processing' : 'ready');
|
|
783
787
|
};
|
|
784
788
|
this.handleTokenizeSuccess = async (paymentMethodTokenData, primerHandler) => {
|
|
785
789
|
try {
|
|
790
|
+
this.emit(EVENTS.START_PURCHASE, paymentMethodTokenData.paymentInstrumentType);
|
|
786
791
|
this.onLoaderChangeWithRace(true);
|
|
787
792
|
this._setState('processing');
|
|
788
793
|
const paymentResponse = await this.apiClient.createPayment({
|
|
@@ -794,7 +799,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
794
799
|
}
|
|
795
800
|
catch (error) {
|
|
796
801
|
this._setState('error');
|
|
797
|
-
this.emit(EVENTS.
|
|
802
|
+
this.emit(EVENTS.PURCHASE_FAILURE, error);
|
|
798
803
|
primerHandler.handleFailure(error.message || 'Payment processing failed');
|
|
799
804
|
}
|
|
800
805
|
finally {
|
|
@@ -815,10 +820,11 @@ class CheckoutInstance extends EventEmitter {
|
|
|
815
820
|
}
|
|
816
821
|
catch (error) {
|
|
817
822
|
this._setState('error');
|
|
818
|
-
this.emit(EVENTS.
|
|
823
|
+
this.emit(EVENTS.PURCHASE_FAILURE, error);
|
|
819
824
|
primerHandler.handleFailure(error.message || 'Payment processing failed');
|
|
820
825
|
}
|
|
821
826
|
finally {
|
|
827
|
+
this.emit(EVENTS.PURCHASE_COMPLETED);
|
|
822
828
|
this.onLoaderChangeWithRace(false);
|
|
823
829
|
this._setState('ready');
|
|
824
830
|
}
|
|
@@ -863,18 +869,6 @@ class CheckoutInstance extends EventEmitter {
|
|
|
863
869
|
this.on(EVENTS.DESTROY, this.callbacks.onDestroy);
|
|
864
870
|
}
|
|
865
871
|
}
|
|
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
872
|
removeAllListeners() {
|
|
879
873
|
return super.removeAllListeners();
|
|
880
874
|
}
|
|
@@ -916,10 +910,11 @@ class CheckoutInstance extends EventEmitter {
|
|
|
916
910
|
onSubmit: this.handleSubmit,
|
|
917
911
|
onInputChange: this.handleInputChange,
|
|
918
912
|
onMethodRender: this.handleMethodRender,
|
|
913
|
+
onSubmitError: (error) => this.emit(EVENTS.PURCHASE_FAILURE, error),
|
|
919
914
|
};
|
|
920
915
|
if (!this.checkoutConfig.cardSelectors ||
|
|
921
916
|
!this.checkoutConfig.paymentButtonSelectors) {
|
|
922
|
-
const cardSelectors = await this.createCardElements(
|
|
917
|
+
const cardSelectors = await this.createCardElements();
|
|
923
918
|
const paymentButtonSelectors = {
|
|
924
919
|
paypal: '#paypalButton',
|
|
925
920
|
googlePay: '#googlePayButton',
|
|
@@ -955,8 +950,6 @@ class CheckoutInstance extends EventEmitter {
|
|
|
955
950
|
this.emit(EVENTS.SUCCESS, {
|
|
956
951
|
orderId: result.orderId,
|
|
957
952
|
status: result.status,
|
|
958
|
-
transactionId: result.transactionId,
|
|
959
|
-
metadata: result.metadata,
|
|
960
953
|
});
|
|
961
954
|
primerHandler.handleSuccess();
|
|
962
955
|
break;
|
|
@@ -990,7 +983,6 @@ class CheckoutInstance extends EventEmitter {
|
|
|
990
983
|
});
|
|
991
984
|
this.checkoutConfig.priceId = newPriceId;
|
|
992
985
|
this._setState('ready');
|
|
993
|
-
this.emit(EVENTS.STATUS_CHANGE, 'price-updated');
|
|
994
986
|
}
|
|
995
987
|
catch (error) {
|
|
996
988
|
this._setState('error');
|
|
@@ -1050,71 +1042,21 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1050
1042
|
}
|
|
1051
1043
|
// Creates containers to render hosted inputs with labels and error messages,
|
|
1052
1044
|
// 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
|
-
};
|
|
1045
|
+
async createCardElements() {
|
|
1046
|
+
const skinFactory = (await Promise.resolve().then(function () { return require('./chunk-index.cjs.js'); }))
|
|
1047
|
+
.default;
|
|
1048
|
+
const skin = await skinFactory(this.primerWrapper, this.checkoutConfig.container);
|
|
1049
|
+
this.on(EVENTS.INPUT_ERROR, skin.onInputError);
|
|
1050
|
+
this.on(EVENTS.STATUS_CHANGE, skin.onStatusChange);
|
|
1051
|
+
this.on(EVENTS.ERROR, (error) => skin.onError(error));
|
|
1052
|
+
this.on(EVENTS.LOADER_CHANGE, skin.onLoaderChange);
|
|
1053
|
+
this.on(EVENTS.DESTROY, skin.onDestroy);
|
|
1054
|
+
this.on(EVENTS.METHOD_RENDER, skin.onMethodRender);
|
|
1055
|
+
this.on(EVENTS.SUCCESS, skin.onSuccess);
|
|
1056
|
+
this.on(EVENTS.START_PURCHASE, skin.onStartPurchase);
|
|
1057
|
+
this.on(EVENTS.PURCHASE_FAILURE, skin.onPurchaseFailure);
|
|
1058
|
+
this.on(EVENTS.PURCHASE_COMPLETED, skin.onPurchaseCompleted);
|
|
1059
|
+
return skin.getCardInputSelectors();
|
|
1118
1060
|
}
|
|
1119
1061
|
}
|
|
1120
1062
|
|