@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.
@@ -374,6 +374,9 @@
374
374
  return v.toString(16);
375
375
  });
376
376
  }
377
+ function isBrowser() {
378
+ return typeof window !== 'undefined' && typeof document !== 'undefined';
379
+ }
377
380
  function sleep(ms) {
378
381
  return new Promise(resolve => setTimeout(resolve, ms));
379
382
  }
@@ -492,7 +495,7 @@
492
495
  /**
493
496
  * @fileoverview Constants for Funnefox SDK
494
497
  */
495
- const SDK_VERSION = '0.7.1';
498
+ const SDK_VERSION = '0.8.0-add-release-docs.8';
496
499
  const DEFAULTS = {
497
500
  BASE_URL: 'https://billing.funnelfox.com',
498
501
  REGION: 'default',
@@ -726,7 +729,7 @@
726
729
  }
727
730
  catch (error) {
728
731
  onMethodRenderError(allowedPaymentMethod);
729
- throw new PrimerError('Failed to initialize Primer checkout', error);
732
+ throw new PrimerError(`Failed to initialize Primer checkout method ${allowedPaymentMethod}`, error);
730
733
  }
731
734
  }
732
735
  async initMethod(method, htmlNode, options) {
@@ -760,7 +763,7 @@
760
763
  }
761
764
  }
762
765
  catch (error) {
763
- throw new PrimerError('Failed to initialize Primer checkout', error);
766
+ throw new PrimerError(`Failed to initialize Primer checkout method ${method}`, error);
764
767
  }
765
768
  }
766
769
  async renderCardCheckoutWithElements(elements, { onSubmit, onInputChange, onCardInputValueChange, isCardholderNameRequired, isPostalCodeRequired, onMethodRenderError, onMethodRender, }) {
@@ -920,7 +923,7 @@
920
923
  }
921
924
  catch (error) {
922
925
  onMethodRenderError(exports.PaymentMethod.PAYMENT_CARD);
923
- throw new PrimerError('Failed to initialize Primer checkout', error);
926
+ throw new PrimerError('Failed to initialize Primer checkout method PAYMENT_CARD', error);
924
927
  }
925
928
  }
926
929
  async initializeHeadlessCheckout(clientToken, primerOptions, method) {
@@ -1060,6 +1063,9 @@
1060
1063
  }
1061
1064
  return element;
1062
1065
  }
1066
+ refreshClientSession() {
1067
+ return this.currentHeadless?.then(headless => headless.refreshClientSession());
1068
+ }
1063
1069
  }
1064
1070
  PrimerWrapper.headlessManager = new HeadlessManager();
1065
1071
 
@@ -1499,6 +1505,216 @@
1499
1505
  });
1500
1506
  }
1501
1507
 
