@funnelfox/billing 0.5.0-beta.2 → 0.5.0-beta.3
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 +30 -22
- package/dist/chunk-index.cjs2.js +27 -10
- package/dist/chunk-index.es.js +24 -16
- package/dist/chunk-index.es2.js +27 -10
- package/dist/funnelfox-billing.cjs.js +121 -33
- package/dist/funnelfox-billing.esm.js +121 -33
- package/dist/funnelfox-billing.js +173 -60
- package/dist/funnelfox-billing.min.js +1 -1
- package/package.json +2 -2
- package/src/types.d.ts +32 -10
|
@@ -213,7 +213,7 @@ var PaymentMethod;
|
|
|
213
213
|
/**
|
|
214
214
|
* @fileoverview Constants for Funnefox SDK
|
|
215
215
|
*/
|
|
216
|
-
const SDK_VERSION = '0.5.0-beta.
|
|
216
|
+
const SDK_VERSION = '0.5.0-beta.3';
|
|
217
217
|
const DEFAULTS = {
|
|
218
218
|
BASE_URL: 'https://billing.funnelfox.com',
|
|
219
219
|
REGION: 'default',
|
|
@@ -245,6 +245,7 @@ const EVENTS = {
|
|
|
245
245
|
PURCHASE_FAILURE: 'purchase-failure',
|
|
246
246
|
PURCHASE_COMPLETED: 'purchase-completed',
|
|
247
247
|
PURCHASE_CANCELLED: 'purchase-cancelled',
|
|
248
|
+
METHODS_AVAILABLE: 'methods-available',
|
|
248
249
|
};
|
|
249
250
|
const API_ENDPOINTS = {
|
|
250
251
|
CREATE_CLIENT_SESSION: '/v1/checkout/create_client_session',
|
|
@@ -291,6 +292,12 @@ const inputStyle = {
|
|
|
291
292
|
({
|
|
292
293
|
paddingLeft: inputStyle.input.base.paddingHorizontal + 'px',
|
|
293
294
|
paddingRight: inputStyle.input.base.paddingHorizontal + 'px'});
|
|
295
|
+
const DEFAULT_PAYMENT_METHOD_ORDER = [
|
|
296
|
+
PaymentMethod.APPLE_PAY,
|
|
297
|
+
PaymentMethod.GOOGLE_PAY,
|
|
298
|
+
PaymentMethod.PAYPAL,
|
|
299
|
+
PaymentMethod.PAYMENT_CARD,
|
|
300
|
+
];
|
|
294
301
|
|
|
295
302
|
/**
|
|
296
303
|
* @fileoverview Primer SDK integration wrapper
|
|
@@ -333,10 +340,40 @@ class PrimerWrapper {
|
|
|
333
340
|
disableButtons(disabled) {
|
|
334
341
|
if (!this.paymentMethodsInterfaces)
|
|
335
342
|
return;
|
|
336
|
-
for (const
|
|
337
|
-
|
|
343
|
+
for (const paymentMethodInterface of this.paymentMethodsInterfaces) {
|
|
344
|
+
paymentMethodInterface.setDisabled(disabled);
|
|
338
345
|
}
|
|
339
346
|
}
|
|
347
|
+
waitForPayPalReady() {
|
|
348
|
+
return new Promise((resolve, reject) => {
|
|
349
|
+
let counter = 0;
|
|
350
|
+
const checkPayPalEnabler = async () => {
|
|
351
|
+
/**
|
|
352
|
+
* Wait 1000 seconds for PayPal SDK to initialize
|
|
353
|
+
*/
|
|
354
|
+
await new Promise(resolve => {
|
|
355
|
+
setTimeout(() => {
|
|
356
|
+
resolve();
|
|
357
|
+
}, 1000);
|
|
358
|
+
});
|
|
359
|
+
/**
|
|
360
|
+
* @link https://github.com/krakenjs/zoid/issues/334
|
|
361
|
+
*/
|
|
362
|
+
// @ts-expect-error paymentMethod is private property
|
|
363
|
+
const isPayPalReady = !!window?.paypalPrimer?.Buttons?.instances?.[0];
|
|
364
|
+
if (++counter < 20 && !isPayPalReady) {
|
|
365
|
+
setTimeout(checkPayPalEnabler, 0);
|
|
366
|
+
}
|
|
367
|
+
else if (!isPayPalReady) {
|
|
368
|
+
reject(new PrimerError('PayPal paypal_js_sdk_v5_unhandled_exception was detected', PaymentMethod.PAYPAL));
|
|
369
|
+
}
|
|
370
|
+
else {
|
|
371
|
+
resolve();
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
checkPayPalEnabler();
|
|
375
|
+
});
|
|
376
|
+
}
|
|
340
377
|
async renderButton(allowedPaymentMethod, { htmlNode, onMethodRenderError, onMethodRender, }) {
|
|
341
378
|
let button;
|
|
342
379
|
this.ensurePrimerAvailable();
|
|
@@ -350,6 +387,9 @@ class PrimerWrapper {
|
|
|
350
387
|
}
|
|
351
388
|
button = pmManager.createButton();
|
|
352
389
|
await button.render(htmlNode, {});
|
|
390
|
+
if (allowedPaymentMethod === PaymentMethod.PAYPAL) {
|
|
391
|
+
await this.waitForPayPalReady();
|
|
392
|
+
}
|
|
353
393
|
this.destroyCallbacks.push(() => button.clean());
|
|
354
394
|
onMethodRender(allowedPaymentMethod);
|
|
355
395
|
return {
|
|
@@ -371,7 +411,7 @@ class PrimerWrapper {
|
|
|
371
411
|
!options.onInputChange) {
|
|
372
412
|
throw new PrimerError('Card elements, onSubmit, and onInputChange are required for PAYMENT_CARD method');
|
|
373
413
|
}
|
|
374
|
-
return this.renderCardCheckoutWithElements(options.cardElements, {
|
|
414
|
+
return await this.renderCardCheckoutWithElements(options.cardElements, {
|
|
375
415
|
onSubmit: options.onSubmit,
|
|
376
416
|
onInputChange: options.onInputChange,
|
|
377
417
|
onMethodRenderError: options.onMethodRenderError,
|
|
@@ -459,8 +499,8 @@ class PrimerWrapper {
|
|
|
459
499
|
]);
|
|
460
500
|
const onDestroy = () => {
|
|
461
501
|
pmManager.removeHostedInputs();
|
|
462
|
-
elements.cardholderName
|
|
463
|
-
elements.button
|
|
502
|
+
elements.cardholderName?.removeEventListener('change', cardHolderOnChange);
|
|
503
|
+
elements.button?.removeEventListener('click', onSubmitHandler);
|
|
464
504
|
};
|
|
465
505
|
this.destroyCallbacks.push(onDestroy);
|
|
466
506
|
onMethodRender(PaymentMethod.PAYMENT_CARD);
|
|
@@ -508,12 +548,13 @@ class PrimerWrapper {
|
|
|
508
548
|
});
|
|
509
549
|
}
|
|
510
550
|
async renderCheckout(clientToken, checkoutOptions, checkoutRenderOptions) {
|
|
511
|
-
const { cardElements, paymentButtonElements, container, onSubmit, onInputChange, onMethodRender, onMethodRenderError, } = checkoutRenderOptions;
|
|
551
|
+
const { cardElements, paymentButtonElements, container, onSubmit, onInputChange, onMethodRender, onMethodRenderError, onMethodsAvailable, } = checkoutRenderOptions;
|
|
512
552
|
await this.initializeHeadlessCheckout(clientToken, checkoutOptions);
|
|
513
|
-
|
|
553
|
+
onMethodsAvailable?.(this.availableMethods);
|
|
554
|
+
return Promise.all(this.availableMethods.map(method => {
|
|
514
555
|
if (method === PaymentMethod.PAYMENT_CARD) {
|
|
515
556
|
// For card, use the main container
|
|
516
|
-
|
|
557
|
+
return this.initMethod(method, container, {
|
|
517
558
|
cardElements,
|
|
518
559
|
onSubmit,
|
|
519
560
|
onInputChange,
|
|
@@ -529,13 +570,15 @@ class PrimerWrapper {
|
|
|
529
570
|
};
|
|
530
571
|
// For buttons, use the specific button container element
|
|
531
572
|
const buttonElement = buttonElementsMap[method];
|
|
532
|
-
|
|
573
|
+
return this.initMethod(method, buttonElement, {
|
|
533
574
|
onMethodRender,
|
|
534
575
|
onMethodRenderError,
|
|
535
576
|
});
|
|
536
577
|
}
|
|
537
|
-
}
|
|
538
|
-
|
|
578
|
+
})).then((interfaces) => {
|
|
579
|
+
this.paymentMethodsInterfaces = interfaces;
|
|
580
|
+
this.isInitialized = true;
|
|
581
|
+
});
|
|
539
582
|
}
|
|
540
583
|
wrapTokenizeHandler(handler) {
|
|
541
584
|
return async (paymentMethodTokenData, primerHandler) => {
|
|
@@ -830,12 +873,6 @@ class CheckoutInstance extends EventEmitter {
|
|
|
830
873
|
constructor(config) {
|
|
831
874
|
super();
|
|
832
875
|
this.counter = 0;
|
|
833
|
-
this.paymentMethodOrder = [
|
|
834
|
-
PaymentMethod.APPLE_PAY,
|
|
835
|
-
PaymentMethod.GOOGLE_PAY,
|
|
836
|
-
PaymentMethod.PAYPAL,
|
|
837
|
-
PaymentMethod.PAYMENT_CARD,
|
|
838
|
-
];
|
|
839
876
|
this.handleInputChange = (inputName, error) => {
|
|
840
877
|
this.emit(EVENTS.INPUT_ERROR, { name: inputName, error });
|
|
841
878
|
};
|
|
@@ -892,6 +929,9 @@ class CheckoutInstance extends EventEmitter {
|
|
|
892
929
|
this._setState('ready');
|
|
893
930
|
}
|
|
894
931
|
};
|
|
932
|
+
this.handleMethodsAvailable = (methods) => {
|
|
933
|
+
this.emit(EVENTS.METHODS_AVAILABLE, methods);
|
|
934
|
+
};
|
|
895
935
|
this.onLoaderChangeWithRace = (state) => {
|
|
896
936
|
const isLoading = !!(state ? ++this.counter : --this.counter);
|
|
897
937
|
this.emit(EVENTS.LOADER_CHANGE, isLoading);
|
|
@@ -961,14 +1001,31 @@ class CheckoutInstance extends EventEmitter {
|
|
|
961
1001
|
timeout: DEFAULTS.REQUEST_TIMEOUT,
|
|
962
1002
|
retryAttempts: DEFAULTS.RETRY_ATTEMPTS,
|
|
963
1003
|
});
|
|
964
|
-
const
|
|
1004
|
+
const sessionParams = {
|
|
965
1005
|
priceId: this.checkoutConfig.priceId,
|
|
966
1006
|
externalId: this.checkoutConfig.customer.externalId,
|
|
967
1007
|
email: this.checkoutConfig.customer.email,
|
|
968
1008
|
region: this.region || DEFAULTS.REGION,
|
|
969
1009
|
clientMetadata: this.checkoutConfig.clientMetadata,
|
|
970
1010
|
countryCode: this.checkoutConfig.customer.countryCode,
|
|
971
|
-
}
|
|
1011
|
+
};
|
|
1012
|
+
const cacheKey = [
|
|
1013
|
+
this.orgId,
|
|
1014
|
+
this.checkoutConfig.priceId,
|
|
1015
|
+
this.checkoutConfig.customer.externalId,
|
|
1016
|
+
this.checkoutConfig.customer.email,
|
|
1017
|
+
].join('-');
|
|
1018
|
+
let sessionResponse;
|
|
1019
|
+
// Return cached response if payload hasn't changed
|
|
1020
|
+
const cachedResponse = CheckoutInstance.sessionCache.get(cacheKey);
|
|
1021
|
+
if (cachedResponse) {
|
|
1022
|
+
sessionResponse = cachedResponse;
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
sessionResponse = await this.apiClient.createClientSession(sessionParams);
|
|
1026
|
+
// Cache the successful response
|
|
1027
|
+
CheckoutInstance.sessionCache.set(cacheKey, sessionResponse);
|
|
1028
|
+
}
|
|
972
1029
|
const sessionData = this.apiClient.processSessionResponse(sessionResponse);
|
|
973
1030
|
this.orderId = sessionData.orderId;
|
|
974
1031
|
this.clientToken = sessionData.clientToken;
|
|
@@ -1015,9 +1072,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1015
1072
|
let checkoutOptions;
|
|
1016
1073
|
if (!this.checkoutConfig.cardSelectors ||
|
|
1017
1074
|
!this.checkoutConfig.paymentButtonSelectors) {
|
|
1018
|
-
|
|
1019
|
-
this.paymentMethodOrder
|
|
1020
|
-
}
|
|
1075
|
+
this.checkoutConfig.paymentMethodOrder =
|
|
1076
|
+
this.checkoutConfig.paymentMethodOrder || DEFAULT_PAYMENT_METHOD_ORDER;
|
|
1021
1077
|
const defaultSkinCheckoutOptions = await this.getDefaultSkinCheckoutOptions();
|
|
1022
1078
|
if (!defaultSkinCheckoutOptions.cardElements ||
|
|
1023
1079
|
!defaultSkinCheckoutOptions.paymentButtonElements) {
|
|
@@ -1029,14 +1085,14 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1029
1085
|
checkoutOptions = this.getCheckoutOptions(defaultSkinCheckoutOptions);
|
|
1030
1086
|
}
|
|
1031
1087
|
else {
|
|
1088
|
+
if (this.checkoutConfig.paymentMethodOrder) {
|
|
1089
|
+
// eslint-disable-next-line no-console
|
|
1090
|
+
console.warn('paymentMethodOrder is using only for default skin and will be ignored if you are using custom checkout');
|
|
1091
|
+
}
|
|
1032
1092
|
cardElements = this.convertCardSelectorsToElements(this.checkoutConfig.cardSelectors, containerElement);
|
|
1033
1093
|
paymentButtonElements = this.convertPaymentButtonSelectorsToElements(this.checkoutConfig.paymentButtonSelectors);
|
|
1034
1094
|
checkoutOptions = this.getCheckoutOptions({});
|
|
1035
1095
|
}
|
|
1036
|
-
if (this.checkoutConfig.paymentMethodOrder) {
|
|
1037
|
-
// eslint-disable-next-line no-console
|
|
1038
|
-
console.warn('paymentMethodOrder is using only for default skin and will be ignored if you are using custom checkout');
|
|
1039
|
-
}
|
|
1040
1096
|
await this.primerWrapper.renderCheckout(this.clientToken, checkoutOptions, {
|
|
1041
1097
|
container: containerElement,
|
|
1042
1098
|
cardElements,
|
|
@@ -1044,6 +1100,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1044
1100
|
onSubmit: this.handleSubmit,
|
|
1045
1101
|
onInputChange: this.handleInputChange,
|
|
1046
1102
|
onMethodRender: this.handleMethodRender,
|
|
1103
|
+
onMethodsAvailable: this.handleMethodsAvailable,
|
|
1104
|
+
onMethodRenderError: this.handleMethodRenderError,
|
|
1047
1105
|
});
|
|
1048
1106
|
}
|
|
1049
1107
|
async _processPaymentResult(result, primerHandler) {
|
|
@@ -1124,6 +1182,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1124
1182
|
}
|
|
1125
1183
|
try {
|
|
1126
1184
|
this._setState('updating');
|
|
1185
|
+
// Invalidate session cache
|
|
1186
|
+
CheckoutInstance.sessionCache.clear();
|
|
1127
1187
|
await this.apiClient.updateClientSession({
|
|
1128
1188
|
orderId: this.orderId,
|
|
1129
1189
|
clientToken: this.clientToken,
|
|
@@ -1193,7 +1253,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1193
1253
|
async getDefaultSkinCheckoutOptions() {
|
|
1194
1254
|
const skinFactory = (await import('./chunk-index.es.js'))
|
|
1195
1255
|
.default;
|
|
1196
|
-
const skin = await skinFactory(this.primerWrapper, this.checkoutConfig
|
|
1256
|
+
const skin = await skinFactory(this.primerWrapper, this.checkoutConfig);
|
|
1197
1257
|
this.on(EVENTS.INPUT_ERROR, skin.onInputError);
|
|
1198
1258
|
this.on(EVENTS.STATUS_CHANGE, skin.onStatusChange);
|
|
1199
1259
|
this.on(EVENTS.ERROR, (error) => skin.onError(error));
|
|
@@ -1204,13 +1264,15 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1204
1264
|
this.on(EVENTS.START_PURCHASE, skin.onStartPurchase);
|
|
1205
1265
|
this.on(EVENTS.PURCHASE_FAILURE, skin.onPurchaseFailure);
|
|
1206
1266
|
this.on(EVENTS.PURCHASE_COMPLETED, skin.onPurchaseCompleted);
|
|
1267
|
+
this.on(EVENTS.METHODS_AVAILABLE, skin.onMethodsAvailable);
|
|
1207
1268
|
return skin.getCheckoutOptions();
|
|
1208
1269
|
}
|
|
1209
1270
|
async getCardDefaultSkinCheckoutOptions(node) {
|
|
1210
1271
|
const CardSkin = (await import('./chunk-index.es2.js')).default;
|
|
1211
|
-
const skin = new CardSkin(node);
|
|
1272
|
+
const skin = new CardSkin(node, this.checkoutConfig);
|
|
1212
1273
|
skin.init();
|
|
1213
1274
|
this.on(EVENTS.INPUT_ERROR, skin.onInputError);
|
|
1275
|
+
this.on(EVENTS.METHOD_RENDER, skin.onMethodRender);
|
|
1214
1276
|
return skin.getCheckoutOptions();
|
|
1215
1277
|
}
|
|
1216
1278
|
showInitializingLoader() {
|
|
@@ -1220,6 +1282,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1220
1282
|
hideLoader();
|
|
1221
1283
|
}
|
|
1222
1284
|
}
|
|
1285
|
+
CheckoutInstance.sessionCache = new Map();
|
|
1223
1286
|
|
|
1224
1287
|
/**
|
|
1225
1288
|
* @fileoverview Public API with configuration and orchestration logic
|
|
@@ -1275,6 +1338,28 @@ async function createClientSession(params) {
|
|
|
1275
1338
|
});
|
|
1276
1339
|
return apiClient.processSessionResponse(sessionResponse);
|
|
1277
1340
|
}
|
|
1341
|
+
async function silentPurchase(options) {
|
|
1342
|
+
const { priceId, externalId, clientMetadata, orgId, baseUrl } = options;
|
|
1343
|
+
const apiClient = new APIClient({
|
|
1344
|
+
baseUrl: baseUrl,
|
|
1345
|
+
orgId: orgId,
|
|
1346
|
+
timeout: DEFAULTS.REQUEST_TIMEOUT,
|
|
1347
|
+
retryAttempts: DEFAULTS.RETRY_ATTEMPTS,
|
|
1348
|
+
});
|
|
1349
|
+
const response = await apiClient.oneClick({
|
|
1350
|
+
pp_ident: priceId,
|
|
1351
|
+
external_id: externalId,
|
|
1352
|
+
client_metadata: clientMetadata,
|
|
1353
|
+
});
|
|
1354
|
+
if (response.status !== 'success' &&
|
|
1355
|
+
response.error.some(({ code }) => code === 'double_purchase')) {
|
|
1356
|
+
throw new APIError('This product was already purchased');
|
|
1357
|
+
}
|
|
1358
|
+
else if (response.status !== 'success') {
|
|
1359
|
+
return false;
|
|
1360
|
+
}
|
|
1361
|
+
return true;
|
|
1362
|
+
}
|
|
1278
1363
|
async function initMethod(method, element, options) {
|
|
1279
1364
|
const checkoutInstance = new CheckoutInstance({
|
|
1280
1365
|
orgId: options.orgId,
|
|
@@ -1287,6 +1372,11 @@ async function initMethod(method, element, options) {
|
|
|
1287
1372
|
},
|
|
1288
1373
|
container: '',
|
|
1289
1374
|
clientMetadata: options.meta,
|
|
1375
|
+
card: options.card,
|
|
1376
|
+
style: options.style,
|
|
1377
|
+
applePay: options.applePay,
|
|
1378
|
+
paypal: options.paypal,
|
|
1379
|
+
googlePay: options.googlePay,
|
|
1290
1380
|
},
|
|
1291
1381
|
});
|
|
1292
1382
|
checkoutInstance._ensureNotDestroyed();
|
|
@@ -1303,7 +1393,6 @@ async function initMethod(method, element, options) {
|
|
|
1303
1393
|
if (method === PaymentMethod.PAYMENT_CARD) {
|
|
1304
1394
|
const cardDefaultOptions = await checkoutInstance['getCardDefaultSkinCheckoutOptions'](element);
|
|
1305
1395
|
const checkoutOptions = checkoutInstance['getCheckoutOptions']({
|
|
1306
|
-
style: options.styles,
|
|
1307
1396
|
...cardDefaultOptions,
|
|
1308
1397
|
});
|
|
1309
1398
|
await checkoutInstance.primerWrapper.initializeHeadlessCheckout(checkoutInstance.clientToken, checkoutOptions);
|
|
@@ -1315,9 +1404,7 @@ async function initMethod(method, element, options) {
|
|
|
1315
1404
|
onMethodRenderError: checkoutInstance['handleMethodRenderError'],
|
|
1316
1405
|
});
|
|
1317
1406
|
}
|
|
1318
|
-
await checkoutInstance.primerWrapper.initializeHeadlessCheckout(checkoutInstance.clientToken, checkoutInstance['getCheckoutOptions']({
|
|
1319
|
-
style: options.styles,
|
|
1320
|
-
}));
|
|
1407
|
+
await checkoutInstance.primerWrapper.initializeHeadlessCheckout(checkoutInstance.clientToken, checkoutInstance['getCheckoutOptions']({}));
|
|
1321
1408
|
return checkoutInstance.primerWrapper.initMethod(method, element, {
|
|
1322
1409
|
onMethodRender: checkoutInstance['handleMethodRender'],
|
|
1323
1410
|
onMethodRenderError: checkoutInstance['handleMethodRenderError'],
|
|
@@ -1332,6 +1419,7 @@ const Billing = {
|
|
|
1332
1419
|
createCheckout: createCheckout,
|
|
1333
1420
|
createClientSession: createClientSession,
|
|
1334
1421
|
initMethod: initMethod,
|
|
1422
|
+
silentPurchase: silentPurchase,
|
|
1335
1423
|
};
|
|
1336
1424
|
if (typeof window !== 'undefined') {
|
|
1337
1425
|
window.Billing = Billing;
|