@funnelfox/billing 0.7.1 → 0.8.0-add-release-docs.8
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 +262 -21
- package/dist/chunk-index.es.js +262 -21
- package/dist/funnelfox-billing.js +262 -21
- package/dist/funnelfox-billing.min.js +1 -1
- package/package.json +6 -2
- package/src/types.d.ts +1 -0
package/dist/chunk-index.es.js
CHANGED
|
@@ -368,6 +368,9 @@ function generateUUID() {
|
|
|
368
368
|
return v.toString(16);
|
|
369
369
|
});
|
|
370
370
|
}
|
|
371
|
+
function isBrowser() {
|
|
372
|
+
return typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
373
|
+
}
|
|
371
374
|
function sleep(ms) {
|
|
372
375
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
373
376
|
}
|
|
@@ -486,7 +489,7 @@ var PaymentMethod;
|
|
|
486
489
|
/**
|
|
487
490
|
* @fileoverview Constants for Funnefox SDK
|
|
488
491
|
*/
|
|
489
|
-
const SDK_VERSION = '0.
|
|
492
|
+
const SDK_VERSION = '0.8.0-add-release-docs.8';
|
|
490
493
|
const DEFAULTS = {
|
|
491
494
|
BASE_URL: 'https://billing.funnelfox.com',
|
|
492
495
|
REGION: 'default',
|
|
@@ -720,7 +723,7 @@ class PrimerWrapper {
|
|
|
720
723
|
}
|
|
721
724
|
catch (error) {
|
|
722
725
|
onMethodRenderError(allowedPaymentMethod);
|
|
723
|
-
throw new PrimerError(
|
|
726
|
+
throw new PrimerError(`Failed to initialize Primer checkout method ${allowedPaymentMethod}`, error);
|
|
724
727
|
}
|
|
725
728
|
}
|
|
726
729
|
async initMethod(method, htmlNode, options) {
|
|
@@ -754,7 +757,7 @@ class PrimerWrapper {
|
|
|
754
757
|
}
|
|
755
758
|
}
|
|
756
759
|
catch (error) {
|
|
757
|
-
throw new PrimerError(
|
|
760
|
+
throw new PrimerError(`Failed to initialize Primer checkout method ${method}`, error);
|
|
758
761
|
}
|
|
759
762
|
}
|
|
760
763
|
async renderCardCheckoutWithElements(elements, { onSubmit, onInputChange, onCardInputValueChange, isCardholderNameRequired, isPostalCodeRequired, onMethodRenderError, onMethodRender, }) {
|
|
@@ -914,7 +917,7 @@ class PrimerWrapper {
|
|
|
914
917
|
}
|
|
915
918
|
catch (error) {
|
|
916
919
|
onMethodRenderError(PaymentMethod.PAYMENT_CARD);
|
|
917
|
-
throw new PrimerError('Failed to initialize Primer checkout', error);
|
|
920
|
+
throw new PrimerError('Failed to initialize Primer checkout method PAYMENT_CARD', error);
|
|
918
921
|
}
|
|
919
922
|
}
|
|
920
923
|
async initializeHeadlessCheckout(clientToken, primerOptions, method) {
|
|
@@ -1054,6 +1057,9 @@ class PrimerWrapper {
|
|
|
1054
1057
|
}
|
|
1055
1058
|
return element;
|
|
1056
1059
|
}
|
|
1060
|
+
refreshClientSession() {
|
|
1061
|
+
return this.currentHeadless?.then(headless => headless.refreshClientSession());
|
|
1062
|
+
}
|
|
1057
1063
|
}
|
|
1058
1064
|
PrimerWrapper.headlessManager = new HeadlessManager();
|
|
1059
1065
|
|
|
@@ -1493,6 +1499,216 @@ async function loadAirwallexDeviceFingerprint(sessionId, isLivemode = true) {
|
|
|
1493
1499
|
});
|
|
1494
1500
|
}
|
|
1495
1501
|
|
|
1502
|
+
const MAX_QUERY_LENGTH = 1800;
|
|
1503
|
+
function getErrorImage(orgId, options) {
|
|
1504
|
+
if (typeof document === 'undefined') {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
const params = new URLSearchParams({
|
|
1508
|
+
message: truncate(options.message, 500),
|
|
1509
|
+
code: options.code || 'SDK_ERROR',
|
|
1510
|
+
timestamp: Date.now().toString(),
|
|
1511
|
+
sdk_version: SDK_VERSION,
|
|
1512
|
+
});
|
|
1513
|
+
if (options.req_id) {
|
|
1514
|
+
appendIfFits(params, 'req_id', options.req_id, MAX_QUERY_LENGTH);
|
|
1515
|
+
}
|
|
1516
|
+
if (options.context) {
|
|
1517
|
+
Object.entries(options.context).forEach(([key, value]) => {
|
|
1518
|
+
if (value !== undefined && value !== null) {
|
|
1519
|
+
appendIfFits(params, key, truncate(String(value), 1000), MAX_QUERY_LENGTH);
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
const origin = (options.baseUrl || DEFAULTS.BASE_URL).replace(/\/$/, '');
|
|
1524
|
+
const url = `${origin}/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
|
|
1525
|
+
const img = new Image();
|
|
1526
|
+
img.src = url;
|
|
1527
|
+
img.style.display = 'none';
|
|
1528
|
+
img.onload = () => {
|
|
1529
|
+
img.remove();
|
|
1530
|
+
};
|
|
1531
|
+
img.onerror = () => {
|
|
1532
|
+
img.remove();
|
|
1533
|
+
};
|
|
1534
|
+
(document.body || document.documentElement)?.appendChild(img);
|
|
1535
|
+
}
|
|
1536
|
+
function truncate(value, maxLength) {
|
|
1537
|
+
return value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
1538
|
+
}
|
|
1539
|
+
function appendIfFits(params, key, value, maxLength) {
|
|
1540
|
+
const nextParams = new URLSearchParams(params);
|
|
1541
|
+
nextParams.append(key, value);
|
|
1542
|
+
if (nextParams.toString().length > maxLength) {
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
1545
|
+
params.append(key, value);
|
|
1546
|
+
return true;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
let activeScope = null;
|
|
1550
|
+
const recentSignatures = new Map();
|
|
1551
|
+
const DEDUPE_WINDOW_MS = 3000;
|
|
1552
|
+
let isListening = false;
|
|
1553
|
+
function startUnhandledErrorTelemetry(scope) {
|
|
1554
|
+
if (!scope.enabled || !isBrowser()) {
|
|
1555
|
+
return () => { };
|
|
1556
|
+
}
|
|
1557
|
+
activeScope = scope;
|
|
1558
|
+
attachListeners();
|
|
1559
|
+
return () => {
|
|
1560
|
+
stopUnhandledErrorTelemetry(scope.id);
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
function stopUnhandledErrorTelemetry(scopeId) {
|
|
1564
|
+
if (activeScope?.id === scopeId) {
|
|
1565
|
+
activeScope = null;
|
|
1566
|
+
recentSignatures.clear();
|
|
1567
|
+
detachListeners();
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
function attachListeners() {
|
|
1571
|
+
if (isListening || typeof window === 'undefined') {
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
window.addEventListener('error', handleWindowError);
|
|
1575
|
+
window.addEventListener('unhandledrejection', handleUnhandledRejection);
|
|
1576
|
+
isListening = true;
|
|
1577
|
+
}
|
|
1578
|
+
function detachListeners() {
|
|
1579
|
+
if (!isListening || typeof window === 'undefined') {
|
|
1580
|
+
return;
|
|
1581
|
+
}
|
|
1582
|
+
window.removeEventListener('error', handleWindowError);
|
|
1583
|
+
window.removeEventListener('unhandledrejection', handleUnhandledRejection);
|
|
1584
|
+
isListening = false;
|
|
1585
|
+
}
|
|
1586
|
+
function handleWindowError(event) {
|
|
1587
|
+
const normalized = normalizeError(event.error || event.message || 'Unhandled browser error', 'UNHANDLED_ERROR');
|
|
1588
|
+
reportToActiveScopes(normalized, {
|
|
1589
|
+
event_type: 'error',
|
|
1590
|
+
filename: event.filename,
|
|
1591
|
+
lineno: event.lineno,
|
|
1592
|
+
colno: event.colno,
|
|
1593
|
+
});
|
|
1594
|
+
}
|
|
1595
|
+
function handleUnhandledRejection(event) {
|
|
1596
|
+
const normalized = normalizeError(event.reason || 'Unhandled promise rejection', 'UNHANDLED_REJECTION');
|
|
1597
|
+
reportToActiveScopes(normalized, {
|
|
1598
|
+
event_type: 'unhandledrejection',
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
function reportToActiveScopes(error, eventContext) {
|
|
1602
|
+
if (!activeScope) {
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
if (!canReport(error, eventContext.event_type)) {
|
|
1606
|
+
return;
|
|
1607
|
+
}
|
|
1608
|
+
try {
|
|
1609
|
+
const context = activeScope.getContext();
|
|
1610
|
+
getErrorImage(activeScope.orgId, {
|
|
1611
|
+
baseUrl: activeScope.baseUrl,
|
|
1612
|
+
message: `${error.name}: ${error.message}`,
|
|
1613
|
+
code: error.code,
|
|
1614
|
+
context: {
|
|
1615
|
+
...eventContext,
|
|
1616
|
+
order_id: context.orderId,
|
|
1617
|
+
price_id: context.priceId,
|
|
1618
|
+
page_url: getPageUrl(),
|
|
1619
|
+
},
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
catch {
|
|
1623
|
+
// Telemetry must never affect checkout behavior.
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function canReport(error, eventType) {
|
|
1627
|
+
const now = Date.now();
|
|
1628
|
+
pruneExpiredSignatures(now);
|
|
1629
|
+
const signature = buildSignature(error, eventType);
|
|
1630
|
+
const previousReportTime = recentSignatures.get(signature);
|
|
1631
|
+
if (typeof previousReportTime === 'number' &&
|
|
1632
|
+
now - previousReportTime < DEDUPE_WINDOW_MS) {
|
|
1633
|
+
return false;
|
|
1634
|
+
}
|
|
1635
|
+
recentSignatures.set(signature, now);
|
|
1636
|
+
return true;
|
|
1637
|
+
}
|
|
1638
|
+
function pruneExpiredSignatures(now) {
|
|
1639
|
+
recentSignatures.forEach((timestamp, signature) => {
|
|
1640
|
+
if (now - timestamp >= DEDUPE_WINDOW_MS) {
|
|
1641
|
+
recentSignatures.delete(signature);
|
|
1642
|
+
}
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
function buildSignature(error, eventType) {
|
|
1646
|
+
const firstStackLine = error.stack?.split('\n')[0] || '';
|
|
1647
|
+
return [
|
|
1648
|
+
eventType || '',
|
|
1649
|
+
error.code || '',
|
|
1650
|
+
error.message || '',
|
|
1651
|
+
firstStackLine,
|
|
1652
|
+
].join('|');
|
|
1653
|
+
}
|
|
1654
|
+
function normalizeError(reason, fallbackCode) {
|
|
1655
|
+
if (reason instanceof Error) {
|
|
1656
|
+
return {
|
|
1657
|
+
name: reason.name || 'Error',
|
|
1658
|
+
message: reason.message || 'Unknown error',
|
|
1659
|
+
code: getErrorCode(reason, fallbackCode),
|
|
1660
|
+
stack: reason.stack,
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
if (typeof reason === 'object' && reason !== null) {
|
|
1664
|
+
const record = reason;
|
|
1665
|
+
return {
|
|
1666
|
+
name: toSafeString(record.name) || 'Error',
|
|
1667
|
+
message: toSafeString(record.message) || safeStringify(reason),
|
|
1668
|
+
code: toSafeString(record.code) || fallbackCode,
|
|
1669
|
+
stack: toSafeString(record.stack),
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
return {
|
|
1673
|
+
name: 'Error',
|
|
1674
|
+
message: toSafeString(reason) || 'Unknown error',
|
|
1675
|
+
code: fallbackCode,
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
function getErrorCode(error, fallbackCode) {
|
|
1679
|
+
const record = error;
|
|
1680
|
+
return typeof record.code === 'string' && record.code
|
|
1681
|
+
? record.code
|
|
1682
|
+
: fallbackCode;
|
|
1683
|
+
}
|
|
1684
|
+
function toSafeString(value) {
|
|
1685
|
+
if (typeof value === 'string') {
|
|
1686
|
+
return value;
|
|
1687
|
+
}
|
|
1688
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
1689
|
+
return String(value);
|
|
1690
|
+
}
|
|
1691
|
+
return '';
|
|
1692
|
+
}
|
|
1693
|
+
function safeStringify(value) {
|
|
1694
|
+
try {
|
|
1695
|
+
return JSON.stringify(value);
|
|
1696
|
+
}
|
|
1697
|
+
catch {
|
|
1698
|
+
return 'Unserializable error';
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
function getPageUrl() {
|
|
1702
|
+
if (typeof window === 'undefined' || !window.location) {
|
|
1703
|
+
return '';
|
|
1704
|
+
}
|
|
1705
|
+
const location = window.location;
|
|
1706
|
+
if (location.origin && location.pathname) {
|
|
1707
|
+
return `${location.origin}${location.pathname}`;
|
|
1708
|
+
}
|
|
1709
|
+
return (location.href || '').split(/[?#]/)[0];
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1496
1712
|
/**
|
|
1497
1713
|
* @fileoverview Checkout instance manager for Funnefox SDK
|
|
1498
1714
|
*/
|
|
@@ -1502,6 +1718,8 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1502
1718
|
this.counter = 0;
|
|
1503
1719
|
this.cachedSessionResponse = null;
|
|
1504
1720
|
this.cardSessionFieldConfig = {};
|
|
1721
|
+
this.isTelemetryEnabled = false;
|
|
1722
|
+
this.telemetryCleanup = null;
|
|
1505
1723
|
this.handleInputChange = (inputName, error) => {
|
|
1506
1724
|
this.emit(EVENTS.INPUT_ERROR, { name: inputName, error });
|
|
1507
1725
|
};
|
|
@@ -1664,6 +1882,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1664
1882
|
await this.createSession();
|
|
1665
1883
|
await this._initializePrimerCheckout();
|
|
1666
1884
|
this._setState('ready');
|
|
1885
|
+
this.startUnhandledTelemetry();
|
|
1667
1886
|
this.checkoutConfig?.onInitialized?.();
|
|
1668
1887
|
return this;
|
|
1669
1888
|
}
|
|
@@ -1738,6 +1957,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1738
1957
|
sessionResponse = await sessionRequest;
|
|
1739
1958
|
}
|
|
1740
1959
|
this.cachedSessionResponse = sessionResponse;
|
|
1960
|
+
this.isTelemetryEnabled = !!sessionResponse.data?.sdk_telemetry_enabled;
|
|
1741
1961
|
this.isCollectingApplePayEmail =
|
|
1742
1962
|
!!sessionResponse.data?.collect_apple_pay_email;
|
|
1743
1963
|
this.applySessionCardFieldConfig(sessionResponse);
|
|
@@ -1991,6 +2211,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
1991
2211
|
throw new CheckoutError('Cannot update price while payment is processing');
|
|
1992
2212
|
}
|
|
1993
2213
|
try {
|
|
2214
|
+
this.onLoaderChangeWithRace(true);
|
|
1994
2215
|
this._setState('updating');
|
|
1995
2216
|
// Invalidate session cache
|
|
1996
2217
|
CheckoutInstance.sessionCache.clear();
|
|
@@ -2001,9 +2222,12 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2001
2222
|
clientMetadata,
|
|
2002
2223
|
});
|
|
2003
2224
|
this.checkoutConfig.priceId = newPriceId;
|
|
2225
|
+
await this.primerWrapper.refreshClientSession();
|
|
2226
|
+
this.onLoaderChangeWithRace(false);
|
|
2004
2227
|
this._setState('ready');
|
|
2005
2228
|
}
|
|
2006
2229
|
catch (error) {
|
|
2230
|
+
this.onLoaderChangeWithRace(false);
|
|
2007
2231
|
this._setState('error');
|
|
2008
2232
|
this.emit(EVENTS.ERROR, error);
|
|
2009
2233
|
throw error;
|
|
@@ -2022,6 +2246,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2022
2246
|
if (this.isDestroyed)
|
|
2023
2247
|
return;
|
|
2024
2248
|
try {
|
|
2249
|
+
this.stopUnhandledTelemetry();
|
|
2025
2250
|
CheckoutInstance.sessionCache.clear();
|
|
2026
2251
|
await this.primerWrapper.destroy();
|
|
2027
2252
|
this._setState('destroyed');
|
|
@@ -2196,6 +2421,7 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2196
2421
|
}
|
|
2197
2422
|
await this.primerWrapper.initializeHeadlessCheckout(this.clientToken, checkoutOptions, method);
|
|
2198
2423
|
const methodInterface = await this.primerWrapper.initMethod(method, element, methodOptions);
|
|
2424
|
+
this.startUnhandledTelemetry(method);
|
|
2199
2425
|
return {
|
|
2200
2426
|
...methodInterface,
|
|
2201
2427
|
destroy: async () => {
|
|
@@ -2204,25 +2430,35 @@ class CheckoutInstance extends EventEmitter {
|
|
|
2204
2430
|
},
|
|
2205
2431
|
};
|
|
2206
2432
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2433
|
+
startUnhandledTelemetry(paymentMethod) {
|
|
2434
|
+
if (paymentMethod) {
|
|
2435
|
+
this.telemetryPaymentMethod = paymentMethod;
|
|
2436
|
+
}
|
|
2437
|
+
if (!this.isTelemetryEnabled || this.telemetryCleanup) {
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
this.telemetryCleanup = startUnhandledErrorTelemetry({
|
|
2441
|
+
id: this.id,
|
|
2442
|
+
orgId: this.orgId,
|
|
2443
|
+
baseUrl: this.baseUrl,
|
|
2444
|
+
enabled: this.isTelemetryEnabled,
|
|
2445
|
+
getContext: () => ({
|
|
2446
|
+
checkoutId: this.id,
|
|
2447
|
+
orderId: this.orderId,
|
|
2448
|
+
priceId: this.checkoutConfig.priceId,
|
|
2449
|
+
state: this.state,
|
|
2450
|
+
paymentMethod: this.telemetryPaymentMethod,
|
|
2451
|
+
reqId: this.cachedSessionResponse?.req_id,
|
|
2452
|
+
}),
|
|
2453
|
+
});
|
|
2454
|
+
}
|
|
2455
|
+
stopUnhandledTelemetry() {
|
|
2456
|
+
this.telemetryCleanup?.();
|
|
2457
|
+
this.telemetryCleanup = null;
|
|
2458
|
+
this.telemetryPaymentMethod = undefined;
|
|
2219
2459
|
}
|
|
2220
|
-
const url = `https://billing.funnelfox.com/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
|
|
2221
|
-
const img = new Image();
|
|
2222
|
-
img.src = url;
|
|
2223
|
-
img.style.display = 'none';
|
|
2224
|
-
document.body.appendChild(img);
|
|
2225
2460
|
}
|
|
2461
|
+
CheckoutInstance.sessionCache = new Map();
|
|
2226
2462
|
|
|
2227
2463
|
/**
|
|
2228
2464
|
* @fileoverview Public API with configuration and orchestration logic
|
|
@@ -2263,6 +2499,7 @@ async function createCheckout(options) {
|
|
|
2263
2499
|
}
|
|
2264
2500
|
catch (error) {
|
|
2265
2501
|
getErrorImage(options.orgId, {
|
|
2502
|
+
baseUrl: options.apiConfig?.baseUrl,
|
|
2266
2503
|
message: error.message,
|
|
2267
2504
|
code: error.code,
|
|
2268
2505
|
req_id: error?.response?.req_id,
|
|
@@ -2314,6 +2551,7 @@ async function silentPurchase(options) {
|
|
|
2314
2551
|
}
|
|
2315
2552
|
catch (error) {
|
|
2316
2553
|
getErrorImage(orgId, {
|
|
2554
|
+
baseUrl,
|
|
2317
2555
|
message: error.message,
|
|
2318
2556
|
code: error.code,
|
|
2319
2557
|
req_id: error?.response?.req_id,
|
|
@@ -2364,6 +2602,7 @@ async function initMethod(method, element, options) {
|
|
|
2364
2602
|
}
|
|
2365
2603
|
catch (error) {
|
|
2366
2604
|
getErrorImage(options.orgId, {
|
|
2605
|
+
baseUrl: options.baseUrl,
|
|
2367
2606
|
message: error.message,
|
|
2368
2607
|
code: error.code,
|
|
2369
2608
|
req_id: error?.response?.req_id,
|
|
@@ -2402,6 +2641,7 @@ async function getAvailablePaymentMethods(params) {
|
|
|
2402
2641
|
}
|
|
2403
2642
|
catch (error) {
|
|
2404
2643
|
getErrorImage(params.orgId, {
|
|
2644
|
+
baseUrl: params.baseUrl,
|
|
2405
2645
|
message: error.message,
|
|
2406
2646
|
code: error.code,
|
|
2407
2647
|
req_id: error?.response?.req_id,
|
|
@@ -2424,5 +2664,6 @@ const Billing = {
|
|
|
2424
2664
|
if (typeof window !== 'undefined') {
|
|
2425
2665
|
window.Billing = Billing;
|
|
2426
2666
|
}
|
|
2667
|
+
console.debug('Billing SDK inited');
|
|
2427
2668
|
|
|
2428
2669
|
export { APIError as A, Billing as B, CheckoutError as C, DEFAULT_BUTTONS_OPTIONS as D, EVENTS as E, FunnefoxSDKError as F, NetworkError as N, PaymentMethod as P, SDK_VERSION as S, ValidationError as V, PrimerError as a, ConfigurationError as b, DEFAULTS as c, CHECKOUT_STATES as d, ERROR_CODES as e, configure as f, createCheckout as g, createClientSession as h, getAvailablePaymentMethods as i };
|