1508
+ const MAX_QUERY_LENGTH = 1800;
1509
+ function getErrorImage(orgId, options) {
1510
+ if (typeof document === 'undefined') {
1511
+ return;
1512
+ }
1513
+ const params = new URLSearchParams({
1514
+ message: truncate(options.message, 500),
1515
+ code: options.code || 'SDK_ERROR',
1516
+ timestamp: Date.now().toString(),
1517
+ sdk_version: SDK_VERSION,
1518
+ });
1519
+ if (options.req_id) {
1520
+ appendIfFits(params, 'req_id', options.req_id, MAX_QUERY_LENGTH);
1521
+ }
1522
+ if (options.context) {
1523
+ Object.entries(options.context).forEach(([key, value]) => {
1524
+ if (value !== undefined && value !== null) {
1525
+ appendIfFits(params, key, truncate(String(value), 1000), MAX_QUERY_LENGTH);
1526
+ }
1527
+ });
1528
+ }
1529
+ const origin = (options.baseUrl || DEFAULTS.BASE_URL).replace(/\/$/, '');
1530
+ const url = `${origin}/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
1531
+ const img = new Image();
1532
+ img.src = url;
1533
+ img.style.display = 'none';
1534
+ img.onload = () => {
1535
+ img.remove();
1536
+ };
1537
+ img.onerror = () => {
1538
+ img.remove();
1539
+ };
1540
+ (document.body || document.documentElement)?.appendChild(img);
1541
+ }
1542
+ function truncate(value, maxLength) {
1543
+ return value.length > maxLength ? value.slice(0, maxLength) : value;
1544
+ }
1545
+ function appendIfFits(params, key, value, maxLength) {
1546
+ const nextParams = new URLSearchParams(params);
1547
+ nextParams.append(key, value);
1548
+ if (nextParams.toString().length > maxLength) {
1549
+ return false;
1550
+ }
1551
+ params.append(key, value);
1552
+ return true;
1553
+ }
1554
+
1555
+ let activeScope = null;
1556
+ const recentSignatures = new Map();
1557
+ const DEDUPE_WINDOW_MS = 3000;
1558
+ let isListening = false;
1559
+ function startUnhandledErrorTelemetry(scope) {
1560
+ if (!scope.enabled || !isBrowser()) {
1561
+ return () => { };
1562
+ }
1563
+ activeScope = scope;
1564
+ attachListeners();
1565
+ return () => {
1566
+ stopUnhandledErrorTelemetry(scope.id);
1567
+ };
1568
+ }
1569
+ function stopUnhandledErrorTelemetry(scopeId) {
1570
+ if (activeScope?.id === scopeId) {
1571
+ activeScope = null;
1572
+ recentSignatures.clear();
1573
+ detachListeners();
1574
+ }
1575
+ }
1576
+ function attachListeners() {
1577
+ if (isListening || typeof window === 'undefined') {
1578
+ return;
1579
+ }
1580
+ window.addEventListener('error', handleWindowError);
1581
+ window.addEventListener('unhandledrejection', handleUnhandledRejection);
1582
+ isListening = true;
1583
+ }
1584
+ function detachListeners() {
1585
+ if (!isListening || typeof window === 'undefined') {
1586
+ return;
1587
+ }
1588
+ window.removeEventListener('error', handleWindowError);
1589
+ window.removeEventListener('unhandledrejection', handleUnhandledRejection);
1590
+ isListening = false;
1591
+ }
1592
+ function handleWindowError(event) {
1593
+ const normalized = normalizeError(event.error || event.message || 'Unhandled browser error', 'UNHANDLED_ERROR');
1594
+ reportToActiveScopes(normalized, {
1595
+ event_type: 'error',
1596
+ filename: event.filename,
1597
+ lineno: event.lineno,
1598
+ colno: event.colno,
1599
+ });
1600
+ }
1601
+ function handleUnhandledRejection(event) {
1602
+ const normalized = normalizeError(event.reason || 'Unhandled promise rejection', 'UNHANDLED_REJECTION');
1603
+ reportToActiveScopes(normalized, {
1604
+ event_type: 'unhandledrejection',
1605
+ });
1606
+ }
1607
+ function reportToActiveScopes(error, eventContext) {
1608
+ if (!activeScope) {
1609
+ return;
1610
+ }
1611
+ if (!canReport(error, eventContext.event_type)) {
1612
+ return;
1613
+ }
1614
+ try {
1615
+ const context = activeScope.getContext();
1616
+ getErrorImage(activeScope.orgId, {
1617
+ baseUrl: activeScope.baseUrl,
1618
+ message: `${error.name}: ${error.message}`,
1619
+ code: error.code,
1620
+ context: {
1621
+ ...eventContext,
1622
+ order_id: context.orderId,
1623
+ price_id: context.priceId,
1624
+ page_url: getPageUrl(),
1625
+ },
1626
+ });
1627
+ }
1628
+ catch {
1629
+ // Telemetry must never affect checkout behavior.
1630
+ }
1631
+ }
1632
+ function canReport(error, eventType) {
1633
+ const now = Date.now();
1634
+ pruneExpiredSignatures(now);
1635
+ const signature = buildSignature(error, eventType);
1636
+ const previousReportTime = recentSignatures.get(signature);
1637
+ if (typeof previousReportTime === 'number' &&
1638
+ now - previousReportTime < DEDUPE_WINDOW_MS) {
1639
+ return false;
1640
+ }
1641
+ recentSignatures.set(signature, now);
1642
+ return true;
1643
+ }
1644
+ function pruneExpiredSignatures(now) {
1645
+ recentSignatures.forEach((timestamp, signature) => {
1646
+ if (now - timestamp >= DEDUPE_WINDOW_MS) {
1647
+ recentSignatures.delete(signature);
1648
+ }
1649
+ });
1650
+ }
1651
+ function buildSignature(error, eventType) {
1652
+ const firstStackLine = error.stack?.split('\n')[0] || '';
1653
+ return [
1654
+ eventType || '',
1655
+ error.code || '',
1656
+ error.message || '',
1657
+ firstStackLine,
1658
+ ].join('|');
1659
+ }
1660
+ function normalizeError(reason, fallbackCode) {
1661
+ if (reason instanceof Error) {
1662
+ return {
1663
+ name: reason.name || 'Error',
1664
+ message: reason.message || 'Unknown error',
1665
+ code: getErrorCode(reason, fallbackCode),
1666
+ stack: reason.stack,
1667
+ };
1668
+ }
1669
+ if (typeof reason === 'object' && reason !== null) {
1670
+ const record = reason;
1671
+ return {
1672
+ name: toSafeString(record.name) || 'Error',
1673
+ message: toSafeString(record.message) || safeStringify(reason),
1674
+ code: toSafeString(record.code) || fallbackCode,
1675
+ stack: toSafeString(record.stack),
1676
+ };
1677
+ }
1678
+ return {
1679
+ name: 'Error',
1680
+ message: toSafeString(reason) || 'Unknown error',
1681
+ code: fallbackCode,
1682
+ };
1683
+ }
1684
+ function getErrorCode(error, fallbackCode) {
1685
+ const record = error;
1686
+ return typeof record.code === 'string' && record.code
1687
+ ? record.code
1688
+ : fallbackCode;
1689
+ }
1690
+ function toSafeString(value) {
1691
+ if (typeof value === 'string') {
1692
+ return value;
1693
+ }
1694
+ if (typeof value === 'number' || typeof value === 'boolean') {
1695
+ return String(value);
1696
+ }
1697
+ return '';
1698
+ }
1699
+ function safeStringify(value) {
1700
+ try {
1701
+ return JSON.stringify(value);
1702
+ }
1703
+ catch {
1704
+ return 'Unserializable error';
1705
+ }
1706
+ }
1707
+ function getPageUrl() {
1708
+ if (typeof window === 'undefined' || !window.location) {
1709
+ return '';
1710
+ }
1711
+ const location = window.location;
1712
+ if (location.origin && location.pathname) {
1713
+ return `${location.origin}${location.pathname}`;
1714
+ }
1715
+ return (location.href || '').split(/[?#]/)[0];
1716
+ }
1717
+
1502
1718
  /**
1503
1719
  * @fileoverview Checkout instance manager for Funnefox SDK
1504
1720
  */
@@ -1508,6 +1724,8 @@
1508
1724
  this.counter = 0;
1509
1725
  this.cachedSessionResponse = null;
1510
1726
  this.cardSessionFieldConfig = {};
1727
+ this.isTelemetryEnabled = false;
1728
+ this.telemetryCleanup = null;
1511
1729
  this.handleInputChange = (inputName, error) => {
1512
1730
  this.emit(EVENTS.INPUT_ERROR, { name: inputName, error });
1513
1731
  };
@@ -1670,6 +1888,7 @@
1670
1888
  await this.createSession();
1671
1889
  await this._initializePrimerCheckout();
1672
1890
  this._setState('ready');
1891
+ this.startUnhandledTelemetry();
1673
1892
  this.checkoutConfig?.onInitialized?.();
1674
1893
  return this;
1675
1894
  }
@@ -1744,6 +1963,7 @@
1744
1963
  sessionResponse = await sessionRequest;
1745
1964
  }
1746
1965
  this.cachedSessionResponse = sessionResponse;
1966
+ this.isTelemetryEnabled = !!sessionResponse.data?.sdk_telemetry_enabled;
1747
1967
  this.isCollectingApplePayEmail =
1748
1968
  !!sessionResponse.data?.collect_apple_pay_email;
1749
1969
  this.applySessionCardFieldConfig(sessionResponse);
@@ -1997,6 +2217,7 @@
1997
2217
  throw new CheckoutError('Cannot update price while payment is processing');
1998
2218
  }
1999
2219
  try {
2220
+ this.onLoaderChangeWithRace(true);
2000
2221
  this._setState('updating');
2001
2222
  // Invalidate session cache
2002
2223
  CheckoutInstance.sessionCache.clear();
@@ -2007,9 +2228,12 @@
2007
2228
  clientMetadata,
2008
2229
  });
2009
2230
  this.checkoutConfig.priceId = newPriceId;
2231
+ await this.primerWrapper.refreshClientSession();
2232
+ this.onLoaderChangeWithRace(false);
2010
2233
  this._setState('ready');
2011
2234
  }
2012
2235
  catch (error) {
2236
+ this.onLoaderChangeWithRace(false);
2013
2237
  this._setState('error');
2014
2238
  this.emit(EVENTS.ERROR, error);
2015
2239
  throw error;
@@ -2028,6 +2252,7 @@
2028
2252
  if (this.isDestroyed)
2029
2253
  return;
2030
2254
  try {
2255
+ this.stopUnhandledTelemetry();
2031
2256
  CheckoutInstance.sessionCache.clear();
2032
2257
  await this.primerWrapper.destroy();
2033
2258
  this._setState('destroyed');
@@ -2202,6 +2427,7 @@
2202
2427
  }
2203
2428
  await this.primerWrapper.initializeHeadlessCheckout(this.clientToken, checkoutOptions, method);
2204
2429
  const methodInterface = await this.primerWrapper.initMethod(method, element, methodOptions);
2430
+ this.startUnhandledTelemetry(method);
2205
2431
  return {
2206
2432
  ...methodInterface,
2207
2433
  destroy: async () => {
@@ -2210,25 +2436,35 @@
2210
2436
  },
2211
2437
  };
2212
2438
  }
2213
- }
2214
- CheckoutInstance.sessionCache = new Map();
2215
-
2216
- function getErrorImage(orgId, options) {
2217
- const params = new URLSearchParams({
2218
- message: options.message,
2219
- code: options.code,
2220
- timestamp: Date.now().toString(),
2221
- sdk_version: SDK_VERSION,
2222
- });
2223
- if (options.req_id) {
2224
- params.append('req_id', options.req_id);
2439
+ startUnhandledTelemetry(paymentMethod) {
2440
+ if (paymentMethod) {
2441
+ this.telemetryPaymentMethod = paymentMethod;
2442
+ }
2443
+ if (!this.isTelemetryEnabled || this.telemetryCleanup) {
2444
+ return;
2445
+ }
2446
+ this.telemetryCleanup = startUnhandledErrorTelemetry({
2447
+ id: this.id,
2448
+ orgId: this.orgId,
2449
+ baseUrl: this.baseUrl,
2450
+ enabled: this.isTelemetryEnabled,
2451
+ getContext: () => ({
2452
+ checkoutId: this.id,
2453
+ orderId: this.orderId,
2454
+ priceId: this.checkoutConfig.priceId,
2455
+ state: this.state,
2456
+ paymentMethod: this.telemetryPaymentMethod,
2457
+ reqId: this.cachedSessionResponse?.req_id,
2458
+ }),
2459
+ });
2460
+ }
2461
+ stopUnhandledTelemetry() {
2462
+ this.telemetryCleanup?.();
2463
+ this.telemetryCleanup = null;
2464
+ this.telemetryPaymentMethod = undefined;
2225
2465
  }
2226
- const url = `https://billing.funnelfox.com/sdk_report/${encodeURIComponent(orgId)}/crash?${params.toString()}`;
2227
- const img = new Image();
2228
- img.src = url;
2229
- img.style.display = 'none';
2230
- document.body.appendChild(img);
2231
2466
  }
