@funnelfox/billing 0.5.3 → 0.5.4
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/funnelfox-billing.cjs.js +207 -115
- package/dist/funnelfox-billing.esm.js +207 -115
- package/dist/funnelfox-billing.js +207 -115
- package/dist/funnelfox-billing.min.js +1 -1
- package/package.json +2 -2
- package/src/types.d.ts +13 -9
|
@@ -153,61 +153,6 @@
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
157
|
-
/**
|
|
158
|
-
* @fileoverview Helper utilities for Funnefox SDK
|
|
159
|
-
*/
|
|
160
|
-
function merge(...objects) {
|
|
161
|
-
const result = {};
|
|
162
|
-
for (const obj of objects) {
|
|
163
|
-
if (obj && typeof obj === 'object') {
|
|
164
|
-
for (const key in obj) {
|
|
165
|
-
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
166
|
-
if (typeof obj[key] === 'object' &&
|
|
167
|
-
!Array.isArray(obj[key]) &&
|
|
168
|
-
obj[key] !== null) {
|
|
169
|
-
result[key] = merge(result[key] || {}, obj[key]);
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
result[key] = obj[key];
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
return result;
|
|
179
|
-
}
|
|
180
|
-
function generateId(prefix = '') {
|
|
181
|
-
const timestamp = Date.now().toString(36);
|
|
182
|
-
const random = Math.random().toString(36).substr(2, 5);
|
|
183
|
-
return `${prefix}${timestamp}_${random}`;
|
|
184
|
-
}
|
|
185
|
-
function sleep(ms) {
|
|
186
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
187
|
-
}
|
|
188
|
-
async function retry(fn, maxAttempts = 3, baseDelay = 1000) {
|
|
189
|
-
let lastError;
|
|
190
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
191
|
-
try {
|
|
192
|
-
return await fn();
|
|
193
|
-
}
|
|
194
|
-
catch (error) {
|
|
195
|
-
lastError = error;
|
|
196
|
-
if (attempt === maxAttempts)
|
|
197
|
-
throw lastError;
|
|
198
|
-
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
199
|
-
await sleep(delay);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
throw lastError;
|
|
203
|
-
}
|
|
204
|
-
function withTimeout(promise, timeoutMs, message = 'Operation timed out') {
|
|
205
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
206
|
-
setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
207
|
-
});
|
|
208
|
-
return Promise.race([promise, timeoutPromise]);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
156
|
/**
|
|
212
157
|
* @fileoverview Dynamic loader for Primer SDK
|
|
213
158
|
* Loads Primer script and CSS from CDN independently of bundler
|
|
@@ -334,6 +279,142 @@
|
|
|
334
279
|
return loadingPromise;
|
|
335
280
|
}
|
|
336
281
|
|
|
282
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
283
|
+
/**
|
|
284
|
+
* @fileoverview Helper utilities for Funnefox SDK
|
|
285
|
+
*/
|
|
286
|
+
function merge(...objects) {
|
|
287
|
+
const result = {};
|
|
288
|
+
for (const obj of objects) {
|
|
289
|
+
if (obj && typeof obj === 'object') {
|
|
290
|
+
for (const key in obj) {
|
|
291
|
+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
292
|
+
if (typeof obj[key] === 'object' &&
|
|
293
|
+
!Array.isArray(obj[key]) &&
|
|
294
|
+
obj[key] !== null) {
|
|
295
|
+
result[key] = merge(result[key] || {}, obj[key]);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
result[key] = obj[key];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
function generateId(prefix = '') {
|
|
307
|
+
const timestamp = Date.now().toString(36);
|
|
308
|
+
const random = Math.random().toString(36).substr(2, 5);
|
|
309
|
+
return `${prefix}${timestamp}_${random}`;
|
|
310
|
+
}
|
|
311
|
+
function sleep(ms) {
|
|
312
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
313
|
+
}
|
|
314
|
+
async function retry(fn, maxAttempts = 3, baseDelay = 1000) {
|
|
315
|
+
let lastError;
|
|
316
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
317
|
+
try {
|
|
318
|
+
return await fn();
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
lastError = error;
|
|
322
|
+
if (attempt === maxAttempts)
|
|
323
|
+
throw lastError;
|
|
324
|
+
const delay = baseDelay * Math.pow(2, attempt - 1);
|
|
325
|
+
await sleep(delay);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
throw lastError;
|
|
329
|
+
}
|
|
330
|
+
function withTimeout(promise, timeoutMs, message = 'Operation timed out') {
|
|
331
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
332
|
+
setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
333
|
+
});
|
|
334
|
+
return Promise.race([promise, timeoutPromise]);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @fileoverview Headless checkout cache manager
|
|
339
|
+
*/
|
|
340
|
+
/**
|
|
341
|
+
* Manages caching and sequential creation of Primer headless checkout instances.
|
|
342
|
+
* Ensures that multiple checkouts with the same configuration reuse the same instance,
|
|
343
|
+
* and that creations happen sequentially to avoid race conditions.
|
|
344
|
+
*/
|
|
345
|
+
class HeadlessManager {
|
|
346
|
+
constructor() {
|
|
347
|
+
this.cache = new Map();
|
|
348
|
+
this.queue = Promise.resolve();
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Generates a cache key from clientToken and serializable options
|
|
352
|
+
*/
|
|
353
|
+
generateKey(clientToken, options) {
|
|
354
|
+
const serializableOptions = {
|
|
355
|
+
paymentHandling: options.paymentHandling,
|
|
356
|
+
apiVersion: options.apiVersion,
|
|
357
|
+
style: options.style,
|
|
358
|
+
card: options.card,
|
|
359
|
+
applePay: options.applePay,
|
|
360
|
+
paypal: options.paypal,
|
|
361
|
+
googlePay: options.googlePay,
|
|
362
|
+
};
|
|
363
|
+
return `${clientToken}:${JSON.stringify(serializableOptions)}`;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Gets a cached headless instance or creates a new one.
|
|
367
|
+
* Ensures sequential creation order to avoid race conditions.
|
|
368
|
+
*/
|
|
369
|
+
getOrCreate(clientToken, options) {
|
|
370
|
+
const key = this.generateKey(clientToken, options);
|
|
371
|
+
// Return cached promise if exists
|
|
372
|
+
const cached = this.cache.get(key);
|
|
373
|
+
if (cached)
|
|
374
|
+
return cached;
|
|
375
|
+
// Create new headless in sequential order
|
|
376
|
+
const previousQueue = this.queue;
|
|
377
|
+
const promise = (async () => {
|
|
378
|
+
await previousQueue; // Wait for previous creation
|
|
379
|
+
const primerOptions = merge({
|
|
380
|
+
paymentHandling: 'MANUAL',
|
|
381
|
+
apiVersion: '2.4',
|
|
382
|
+
}, options);
|
|
383
|
+
try {
|
|
384
|
+
const headlessResult = await window.Primer.createHeadless(clientToken, primerOptions);
|
|
385
|
+
const headless = await headlessResult;
|
|
386
|
+
await headless.start();
|
|
387
|
+
return headless;
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
// Remove from cache on failure
|
|
391
|
+
this.cache.delete(key);
|
|
392
|
+
throw new PrimerError('Failed to create Primer headless checkout', error);
|
|
393
|
+
}
|
|
394
|
+
})();
|
|
395
|
+
this.cache.set(key, promise);
|
|
396
|
+
this.queue = promise.catch(() => { }); // Update queue, ignore errors
|
|
397
|
+
return promise;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Removes a headless instance from the cache
|
|
401
|
+
*/
|
|
402
|
+
remove(headlessPromise) {
|
|
403
|
+
for (const [key, value] of this.cache.entries()) {
|
|
404
|
+
if (value === headlessPromise) {
|
|
405
|
+
this.cache.delete(key);
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Clears all cached instances
|
|
412
|
+
*/
|
|
413
|
+
clear() {
|
|
414
|
+
this.cache.clear();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
337
418
|
exports.PaymentMethod = void 0;
|
|
338
419
|
(function (PaymentMethod) {
|
|
339
420
|
PaymentMethod["GOOGLE_PAY"] = "GOOGLE_PAY";
|
|
@@ -345,7 +426,7 @@
|
|
|
345
426
|
/**
|
|
346
427
|
* @fileoverview Constants for Funnefox SDK
|
|
347
428
|
*/
|
|
348
|
-
const SDK_VERSION = '0.5.
|
|
429
|
+
const SDK_VERSION = '0.5.4';
|
|
349
430
|
const DEFAULTS = {
|
|
350
431
|
BASE_URL: 'https://billing.funnelfox.com',
|
|
351
432
|
REGION: 'default',
|
|
@@ -438,6 +519,7 @@
|
|
|
438
519
|
constructor() {
|
|
439
520
|
this.isInitialized = false;
|
|
440
521
|
this.destroyCallbacks = [];
|
|
522
|
+
this.currentHeadless = null;
|
|
441
523
|
this.availableMethods = [];
|
|
442
524
|
this.paymentMethodsInterfaces = [];
|
|
443
525
|
}
|
|
@@ -467,26 +549,9 @@
|
|
|
467
549
|
}
|
|
468
550
|
}
|
|
469
551
|
async createHeadlessCheckout(clientToken, options) {
|
|
470
|
-
if (PrimerWrapper.headless) {
|
|
471
|
-
return PrimerWrapper.headless;
|
|
472
|
-
}
|
|
473
|
-
// Load Primer SDK if not already available
|
|
474
552
|
await this.ensurePrimerLoaded();
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
apiVersion: '2.4',
|
|
478
|
-
}, options);
|
|
479
|
-
try {
|
|
480
|
-
PrimerWrapper.headless = window.Primer.createHeadless(clientToken, primerOptions).then(async (headlessPromise) => {
|
|
481
|
-
const headless = await headlessPromise;
|
|
482
|
-
await headless.start();
|
|
483
|
-
return headless;
|
|
484
|
-
});
|
|
485
|
-
return await PrimerWrapper.headless;
|
|
486
|
-
}
|
|
487
|
-
catch (error) {
|
|
488
|
-
throw new PrimerError('Failed to create Primer headless checkout', error);
|
|
489
|
-
}
|
|
553
|
+
this.currentHeadless = PrimerWrapper.headlessManager.getOrCreate(clientToken, options);
|
|
554
|
+
return this.currentHeadless;
|
|
490
555
|
}
|
|
491
556
|
disableButtons(disabled) {
|
|
492
557
|
if (!this.paymentMethodsInterfaces)
|
|
@@ -499,11 +564,11 @@
|
|
|
499
564
|
let button;
|
|
500
565
|
// Ensure Primer SDK is loaded
|
|
501
566
|
await this.ensurePrimerLoaded();
|
|
502
|
-
if (!
|
|
567
|
+
if (!this.currentHeadless) {
|
|
503
568
|
throw new PrimerError('Headless checkout not found');
|
|
504
569
|
}
|
|
505
570
|
try {
|
|
506
|
-
const headless = await
|
|
571
|
+
const headless = await this.currentHeadless;
|
|
507
572
|
const pmManager = await headless.createPaymentMethodManager(allowedPaymentMethod);
|
|
508
573
|
if (!pmManager) {
|
|
509
574
|
throw new Error('Payment method manager is not available');
|
|
@@ -557,7 +622,10 @@
|
|
|
557
622
|
}
|
|
558
623
|
async renderCardCheckoutWithElements(elements, { onSubmit, onInputChange, onMethodRenderError, onMethodRender, }) {
|
|
559
624
|
try {
|
|
560
|
-
|
|
625
|
+
if (!this.currentHeadless) {
|
|
626
|
+
throw new PrimerError('Headless checkout not found');
|
|
627
|
+
}
|
|
628
|
+
const headless = await this.currentHeadless;
|
|
561
629
|
const pmManager = await headless.createPaymentMethodManager('PAYMENT_CARD');
|
|
562
630
|
if (!pmManager) {
|
|
563
631
|
throw new Error('Payment method manager is not available');
|
|
@@ -733,7 +801,10 @@
|
|
|
733
801
|
};
|
|
734
802
|
}
|
|
735
803
|
async destroy() {
|
|
736
|
-
|
|
804
|
+
if (this.currentHeadless) {
|
|
805
|
+
PrimerWrapper.headlessManager.remove(this.currentHeadless);
|
|
806
|
+
this.currentHeadless = null;
|
|
807
|
+
}
|
|
737
808
|
if (this.destroyCallbacks) {
|
|
738
809
|
try {
|
|
739
810
|
Promise.all(this.destroyCallbacks.map(destroy => destroy()));
|
|
@@ -781,7 +852,7 @@
|
|
|
781
852
|
return element;
|
|
782
853
|
}
|
|
783
854
|
}
|
|
784
|
-
PrimerWrapper.
|
|
855
|
+
PrimerWrapper.headlessManager = new HeadlessManager();
|
|
785
856
|
|
|
786
857
|
/**
|
|
787
858
|
* @fileoverview Input validation utilities for Funnefox SDK
|
|
@@ -1148,9 +1219,9 @@
|
|
|
1148
1219
|
].join('-');
|
|
1149
1220
|
let sessionResponse;
|
|
1150
1221
|
// Return cached response if payload hasn't changed
|
|
1151
|
-
const cachedResponse =
|
|
1222
|
+
const cachedResponse = CheckoutInstance.sessionCache.get(cacheKey);
|
|
1152
1223
|
if (cachedResponse) {
|
|
1153
|
-
sessionResponse = cachedResponse;
|
|
1224
|
+
sessionResponse = await cachedResponse;
|
|
1154
1225
|
}
|
|
1155
1226
|
else {
|
|
1156
1227
|
const sessionRequest = this.apiClient.createClientSession(sessionParams);
|
|
@@ -1287,6 +1358,7 @@
|
|
|
1287
1358
|
this.emit(EVENTS.PURCHASE_FAILURE, error);
|
|
1288
1359
|
},
|
|
1289
1360
|
onTokenizeShouldStart: data => {
|
|
1361
|
+
wasPaymentProcessedStarted = true;
|
|
1290
1362
|
this.emit(EVENTS.ERROR, undefined);
|
|
1291
1363
|
this.emit(EVENTS.START_PURCHASE, data.paymentMethodType);
|
|
1292
1364
|
return true;
|
|
@@ -1407,6 +1479,7 @@
|
|
|
1407
1479
|
this.on(EVENTS.INPUT_ERROR, skin.onInputError);
|
|
1408
1480
|
this.on(EVENTS.METHOD_RENDER, skin.onMethodRender);
|
|
1409
1481
|
this.on(EVENTS.SUCCESS, skin.onDestroy);
|
|
1482
|
+
this.on(EVENTS.DESTROY, skin.onDestroy);
|
|
1410
1483
|
return skin.getCheckoutOptions();
|
|
1411
1484
|
}
|
|
1412
1485
|
showInitializingLoader() {
|
|
@@ -1415,6 +1488,48 @@
|
|
|
1415
1488
|
hideInitializingLoader() {
|
|
1416
1489
|
hideLoader();
|
|
1417
1490
|
}
|
|
1491
|
+
async initMethod(method, element, callbacks) {
|
|
1492
|
+
this._ensureNotDestroyed();
|
|
1493
|
+
if (!this.isReady()) {
|
|
1494
|
+
await this.createSession();
|
|
1495
|
+
}
|
|
1496
|
+
this.on(EVENTS.METHOD_RENDER, callbacks.onRenderSuccess);
|
|
1497
|
+
this.on(EVENTS.METHOD_RENDER_ERROR, callbacks.onRenderError);
|
|
1498
|
+
this.on(EVENTS.LOADER_CHANGE, callbacks.onLoaderChange);
|
|
1499
|
+
this.on(EVENTS.SUCCESS, callbacks.onPaymentSuccess);
|
|
1500
|
+
this.on(EVENTS.PURCHASE_FAILURE, callbacks.onPaymentFail);
|
|
1501
|
+
this.on(EVENTS.PURCHASE_CANCELLED, callbacks.onPaymentCancel);
|
|
1502
|
+
this.on(EVENTS.ERROR, callbacks.onErrorMessageChange);
|
|
1503
|
+
this.on(EVENTS.START_PURCHASE, callbacks.onPaymentStarted);
|
|
1504
|
+
this.on(EVENTS.METHODS_AVAILABLE, callbacks.onMethodsAvailable);
|
|
1505
|
+
let checkoutOptions = this.getCheckoutOptions({});
|
|
1506
|
+
let methodOptions = {
|
|
1507
|
+
onMethodRender: this.handleMethodRender,
|
|
1508
|
+
onMethodRenderError: this.handleMethodRenderError,
|
|
1509
|
+
};
|
|
1510
|
+
if (method === exports.PaymentMethod.PAYMENT_CARD) {
|
|
1511
|
+
const cardDefaultOptions = await this.getCardDefaultSkinCheckoutOptions(element);
|
|
1512
|
+
checkoutOptions = this.getCheckoutOptions({
|
|
1513
|
+
...cardDefaultOptions,
|
|
1514
|
+
});
|
|
1515
|
+
methodOptions = {
|
|
1516
|
+
cardElements: cardDefaultOptions.cardElements,
|
|
1517
|
+
onSubmit: this.handleSubmit,
|
|
1518
|
+
onInputChange: this.handleInputChange,
|
|
1519
|
+
onMethodRender: this.handleMethodRender,
|
|
1520
|
+
onMethodRenderError: this.handleMethodRenderError,
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
await this.primerWrapper.initializeHeadlessCheckout(this.clientToken, checkoutOptions);
|
|
1524
|
+
const methodInterface = await this.primerWrapper.initMethod(method, element, methodOptions);
|
|
1525
|
+
return {
|
|
1526
|
+
...methodInterface,
|
|
1527
|
+
destroy: async () => {
|
|
1528
|
+
await methodInterface.destroy();
|
|
1529
|
+
await this.destroy();
|
|
1530
|
+
},
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1418
1533
|
}
|
|
1419
1534
|
CheckoutInstance.sessionCache = new Map();
|
|
1420
1535
|
|
|
@@ -1496,9 +1611,6 @@
|
|
|
1496
1611
|
return true;
|
|
1497
1612
|
}
|
|
1498
1613
|
async function initMethod(method, element, options) {
|
|
1499
|
-
// Ensure Primer SDK is loaded before initializing payment method
|
|
1500
|
-
const primerWrapper = new PrimerWrapper();
|
|
1501
|
-
await primerWrapper.ensurePrimerLoaded();
|
|
1502
1614
|
const checkoutInstance = new CheckoutInstance({
|
|
1503
1615
|
orgId: options.orgId,
|
|
1504
1616
|
baseUrl: options.baseUrl,
|
|
@@ -1517,36 +1629,16 @@
|
|
|
1517
1629
|
googlePay: options.googlePay,
|
|
1518
1630
|
},
|
|
1519
1631
|
});
|
|
1520
|
-
checkoutInstance.
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
checkoutInstance.on(EVENTS.ERROR, options.onErrorMessageChange);
|
|
1531
|
-
checkoutInstance.on(EVENTS.START_PURCHASE, options.onPaymentStarted);
|
|
1532
|
-
if (method === exports.PaymentMethod.PAYMENT_CARD) {
|
|
1533
|
-
const cardDefaultOptions = await checkoutInstance['getCardDefaultSkinCheckoutOptions'](element);
|
|
1534
|
-
const checkoutOptions = checkoutInstance['getCheckoutOptions']({
|
|
1535
|
-
...cardDefaultOptions,
|
|
1536
|
-
});
|
|
1537
|
-
await checkoutInstance.primerWrapper.initializeHeadlessCheckout(checkoutInstance.clientToken, checkoutOptions);
|
|
1538
|
-
return checkoutInstance.primerWrapper.initMethod(method, element, {
|
|
1539
|
-
cardElements: cardDefaultOptions.cardElements,
|
|
1540
|
-
onSubmit: checkoutInstance['handleSubmit'],
|
|
1541
|
-
onInputChange: checkoutInstance['handleInputChange'],
|
|
1542
|
-
onMethodRender: checkoutInstance['handleMethodRender'],
|
|
1543
|
-
onMethodRenderError: checkoutInstance['handleMethodRenderError'],
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
await checkoutInstance.primerWrapper.initializeHeadlessCheckout(checkoutInstance.clientToken, checkoutInstance['getCheckoutOptions']({}));
|
|
1547
|
-
return checkoutInstance.primerWrapper.initMethod(method, element, {
|
|
1548
|
-
onMethodRender: checkoutInstance['handleMethodRender'],
|
|
1549
|
-
onMethodRenderError: checkoutInstance['handleMethodRenderError'],
|
|
1632
|
+
return checkoutInstance.initMethod(method, element, {
|
|
1633
|
+
onRenderSuccess: options.onRenderSuccess,
|
|
1634
|
+
onRenderError: options.onRenderError,
|
|
1635
|
+
onLoaderChange: options.onLoaderChange,
|
|
1636
|
+
onPaymentSuccess: options.onPaymentSuccess,
|
|
1637
|
+
onPaymentFail: options.onPaymentFail,
|
|
1638
|
+
onPaymentCancel: options.onPaymentCancel,
|
|
1639
|
+
onErrorMessageChange: options.onErrorMessageChange,
|
|
1640
|
+
onPaymentStarted: options.onPaymentStarted,
|
|
1641
|
+
onMethodsAvailable: options.onMethodsAvailable,
|
|
1550
1642
|
});
|
|
1551
1643
|
}
|
|
1552
1644
|
|