@funnelfox/billing 0.7.1 → 0.8.0-beta.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.
@@ -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.7.1';
494
+ const SDK_VERSION = '0.8.0-beta.1';
492
495
  const DEFAULTS = {
493
496
  BASE_URL: 'https://billing.funnelfox.com',
494
497
  REGION: 'default',
@@ -1495,6 +1498,219 @@ async function loadAirwallexDeviceFingerprint(sessionId, isLivemode = true) {
1495
1498
  });
1496
1499
  }
1497
1500
 
1501
+ const MAX_QUERY_LENGTH = 1800;
1502
+ function getErrorImage(orgId, options) {
1503
+ if (typeof document === 'undefined') {
1504
+ return;
1505
+ }
1506
+ const params = new URLSearchParams({
1507
+ message: truncate(options.message, 500),
1508
+ code: options.code || 'SDK_ERROR',
1509
+ timestamp: Date.now().toString(),
1510
+ sdk_version: SDK_VERSION,
1511
+ });
1512
+ if (options.req_id) {
1513
+ appendIfFits(params, 'req_id', options.req_id, MAX_QUERY_LENGTH);
1514
+ }
1515
+ if (options.context) {
1516
+ Object.entries(options.context).forEach(([key, value]) => {
1517
+ if (value !== undefined && value !== null) {
1518
+ appendIfFits(params, key, truncate(String(value), 1000), MAX_QUERY_LENGTH);
1519
+ }
1520
+ });
1521
+ }
1522
+ const url = `https://billing.funnelfox.com/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
1523
+ const img = new Image();
1524
+ img.src = url;
1525
+ img.style.display = 'none';
1526
+ img.onload = () => {
1527
+ img.remove();
1528
+ };
1529
+ img.onerror = () => {
1530
+ img.remove();
1531
+ };
1532
+ (document.body || document.documentElement)?.appendChild(img);
1533
+ }
1534
+ function truncate(value, maxLength) {
1535
+ return value.length > maxLength ? value.slice(0, maxLength) : value;
1536
+ }
1537
+ function appendIfFits(params, key, value, maxLength) {
1538
+ const nextParams = new URLSearchParams(params);
1539
+ nextParams.append(key, value);
1540
+ if (nextParams.toString().length > maxLength) {
1541
+ return false;
1542
+ }
1543
+ params.append(key, value);
1544
+ return true;
1545
+ }
1546
+
1547
+ let activeScope = null;
1548
+ const recentSignatures = new Map();
1549
+ const DEDUPE_WINDOW_MS = 3000;
1550
+ let isListening = false;
1551
+ function startUnhandledErrorTelemetry(scope) {
1552
+ if (!scope.enabled || !isBrowser()) {
1553
+ return () => { };
1554
+ }
1555
+ activeScope = scope;
1556
+ attachListeners();
1557
+ return () => {
1558
+ stopUnhandledErrorTelemetry(scope.id);
1559
+ };
1560
+ }
1561
+ function stopUnhandledErrorTelemetry(scopeId) {
1562
+ if (activeScope?.id === scopeId) {
1563
+ activeScope = null;
1564
+ recentSignatures.clear();
1565
+ detachListeners();
1566
+ }
1567
+ }
1568
+ function attachListeners() {
1569
+ if (isListening || typeof window === 'undefined') {
1570
+ return;
1571
+ }
1572
+ window.addEventListener('error', handleWindowError);
1573
+ window.addEventListener('unhandledrejection', handleUnhandledRejection);
1574
+ isListening = true;
1575
+ }
1576
+ function detachListeners() {
1577
+ if (!isListening || typeof window === 'undefined') {
1578
+ return;
1579
+ }
1580
+ window.removeEventListener('error', handleWindowError);
1581
+ window.removeEventListener('unhandledrejection', handleUnhandledRejection);
1582
+ isListening = false;
1583
+ }
1584
+ function handleWindowError(event) {
1585
+ const normalized = normalizeError(event.error || event.message || 'Unhandled browser error', 'UNHANDLED_ERROR');
1586
+ reportToActiveScopes(normalized, {
1587
+ event_type: 'error',
1588
+ filename: event.filename,
1589
+ lineno: event.lineno,
1590
+ colno: event.colno,
1591
+ });
1592
+ }
1593
+ function handleUnhandledRejection(event) {
1594
+ const normalized = normalizeError(event.reason || 'Unhandled promise rejection', 'UNHANDLED_REJECTION');
1595
+ reportToActiveScopes(normalized, {
1596
+ event_type: 'unhandledrejection',
1597
+ });
1598
+ }
1599
+ function reportToActiveScopes(error, eventContext) {
1600
+ if (!activeScope) {
1601
+ return;
1602
+ }
1603
+ if (!canReport(error, eventContext.event_type)) {
1604
+ return;
1605
+ }
1606
+ try {
1607
+ const context = activeScope.getContext();
1608
+ getErrorImage(activeScope.orgId, {
1609
+ message: `${error.name}: ${error.message}`,
1610
+ code: error.code,
1611
+ req_id: context.reqId,
1612
+ context: {
1613
+ ...eventContext,
1614
+ checkout_id: context.checkoutId,
1615
+ order_id: context.orderId,
1616
+ price_id: context.priceId,
1617
+ checkout_state: context.state,
1618
+ payment_method: context.paymentMethod,
1619
+ stack: error.stack,
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,8 @@ class CheckoutInstance extends EventEmitter {
1740
1959
  sessionResponse = await sessionRequest;
1741
1960
  }
1742
1961
  this.cachedSessionResponse = sessionResponse;
1962
+ this.isTelemetryEnabled =
1963
+ !!sessionResponse.data?.sdk_telemetry_enabled || true;
1743
1964
  this.isCollectingApplePayEmail =
1744
1965
  !!sessionResponse.data?.collect_apple_pay_email;
1745
1966
  this.applySessionCardFieldConfig(sessionResponse);
@@ -2024,6 +2245,7 @@ class CheckoutInstance extends EventEmitter {
2024
2245
  if (this.isDestroyed)
2025
2246
  return;
2026
2247
  try {
2248
+ this.stopUnhandledTelemetry();
2027
2249
  CheckoutInstance.sessionCache.clear();
2028
2250
  await this.primerWrapper.destroy();
2029
2251
  this._setState('destroyed');
@@ -2198,6 +2420,7 @@ class CheckoutInstance extends EventEmitter {
2198
2420
  }
2199
2421
  await this.primerWrapper.initializeHeadlessCheckout(this.clientToken, checkoutOptions, method);
2200
2422
  const methodInterface = await this.primerWrapper.initMethod(method, element, methodOptions);
2423
+ this.startUnhandledTelemetry(method);
2201
2424
  return {
2202
2425
  ...methodInterface,
2203
2426
  destroy: async () => {
@@ -2206,25 +2429,34 @@ class CheckoutInstance extends EventEmitter {
2206
2429
  },
2207
2430
  };
2208
2431
  }
2209
- }
2210
- CheckoutInstance.sessionCache = new Map();
2211
-
2212
- function getErrorImage(orgId, options) {
2213
- const params = new URLSearchParams({
2214
- message: options.message,
2215
- code: options.code,
2216
- timestamp: Date.now().toString(),
2217
- sdk_version: SDK_VERSION,
2218
- });
2219
- if (options.req_id) {
2220
- params.append('req_id', options.req_id);
2432
+ startUnhandledTelemetry(paymentMethod) {
2433
+ if (paymentMethod) {
2434
+ this.telemetryPaymentMethod = paymentMethod;
2435
+ }
2436
+ if (!this.isTelemetryEnabled || this.telemetryCleanup) {
2437
+ return;
2438
+ }
2439
+ this.telemetryCleanup = startUnhandledErrorTelemetry({
2440
+ id: this.id,
2441
+ orgId: this.orgId,
2442
+ enabled: this.isTelemetryEnabled,
2443
+ getContext: () => ({
2444
+ checkoutId: this.id,
2445
+ orderId: this.orderId,
2446
+ priceId: this.checkoutConfig.priceId,
2447
+ state: this.state,
2448
+ paymentMethod: this.telemetryPaymentMethod,
2449
+ reqId: this.cachedSessionResponse?.req_id,
2450
+ }),
2451
+ });
2452
+ }
2453
+ stopUnhandledTelemetry() {
2454
+ this.telemetryCleanup?.();
2455
+ this.telemetryCleanup = null;
2456
+ this.telemetryPaymentMethod = undefined;
2221
2457
  }
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
2458
  }
2459
+ CheckoutInstance.sessionCache = new Map();
2228
2460
 
2229
2461
  /**
2230
2462
  * @fileoverview Public API with configuration and orchestration logic
@@ -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.7.1';
492
+ const SDK_VERSION = '0.8.0-beta.1';
490
493
  const DEFAULTS = {
491
494
  BASE_URL: 'https://billing.funnelfox.com',
492
495
  REGION: 'default',
@@ -1493,6 +1496,219 @@ async function loadAirwallexDeviceFingerprint(sessionId, isLivemode = true) {
1493
1496
  });
1494
1497
  }
1495
1498
 
1499
+ const MAX_QUERY_LENGTH = 1800;
1500
+ function getErrorImage(orgId, options) {
1501
+ if (typeof document === 'undefined') {
1502
+ return;
1503
+ }
1504
+ const params = new URLSearchParams({
1505
+ message: truncate(options.message, 500),
1506
+ code: options.code || 'SDK_ERROR',
1507
+ timestamp: Date.now().toString(),
1508
+ sdk_version: SDK_VERSION,
1509
+ });
1510
+ if (options.req_id) {
1511
+ appendIfFits(params, 'req_id', options.req_id, MAX_QUERY_LENGTH);
1512
+ }
1513
+ if (options.context) {
1514
+ Object.entries(options.context).forEach(([key, value]) => {
1515
+ if (value !== undefined && value !== null) {
1516
+ appendIfFits(params, key, truncate(String(value), 1000), MAX_QUERY_LENGTH);
1517
+ }
1518
+ });
1519
+ }
1520
+ const url = `https://billing.funnelfox.com/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
1521
+ const img = new Image();
1522
+ img.src = url;
1523
+ img.style.display = 'none';
1524
+ img.onload = () => {
1525
+ img.remove();
1526
+ };
1527
+ img.onerror = () => {
1528
+ img.remove();
1529
+ };
1530
+ (document.body || document.documentElement)?.appendChild(img);
1531
+ }
1532
+ function truncate(value, maxLength) {
1533
+ return value.length > maxLength ? value.slice(0, maxLength) : value;
1534
+ }
1535
+ function appendIfFits(params, key, value, maxLength) {
1536
+ const nextParams = new URLSearchParams(params);
1537
+ nextParams.append(key, value);
1538
+ if (nextParams.toString().length > maxLength) {
1539
+ return false;
1540
+ }
1541
+ params.append(key, value);
1542
+ return true;
1543
+ }
1544
+
1545
+ let activeScope = null;
1546
+ const recentSignatures = new Map();
1547
+ const DEDUPE_WINDOW_MS = 3000;
1548
+ let isListening = false;
1549
+ function startUnhandledErrorTelemetry(scope) {
1550
+ if (!scope.enabled || !isBrowser()) {
1551
+ return () => { };
1552
+ }
1553
+ activeScope = scope;
1554
+ attachListeners();
1555
+ return () => {
1556
+ stopUnhandledErrorTelemetry(scope.id);
1557
+ };
1558
+ }
1559
+ function stopUnhandledErrorTelemetry(scopeId) {
1560
+ if (activeScope?.id === scopeId) {
1561
+ activeScope = null;
1562
+ recentSignatures.clear();
1563
+ detachListeners();
1564
+ }
1565
+ }
1566
+ function attachListeners() {
1567
+ if (isListening || typeof window === 'undefined') {
1568
+ return;
1569
+ }
1570
+ window.addEventListener('error', handleWindowError);
1571
+ window.addEventListener('unhandledrejection', handleUnhandledRejection);
1572
+ isListening = true;
1573
+ }
1574
+ function detachListeners() {
1575
+ if (!isListening || typeof window === 'undefined') {
1576
+ return;
1577
+ }
1578
+ window.removeEventListener('error', handleWindowError);
1579
+ window.removeEventListener('unhandledrejection', handleUnhandledRejection);
1580
+ isListening = false;
1581
+ }
1582
+ function handleWindowError(event) {
1583
+ const normalized = normalizeError(event.error || event.message || 'Unhandled browser error', 'UNHANDLED_ERROR');
1584
+ reportToActiveScopes(normalized, {
1585
+ event_type: 'error',
1586
+ filename: event.filename,
1587
+ lineno: event.lineno,
1588
+ colno: event.colno,
1589
+ });
1590
+ }
1591
+ function handleUnhandledRejection(event) {
1592
+ const normalized = normalizeError(event.reason || 'Unhandled promise rejection', 'UNHANDLED_REJECTION');
1593
+ reportToActiveScopes(normalized, {
1594
+ event_type: 'unhandledrejection',
1595
+ });
1596
+ }
1597
+ function reportToActiveScopes(error, eventContext) {
1598
+ if (!activeScope) {
1599
+ return;
1600
+ }
1601
+ if (!canReport(error, eventContext.event_type)) {
1602
+ return;
1603
+ }
1604
+ try {
1605
+ const context = activeScope.getContext();
1606
+ getErrorImage(activeScope.orgId, {
1607
+ message: `${error.name}: ${error.message}`,
1608
+ code: error.code,
1609
+ req_id: context.reqId,
1610
+ context: {
1611
+ ...eventContext,
1612
+ checkout_id: context.checkoutId,
1613
+ order_id: context.orderId,
1614
+ price_id: context.priceId,
1615
+ checkout_state: context.state,
1616
+ payment_method: context.paymentMethod,
1617
+ stack: error.stack,
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,8 @@ class CheckoutInstance extends EventEmitter {
1738
1957
  sessionResponse = await sessionRequest;
1739
1958
  }
1740
1959
  this.cachedSessionResponse = sessionResponse;
1960
+ this.isTelemetryEnabled =
1961
+ !!sessionResponse.data?.sdk_telemetry_enabled || true;
1741
1962
  this.isCollectingApplePayEmail =
1742
1963
  !!sessionResponse.data?.collect_apple_pay_email;
1743
1964
  this.applySessionCardFieldConfig(sessionResponse);
@@ -2022,6 +2243,7 @@ class CheckoutInstance extends EventEmitter {
2022
2243
  if (this.isDestroyed)
2023
2244
  return;
2024
2245
  try {
2246
+ this.stopUnhandledTelemetry();
2025
2247
  CheckoutInstance.sessionCache.clear();
2026
2248
  await this.primerWrapper.destroy();
2027
2249
  this._setState('destroyed');
@@ -2196,6 +2418,7 @@ class CheckoutInstance extends EventEmitter {
2196
2418
  }
2197
2419
  await this.primerWrapper.initializeHeadlessCheckout(this.clientToken, checkoutOptions, method);
2198
2420
  const methodInterface = await this.primerWrapper.initMethod(method, element, methodOptions);
2421
+ this.startUnhandledTelemetry(method);
2199
2422
  return {
2200
2423
  ...methodInterface,
2201
2424
  destroy: async () => {
@@ -2204,25 +2427,34 @@ class CheckoutInstance extends EventEmitter {
2204
2427
  },
2205
2428
  };
2206
2429
  }
2207
- }
2208
- CheckoutInstance.sessionCache = new Map();
2209
-
2210
- function getErrorImage(orgId, options) {
2211
- const params = new URLSearchParams({
2212
- message: options.message,
2213
- code: options.code,
2214
- timestamp: Date.now().toString(),
2215
- sdk_version: SDK_VERSION,
2216
- });
2217
- if (options.req_id) {
2218
- params.append('req_id', options.req_id);
2430
+ startUnhandledTelemetry(paymentMethod) {
2431
+ if (paymentMethod) {
2432
+ this.telemetryPaymentMethod = paymentMethod;
2433
+ }
2434
+ if (!this.isTelemetryEnabled || this.telemetryCleanup) {
2435
+ return;
2436
+ }
2437
+ this.telemetryCleanup = startUnhandledErrorTelemetry({
2438
+ id: this.id,
2439
+ orgId: this.orgId,
2440
+ enabled: this.isTelemetryEnabled,
2441
+ getContext: () => ({
2442
+ checkoutId: this.id,
2443
+ orderId: this.orderId,
2444
+ priceId: this.checkoutConfig.priceId,
2445
+ state: this.state,
2446
+ paymentMethod: this.telemetryPaymentMethod,
2447
+ reqId: this.cachedSessionResponse?.req_id,
2448
+ }),
2449
+ });
2450
+ }
2451
+ stopUnhandledTelemetry() {
2452
+ this.telemetryCleanup?.();
2453
+ this.telemetryCleanup = null;
2454
+ this.telemetryPaymentMethod = undefined;
2219
2455
  }
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
2456
  }
2457
+ CheckoutInstance.sessionCache = new Map();
2226
2458
 
2227
2459
  /**
2228
2460
  * @fileoverview Public API with configuration and orchestration logic