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