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