2467
+ CheckoutInstance.sessionCache = new Map();
2232
2468
 
2233
2469
  /**
2234
2470
  * @fileoverview Public API with configuration and orchestration logic
@@ -2269,6 +2505,7 @@
2269
2505
  }
2270
2506
  catch (error) {
2271
2507
  getErrorImage(options.orgId, {
2508
+ baseUrl: options.apiConfig?.baseUrl,
2272
2509
  message: error.message,
2273
2510
  code: error.code,
2274
2511
  req_id: error?.response?.req_id,
@@ -2320,6 +2557,7 @@
2320
2557
  }
2321
2558
  catch (error) {
2322
2559
  getErrorImage(orgId, {
2560
+ baseUrl,
2323
2561
  message: error.message,
2324
2562
  code: error.code,
2325
2563
  req_id: error?.response?.req_id,
@@ -2370,6 +2608,7 @@
2370
2608
  }
2371
2609
  catch (error) {
2372
2610
  getErrorImage(options.orgId, {
2611
+ baseUrl: options.baseUrl,
2373
2612
  message: error.message,
2374
2613
  code: error.code,
2375
2614
  req_id: error?.response?.req_id,
@@ -2408,6 +2647,7 @@
2408
2647
  }
2409
2648
  catch (error) {
2410
2649
  getErrorImage(params.orgId, {
2650
+ baseUrl: params.baseUrl,
2411
2651
  message: error.message,
2412
2652
  code: error.code,
2413
2653
  req_id: error?.response?.req_id,
@@ -2430,6 +2670,7 @@
2430
2670
  if (typeof window !== 'undefined') {
2431
2671
  window.Billing = Billing;
2432
2672
  }
2673
+ console.debug('Billing SDK inited');
2433
2674
 
2434
2675
  var template$1 = "<div class=\"ff-skin-default\" id=\"ff-payment-method-containers\">\n <div id=\"success-screen\" style=\"display: none\">\n <div class=\"success\">\n <img alt=\"Loading\" src=\"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTAiIGhlaWdodD0iNTAiIHZpZXdCb3g9IjAgMCA1MCA1MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTI1IDAuMTExMzI4QzIwLjA3NzQgMC4xMTEzMjggMTUuMjY1NCAxLjU3MTA0IDExLjE3MjUgNC4zMDU4NkM3LjA3OTUyIDcuMDQwNjkgMy44ODk0NSAxMC45Mjc4IDIuMDA1NjYgMTUuNDc1N0MwLjEyMTg4MyAyMC4wMjM1IC0wLjM3MSAyNS4wMjc4IDAuNTg5MzQzIDI5Ljg1NThDMS41NDk2OSAzNC42ODM4IDMuOTIwMTIgMzkuMTE4NSA3LjQwMDkgNDIuNTk5M0MxMC44ODE3IDQ2LjA4MDEgMTUuMzE2NCA0OC40NTA1IDIwLjE0NDQgNDkuNDEwOUMyNC45NzI0IDUwLjM3MTIgMjkuOTc2NyA0OS44NzgzIDM0LjUyNDYgNDcuOTk0NkMzOS4wNzI0IDQ2LjExMDggNDIuOTU5NSA0Mi45MjA3IDQ1LjY5NDQgMzguODI3N0M0OC40MjkyIDM0LjczNDggNDkuODg4OSAyOS45MjI4IDQ5Ljg4ODkgMjUuMDAwMkM0OS44ODg5IDE4LjM5OTMgNDcuMjY2NyAxMi4wNjg3IDQyLjU5OTEgNy40MDExMkMzNy45MzE1IDIuNzMzNTQgMzEuNjAwOSAwLjExMTMyOCAyNSAwLjExMTMyOFpNNDEuMjU1NiAxNi42NDY5TDIwLjgxNTYgMzcuMDcxM0w4Ljc0NDQ0IDI1LjAwMDJDOC4zMzE4OCAyNC41ODc3IDguMTAwMTEgMjQuMDI4MSA4LjEwMDExIDIzLjQ0NDdDOC4xMDAxMSAyMi44NjEyIDguMzMxODggMjIuMzAxNyA4Ljc0NDQ0IDIxLjg4OTFDOS4xNTcgMjEuNDc2NSA5LjcxNjU1IDIxLjI0NDggMTAuMyAyMS4yNDQ4QzEwLjg4MzQgMjEuMjQ0OCAxMS40NDMgMjEuNDc2NSAxMS44NTU2IDIxLjg4OTFMMjAuODQ2NyAzMC44ODAyTDM4LjE3NTYgMTMuNTY2OUMzOC4zNzk4IDEzLjM2MjYgMzguNjIyMyAxMy4yMDA2IDM4Ljg4OTIgMTMuMDlDMzkuMTU2MiAxMi45Nzk1IDM5LjQ0MjIgMTIuOTIyNiAzOS43MzExIDEyLjkyMjZDNDAuMDIgMTIuOTIyNiA0MC4zMDYxIDEyLjk3OTUgNDAuNTczIDEzLjA5QzQwLjgzOTkgMTMuMjAwNiA0MS4wODI0IDEzLjM2MjYgNDEuMjg2NyAxMy41NjY5QzQxLjQ5MDkgMTMuNzcxMiA0MS42NTMgMTQuMDEzNyA0MS43NjM1IDE0LjI4MDZDNDEuODc0MSAxNC41NDc1IDQxLjkzMSAxNC44MzM1IDQxLjkzMSAxNS4xMjI0QzQxLjkzMSAxNS40MTEzIDQxLjg3NDEgMTUuNjk3NCA0MS43NjM1IDE1Ljk2NDNDNDEuNjUzIDE2LjIzMTIgNDEuNDkwOSAxNi40NzM3IDQxLjI4NjcgMTYuNjc4TDQxLjI1NTYgMTYuNjQ2OVoiIGZpbGw9IiM4RURGQzIiLz4KPC9zdmc+Cg==\">\n <div>Payment completed successfully</div>\n </div>\n </div>\n</div>\n";
2435
2676