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