@funnelfox/billing 0.5.0-beta.4 → 0.5.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 +1 -0
- package/dist/chunk-index.es.js +1 -0
- package/dist/funnelfox-billing.cjs.js +185 -66
- package/dist/funnelfox-billing.esm.js +185 -66
- package/dist/funnelfox-billing.js +186 -66
- package/dist/funnelfox-billing.min.js +1 -1
- package/package.json +3 -5
|
@@ -208,6 +208,132 @@
|
|
|
208
208
|
return Promise.race([promise, timeoutPromise]);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/**
|
|
212
|
+
* @fileoverview Dynamic loader for Primer SDK
|
|
213
|
+
* Loads Primer script and CSS from CDN independently of bundler
|
|
214
|
+
*/
|
|
215
|
+
const PRIMER_CDN_BASE = 'https://sdk.primer.io/web';
|
|
216
|
+
const DEFAULT_VERSION = '2.57.3';
|
|
217
|
+
// Integrity hashes for specific versions (for SRI security)
|
|
218
|
+
const INTEGRITY_HASHES = {
|
|
219
|
+
'2.57.3': {
|
|
220
|
+
js: 'sha384-xq2SWkYvTlKOMpuXQUXq1QI3eZN7JiqQ3Sc72U9wY1IE30MW3HkwQWg/1n6BTMz4',
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
let loadingPromise = null;
|
|
224
|
+
let isLoaded = false;
|
|
225
|
+
/**
|
|
226
|
+
* Injects a script tag into the document head
|
|
227
|
+
*/
|
|
228
|
+
function injectScript(src, integrity) {
|
|
229
|
+
return new Promise((resolve, reject) => {
|
|
230
|
+
// Check if script already exists
|
|
231
|
+
const existingScript = document.querySelector(`script[src="${src}"]`);
|
|
232
|
+
if (existingScript) {
|
|
233
|
+
resolve(existingScript);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const script = document.createElement('script');
|
|
237
|
+
script.src = src;
|
|
238
|
+
script.async = true;
|
|
239
|
+
script.crossOrigin = 'anonymous';
|
|
240
|
+
if (integrity) {
|
|
241
|
+
script.integrity = integrity;
|
|
242
|
+
}
|
|
243
|
+
script.onload = () => resolve(script);
|
|
244
|
+
script.onerror = () => reject(new Error(`Failed to load Primer SDK script from ${src}`));
|
|
245
|
+
document.head.appendChild(script);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Injects a CSS link tag into the document head
|
|
250
|
+
*/
|
|
251
|
+
function injectCSS(href, integrity) {
|
|
252
|
+
return new Promise((resolve, reject) => {
|
|
253
|
+
// Check if stylesheet already exists
|
|
254
|
+
const existingLink = document.querySelector(`link[href="${href}"]`);
|
|
255
|
+
if (existingLink) {
|
|
256
|
+
resolve(existingLink);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
const link = document.createElement('link');
|
|
260
|
+
link.rel = 'stylesheet';
|
|
261
|
+
link.href = href;
|
|
262
|
+
link.crossOrigin = 'anonymous';
|
|
263
|
+
if (integrity) {
|
|
264
|
+
link.integrity = integrity;
|
|
265
|
+
}
|
|
266
|
+
link.onload = () => resolve(link);
|
|
267
|
+
link.onerror = () => reject(new Error(`Failed to load Primer SDK CSS from ${href}`));
|
|
268
|
+
document.head.appendChild(link);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Waits for window.Primer to be available
|
|
273
|
+
*/
|
|
274
|
+
function waitForPrimer(timeout = 10000) {
|
|
275
|
+
return new Promise((resolve, reject) => {
|
|
276
|
+
const startTime = Date.now();
|
|
277
|
+
const check = () => {
|
|
278
|
+
if (typeof window !== 'undefined' &&
|
|
279
|
+
window.Primer &&
|
|
280
|
+
typeof window.Primer.createHeadless === 'function') {
|
|
281
|
+
resolve();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (Date.now() - startTime > timeout) {
|
|
285
|
+
reject(new Error('Timeout waiting for Primer SDK to initialize on window'));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
setTimeout(check, 50);
|
|
289
|
+
};
|
|
290
|
+
check();
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Loads the Primer SDK script and CSS from CDN
|
|
295
|
+
* @param version - The version of Primer SDK to load (default: 2.57.3)
|
|
296
|
+
* @returns Promise that resolves when SDK is loaded and ready
|
|
297
|
+
*/
|
|
298
|
+
async function loadPrimerSDK(version) {
|
|
299
|
+
// Already loaded
|
|
300
|
+
if (isLoaded) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
// Already loading - return existing promise
|
|
304
|
+
if (loadingPromise) {
|
|
305
|
+
return loadingPromise;
|
|
306
|
+
}
|
|
307
|
+
// Check if Primer is already available (user may have loaded it manually)
|
|
308
|
+
if (typeof window !== 'undefined' &&
|
|
309
|
+
window.Primer &&
|
|
310
|
+
typeof window.Primer.createHeadless === 'function') {
|
|
311
|
+
isLoaded = true;
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const ver = version || DEFAULT_VERSION;
|
|
315
|
+
const jsUrl = `${PRIMER_CDN_BASE}/v${ver}/Primer.min.js`;
|
|
316
|
+
const cssUrl = `${PRIMER_CDN_BASE}/v${ver}/Checkout.css`;
|
|
317
|
+
const hashes = INTEGRITY_HASHES[ver];
|
|
318
|
+
loadingPromise = (async () => {
|
|
319
|
+
try {
|
|
320
|
+
// Load CSS and JS in parallel
|
|
321
|
+
await Promise.all([
|
|
322
|
+
injectCSS(cssUrl, hashes?.css),
|
|
323
|
+
injectScript(jsUrl, hashes?.js),
|
|
324
|
+
]);
|
|
325
|
+
// Wait for Primer to be available on window
|
|
326
|
+
await waitForPrimer();
|
|
327
|
+
isLoaded = true;
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
loadingPromise = null;
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
})();
|
|
334
|
+
return loadingPromise;
|
|
335
|
+
}
|
|
336
|
+
|
|
211
337
|
exports.PaymentMethod = void 0;
|
|
212
338
|
(function (PaymentMethod) {
|
|
213
339
|
PaymentMethod["GOOGLE_PAY"] = "GOOGLE_PAY";
|
|
@@ -219,7 +345,7 @@
|
|
|
219
345
|
/**
|
|
220
346
|
* @fileoverview Constants for Funnefox SDK
|
|
221
347
|
*/
|
|
222
|
-
const SDK_VERSION = '0.5.
|
|
348
|
+
const SDK_VERSION = '0.5.1';
|
|
223
349
|
const DEFAULTS = {
|
|
224
350
|
BASE_URL: 'https://billing.funnelfox.com',
|
|
225
351
|
REGION: 'default',
|
|
@@ -312,7 +438,6 @@
|
|
|
312
438
|
constructor() {
|
|
313
439
|
this.isInitialized = false;
|
|
314
440
|
this.destroyCallbacks = [];
|
|
315
|
-
this.headless = null;
|
|
316
441
|
this.availableMethods = [];
|
|
317
442
|
this.paymentMethodsInterfaces = [];
|
|
318
443
|
}
|
|
@@ -321,24 +446,43 @@
|
|
|
321
446
|
window.Primer &&
|
|
322
447
|
typeof window.Primer?.createHeadless === 'function');
|
|
323
448
|
}
|
|
449
|
+
/**
|
|
450
|
+
* Loads Primer SDK if not already available
|
|
451
|
+
* @param version - Optional version to load (uses default if not specified)
|
|
452
|
+
*/
|
|
453
|
+
async ensurePrimerLoaded(version) {
|
|
454
|
+
if (this.isPrimerAvailable()) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
try {
|
|
458
|
+
await loadPrimerSDK(version);
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
throw new PrimerError('Failed to load Primer SDK', error);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
324
464
|
ensurePrimerAvailable() {
|
|
325
465
|
if (!this.isPrimerAvailable()) {
|
|
326
466
|
throw new PrimerError('Primer SDK not found. Please include the Primer SDK script before initializing FunnefoxSDK.');
|
|
327
467
|
}
|
|
328
468
|
}
|
|
329
469
|
async createHeadlessCheckout(clientToken, options) {
|
|
330
|
-
if (
|
|
331
|
-
return
|
|
470
|
+
if (PrimerWrapper.headless) {
|
|
471
|
+
return PrimerWrapper.headless;
|
|
332
472
|
}
|
|
333
|
-
|
|
473
|
+
// Load Primer SDK if not already available
|
|
474
|
+
await this.ensurePrimerLoaded();
|
|
334
475
|
const primerOptions = merge({
|
|
335
476
|
paymentHandling: 'MANUAL',
|
|
336
477
|
apiVersion: '2.4',
|
|
337
478
|
}, options);
|
|
338
479
|
try {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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;
|
|
342
486
|
}
|
|
343
487
|
catch (error) {
|
|
344
488
|
throw new PrimerError('Failed to create Primer headless checkout', error);
|
|
@@ -351,52 +495,21 @@
|
|
|
351
495
|
paymentMethodInterface.setDisabled(disabled);
|
|
352
496
|
}
|
|
353
497
|
}
|
|
354
|
-
waitForPayPalReady() {
|
|
355
|
-
return new Promise((resolve, reject) => {
|
|
356
|
-
let counter = 0;
|
|
357
|
-
const checkPayPalEnabler = async () => {
|
|
358
|
-
/**
|
|
359
|
-
* Wait 1000 seconds for PayPal SDK to initialize
|
|
360
|
-
*/
|
|
361
|
-
await new Promise(resolve => {
|
|
362
|
-
setTimeout(() => {
|
|
363
|
-
resolve();
|
|
364
|
-
}, 1000);
|
|
365
|
-
});
|
|
366
|
-
/**
|
|
367
|
-
* @link https://github.com/krakenjs/zoid/issues/334
|
|
368
|
-
*/
|
|
369
|
-
// @ts-expect-error paymentMethod is private property
|
|
370
|
-
const isPayPalReady = !!window?.paypalPrimer?.Buttons?.instances?.[0];
|
|
371
|
-
if (++counter < 20 && !isPayPalReady) {
|
|
372
|
-
setTimeout(checkPayPalEnabler, 0);
|
|
373
|
-
}
|
|
374
|
-
else if (!isPayPalReady) {
|
|
375
|
-
reject(new PrimerError('PayPal paypal_js_sdk_v5_unhandled_exception was detected', exports.PaymentMethod.PAYPAL));
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
resolve();
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
checkPayPalEnabler();
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
498
|
async renderButton(allowedPaymentMethod, { htmlNode, onMethodRenderError, onMethodRender, }) {
|
|
385
499
|
let button;
|
|
386
|
-
|
|
387
|
-
|
|
500
|
+
// Ensure Primer SDK is loaded
|
|
501
|
+
await this.ensurePrimerLoaded();
|
|
502
|
+
if (!PrimerWrapper.headless) {
|
|
388
503
|
throw new PrimerError('Headless checkout not found');
|
|
389
504
|
}
|
|
390
505
|
try {
|
|
391
|
-
const
|
|
506
|
+
const headless = await PrimerWrapper.headless;
|
|
507
|
+
const pmManager = await headless.createPaymentMethodManager(allowedPaymentMethod);
|
|
392
508
|
if (!pmManager) {
|
|
393
509
|
throw new Error('Payment method manager is not available');
|
|
394
510
|
}
|
|
395
511
|
button = pmManager.createButton();
|
|
396
512
|
await button.render(htmlNode, {});
|
|
397
|
-
if (allowedPaymentMethod === exports.PaymentMethod.PAYPAL) {
|
|
398
|
-
await this.waitForPayPalReady();
|
|
399
|
-
}
|
|
400
513
|
this.destroyCallbacks.push(() => button.clean());
|
|
401
514
|
onMethodRender(allowedPaymentMethod);
|
|
402
515
|
return {
|
|
@@ -412,23 +525,23 @@
|
|
|
412
525
|
}
|
|
413
526
|
}
|
|
414
527
|
async initMethod(method, htmlNode, options) {
|
|
415
|
-
|
|
416
|
-
if (
|
|
417
|
-
!options.
|
|
418
|
-
|
|
419
|
-
|
|
528
|
+
try {
|
|
529
|
+
if (method === exports.PaymentMethod.PAYMENT_CARD) {
|
|
530
|
+
if (!options.cardElements ||
|
|
531
|
+
!options.onSubmit ||
|
|
532
|
+
!options.onInputChange) {
|
|
533
|
+
throw new PrimerError('Card elements, onSubmit, and onInputChange are required for PAYMENT_CARD method');
|
|
534
|
+
}
|
|
535
|
+
const cardInterface = await this.renderCardCheckoutWithElements(options.cardElements, {
|
|
536
|
+
onSubmit: options.onSubmit,
|
|
537
|
+
onInputChange: options.onInputChange,
|
|
538
|
+
onMethodRenderError: options.onMethodRenderError,
|
|
539
|
+
onMethodRender: options.onMethodRender,
|
|
540
|
+
});
|
|
541
|
+
this.paymentMethodsInterfaces.push(cardInterface);
|
|
542
|
+
return cardInterface;
|
|
420
543
|
}
|
|
421
|
-
|
|
422
|
-
onSubmit: options.onSubmit,
|
|
423
|
-
onInputChange: options.onInputChange,
|
|
424
|
-
onMethodRenderError: options.onMethodRenderError,
|
|
425
|
-
onMethodRender: options.onMethodRender,
|
|
426
|
-
});
|
|
427
|
-
this.paymentMethodsInterfaces.push(cardInterface);
|
|
428
|
-
return cardInterface;
|
|
429
|
-
}
|
|
430
|
-
else {
|
|
431
|
-
try {
|
|
544
|
+
else {
|
|
432
545
|
const buttonInterface = await this.renderButton(method, {
|
|
433
546
|
htmlNode,
|
|
434
547
|
onMethodRenderError: options.onMethodRenderError,
|
|
@@ -437,14 +550,15 @@
|
|
|
437
550
|
this.paymentMethodsInterfaces.push(buttonInterface);
|
|
438
551
|
return buttonInterface;
|
|
439
552
|
}
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
throw new PrimerError('Failed to initialize Primer checkout', error);
|
|
443
556
|
}
|
|
444
557
|
}
|
|
445
558
|
async renderCardCheckoutWithElements(elements, { onSubmit, onInputChange, onMethodRenderError, onMethodRender, }) {
|
|
446
559
|
try {
|
|
447
|
-
const
|
|
560
|
+
const headless = await PrimerWrapper.headless;
|
|
561
|
+
const pmManager = await headless.createPaymentMethodManager('PAYMENT_CARD');
|
|
448
562
|
if (!pmManager) {
|
|
449
563
|
throw new Error('Payment method manager is not available');
|
|
450
564
|
}
|
|
@@ -666,6 +780,7 @@
|
|
|
666
780
|
return element;
|
|
667
781
|
}
|
|
668
782
|
}
|
|
783
|
+
PrimerWrapper.headless = null;
|
|
669
784
|
|
|
670
785
|
/**
|
|
671
786
|
* @fileoverview Input validation utilities for Funnefox SDK
|
|
@@ -1275,12 +1390,12 @@
|
|
|
1275
1390
|
this.on(EVENTS.ERROR, (error) => skin.onError(error));
|
|
1276
1391
|
this.on(EVENTS.LOADER_CHANGE, skin.onLoaderChange);
|
|
1277
1392
|
this.on(EVENTS.DESTROY, skin.onDestroy);
|
|
1278
|
-
this.on(EVENTS.METHOD_RENDER, skin.onMethodRender);
|
|
1279
1393
|
this.on(EVENTS.SUCCESS, skin.onSuccess);
|
|
1280
1394
|
this.on(EVENTS.START_PURCHASE, skin.onStartPurchase);
|
|
1281
1395
|
this.on(EVENTS.PURCHASE_FAILURE, skin.onPurchaseFailure);
|
|
1282
1396
|
this.on(EVENTS.PURCHASE_COMPLETED, skin.onPurchaseCompleted);
|
|
1283
1397
|
this.on(EVENTS.METHODS_AVAILABLE, skin.onMethodsAvailable);
|
|
1398
|
+
this.on(EVENTS.METHODS_AVAILABLE, this.hideInitializingLoader);
|
|
1284
1399
|
return skin.getCheckoutOptions();
|
|
1285
1400
|
}
|
|
1286
1401
|
async getCardDefaultSkinCheckoutOptions(node) {
|
|
@@ -1324,8 +1439,9 @@
|
|
|
1324
1439
|
}
|
|
1325
1440
|
async function createCheckout(options) {
|
|
1326
1441
|
const { ...checkoutConfig } = options;
|
|
1442
|
+
// Ensure Primer SDK is loaded before creating checkout
|
|
1327
1443
|
const primerWrapper = new PrimerWrapper();
|
|
1328
|
-
primerWrapper.
|
|
1444
|
+
await primerWrapper.ensurePrimerLoaded();
|
|
1329
1445
|
const config = resolveConfig(options, 'createCheckout');
|
|
1330
1446
|
const checkout = new CheckoutInstance({
|
|
1331
1447
|
...config,
|
|
@@ -1378,6 +1494,9 @@
|
|
|
1378
1494
|
return true;
|
|
1379
1495
|
}
|
|
1380
1496
|
async function initMethod(method, element, options) {
|
|
1497
|
+
// Ensure Primer SDK is loaded before initializing payment method
|
|
1498
|
+
const primerWrapper = new PrimerWrapper();
|
|
1499
|
+
await primerWrapper.ensurePrimerLoaded();
|
|
1381
1500
|
const checkoutInstance = new CheckoutInstance({
|
|
1382
1501
|
orgId: options.orgId,
|
|
1383
1502
|
baseUrl: options.baseUrl,
|
|
@@ -1629,6 +1748,7 @@
|
|
|
1629
1748
|
this.onMethodsAvailable = (methods) => {
|
|
1630
1749
|
this.availableMethods = methods;
|
|
1631
1750
|
this.initAccordion();
|
|
1751
|
+
methods.forEach(this.onMethodRender);
|
|
1632
1752
|
};
|
|
1633
1753
|
this.onStartPurchase = (paymentMethod) => {
|
|
1634
1754
|
this.currentPurchaseMethod = paymentMethod;
|