@flopay/react 1.2.0 → 1.2.4

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/index.cjs CHANGED
@@ -714,6 +714,7 @@ var import_jsx_runtime5 = require("react/jsx-runtime");
714
714
  var DEFAULT_BUTTON_HEIGHT = 45;
715
715
  function DirectPayPalButton({
716
716
  sessionId,
717
+ nonce,
717
718
  billingApiUrl,
718
719
  email,
719
720
  clientId,
@@ -751,6 +752,7 @@ function DirectPayPalButton({
751
752
  const runBeforeButtonClickRef = (0, import_react7.useRef)(runBeforeButtonClick);
752
753
  const sessionRef = (0, import_react7.useRef)(session);
753
754
  const emailRef = (0, import_react7.useRef)(email);
755
+ const nonceRef = (0, import_react7.useRef)(nonce);
754
756
  const beforeClickRef = (0, import_react7.useRef)(null);
755
757
  (0, import_react7.useEffect)(() => {
756
758
  onTokenizedBodyRef.current = onTokenizedBody;
@@ -779,6 +781,9 @@ function DirectPayPalButton({
779
781
  (0, import_react7.useEffect)(() => {
780
782
  emailRef.current = email;
781
783
  }, [email]);
784
+ (0, import_react7.useEffect)(() => {
785
+ nonceRef.current = nonce;
786
+ }, [nonce]);
782
787
  (0, import_react7.useEffect)(() => {
783
788
  onLoadStateChangeRef.current?.(ready && !failed);
784
789
  }, [ready, failed]);
@@ -934,6 +939,7 @@ function DirectPayPalButton({
934
939
  effectiveUserId,
935
940
  {
936
941
  sessionId: effectiveSessionId,
942
+ nonce: nonceRef.current,
937
943
  tokenizedData: body,
938
944
  accountData: {
939
945
  userId: effectiveUserId,
@@ -968,9 +974,12 @@ function DirectPayPalButton({
968
974
  const prepared = beforeClickRef.current;
969
975
  const effectiveSessionId = prepared?.sessionId ?? sessionId;
970
976
  const effectiveEmail = prepared?.accountPatch?.email ?? emailRef.current;
977
+ const intentHeaders = { "Content-Type": "application/json" };
978
+ const currentNonce = nonceRef.current;
979
+ if (currentNonce) intentHeaders["x-checkout-session-token"] = currentNonce;
971
980
  const response = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
972
981
  method: "POST",
973
- headers: { "Content-Type": "application/json" },
982
+ headers: intentHeaders,
974
983
  body: JSON.stringify({
975
984
  sessionId: effectiveSessionId,
976
985
  email: effectiveEmail,
@@ -1263,6 +1272,7 @@ var import_shared6 = require("@flopay/shared");
1263
1272
  var import_jsx_runtime6 = require("react/jsx-runtime");
1264
1273
  var STRIPE_RESUME_KEY = "flopay_stripe_resume";
1265
1274
  var LEGACY_WALLET_RESUME_KEY = "flopay_wallet_resume";
1275
+ var PAYPAL_RESUME_KEY = "flopay_paypal_resume";
1266
1276
  var FLOPAY_KEYFRAMES = `
1267
1277
  .paypal-buttons { margin: 0 !important; vertical-align: top !important; }
1268
1278
  @keyframes flopay-spin { to { transform: rotate(360deg); } }
@@ -1399,6 +1409,7 @@ var SplitCardForm = (0, import_react8.forwardRef)(
1399
1409
  );
1400
1410
  function PayPalButtonInner({
1401
1411
  sessionId,
1412
+ nonce,
1402
1413
  email,
1403
1414
  billingApiUrl,
1404
1415
  onTokenizedBody,
@@ -1428,6 +1439,25 @@ function PayPalButtonInner({
1428
1439
  const redirectStatus = params.get("redirect_status");
1429
1440
  if (!paymentIntentId || !clientSecret) return;
1430
1441
  paypalResumeAttempted.current = true;
1442
+ let persistedOverrides;
1443
+ try {
1444
+ const raw = localStorage.getItem(PAYPAL_RESUME_KEY);
1445
+ if (raw) {
1446
+ const parsed = JSON.parse(raw);
1447
+ if (parsed.clientSecret === clientSecret) {
1448
+ persistedOverrides = {
1449
+ ...parsed.accountPatch ? { accountPatch: parsed.accountPatch } : {},
1450
+ ...parsed.sessionId ? { sessionId: parsed.sessionId } : {},
1451
+ ...parsed.nonce ? { nonce: parsed.nonce } : {}
1452
+ };
1453
+ }
1454
+ }
1455
+ } catch {
1456
+ }
1457
+ try {
1458
+ localStorage.removeItem(PAYPAL_RESUME_KEY);
1459
+ } catch {
1460
+ }
1431
1461
  const cleanedUrl = new URL(window.location.href);
1432
1462
  cleanedUrl.searchParams.delete("payment_intent");
1433
1463
  cleanedUrl.searchParams.delete("payment_intent_client_secret");
@@ -1454,7 +1484,7 @@ function PayPalButtonInner({
1454
1484
  type: "card",
1455
1485
  threeDSecureActionResultTokenId: paymentIntent.id,
1456
1486
  isPaypal: true
1457
- });
1487
+ }, persistedOverrides);
1458
1488
  } else {
1459
1489
  const message = "PayPal payment was not completed. Please try again.";
1460
1490
  onErrorChange?.(message);
@@ -1480,7 +1510,8 @@ function PayPalButtonInner({
1480
1510
  }
1481
1511
  beforeClickRef.current = {
1482
1512
  accountPatch: beforeClick.accountPatch,
1483
- sessionId: beforeClick.sessionId
1513
+ sessionId: beforeClick.sessionId,
1514
+ nonce: beforeClick.nonce
1484
1515
  };
1485
1516
  onButtonClick?.("paypal");
1486
1517
  event.resolve();
@@ -1497,10 +1528,12 @@ function PayPalButtonInner({
1497
1528
  }
1498
1529
  prepared = {
1499
1530
  accountPatch: beforeClick.accountPatch,
1500
- sessionId: beforeClick.sessionId
1531
+ sessionId: beforeClick.sessionId,
1532
+ nonce: beforeClick.nonce
1501
1533
  };
1502
1534
  }
1503
1535
  const effectiveSessionId = prepared?.sessionId ?? sessionId;
1536
+ const effectiveNonce = prepared?.nonce ?? nonce;
1504
1537
  const effectiveEmail = prepared?.accountPatch?.email ?? email;
1505
1538
  try {
1506
1539
  setSubmitting(true);
@@ -1516,9 +1549,11 @@ function PayPalButtonInner({
1516
1549
  event.paymentFailed({ reason: "fail", message });
1517
1550
  return;
1518
1551
  }
1552
+ const intentHeaders = { "Content-Type": "application/json" };
1553
+ if (effectiveNonce) intentHeaders["x-checkout-session-token"] = effectiveNonce;
1519
1554
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
1520
1555
  method: "POST",
1521
- headers: { "Content-Type": "application/json" },
1556
+ headers: intentHeaders,
1522
1557
  body: JSON.stringify({
1523
1558
  sessionId: effectiveSessionId,
1524
1559
  email: effectiveEmail,
@@ -1547,12 +1582,29 @@ function PayPalButtonInner({
1547
1582
  if (paymentMethod?.id) {
1548
1583
  confirmParams["payment_method"] = paymentMethod.id;
1549
1584
  }
1585
+ if (typeof window !== "undefined") {
1586
+ try {
1587
+ localStorage.setItem(PAYPAL_RESUME_KEY, JSON.stringify({
1588
+ clientSecret: intentClientSecret,
1589
+ sessionId: effectiveSessionId,
1590
+ nonce: effectiveNonce,
1591
+ accountPatch: prepared?.accountPatch
1592
+ }));
1593
+ } catch {
1594
+ }
1595
+ }
1550
1596
  const { error: confirmError, paymentIntent } = await stripe.confirmPayment({
1551
1597
  clientSecret: intentClientSecret,
1552
1598
  confirmParams,
1553
1599
  redirect: "if_required"
1554
1600
  });
1555
1601
  if (confirmError) {
1602
+ if (typeof window !== "undefined") {
1603
+ try {
1604
+ localStorage.removeItem(PAYPAL_RESUME_KEY);
1605
+ } catch {
1606
+ }
1607
+ }
1556
1608
  const message = confirmError.message ?? "PayPal payment failed.";
1557
1609
  onErrorChange?.(message);
1558
1610
  onDecline?.(buildDeclineEvent("paypal", message, {
@@ -1560,6 +1612,12 @@ function PayPalButtonInner({
1560
1612
  }));
1561
1613
  return;
1562
1614
  }
1615
+ if (typeof window !== "undefined") {
1616
+ try {
1617
+ localStorage.removeItem(PAYPAL_RESUME_KEY);
1618
+ } catch {
1619
+ }
1620
+ }
1563
1621
  const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
1564
1622
  onTokenizedBody({
1565
1623
  id: confirmedPmId ?? paymentIntent?.id,
@@ -1568,7 +1626,8 @@ function PayPalButtonInner({
1568
1626
  isPaypal: true
1569
1627
  }, {
1570
1628
  accountPatch: prepared?.accountPatch,
1571
- sessionId: effectiveSessionId
1629
+ sessionId: effectiveSessionId,
1630
+ nonce: effectiveNonce
1572
1631
  });
1573
1632
  return;
1574
1633
  } catch (err) {
@@ -1576,7 +1635,7 @@ function PayPalButtonInner({
1576
1635
  } finally {
1577
1636
  setSubmitting(false);
1578
1637
  }
1579
- }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]);
1638
+ }, [stripe, elements, sessionId, nonce, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]);
1580
1639
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1581
1640
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1582
1641
  ExpressCheckoutReadySwap,
@@ -1616,6 +1675,7 @@ function PayPalButtonInner({
1616
1675
  }
1617
1676
  function WalletButtonInner({
1618
1677
  sessionId,
1678
+ nonce,
1619
1679
  email,
1620
1680
  billingApiUrl,
1621
1681
  expressMethods,
@@ -1652,11 +1712,13 @@ function WalletButtonInner({
1652
1712
  }
1653
1713
  prepared = {
1654
1714
  accountPatch: beforeClick.accountPatch,
1655
- sessionId: beforeClick.sessionId
1715
+ sessionId: beforeClick.sessionId,
1716
+ nonce: beforeClick.nonce
1656
1717
  };
1657
1718
  }
1658
1719
  const method = buttonMethod;
1659
1720
  const effectiveSessionId = prepared?.sessionId ?? sessionId;
1721
+ const effectiveNonce = prepared?.nonce ?? nonce;
1660
1722
  const effectiveEmail = prepared?.accountPatch?.email ?? email;
1661
1723
  try {
1662
1724
  setSubmitting(true);
@@ -1674,9 +1736,11 @@ function WalletButtonInner({
1674
1736
  if (!effectiveSessionId || !effectiveEmail) {
1675
1737
  throw new Error("Missing sessionId or email for wallet payment");
1676
1738
  }
1739
+ const intentHeaders = { "Content-Type": "application/json" };
1740
+ if (effectiveNonce) intentHeaders["x-checkout-session-token"] = effectiveNonce;
1677
1741
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
1678
1742
  method: "POST",
1679
- headers: { "Content-Type": "application/json" },
1743
+ headers: intentHeaders,
1680
1744
  body: JSON.stringify({
1681
1745
  sessionId: effectiveSessionId,
1682
1746
  email: effectiveEmail,
@@ -1715,7 +1779,8 @@ function WalletButtonInner({
1715
1779
  threeDSecureActionResultTokenId: paymentIntent?.id
1716
1780
  }, {
1717
1781
  accountPatch: prepared?.accountPatch,
1718
- sessionId: effectiveSessionId
1782
+ sessionId: effectiveSessionId,
1783
+ nonce: effectiveNonce
1719
1784
  });
1720
1785
  } catch (err) {
1721
1786
  onErrorChange?.(err instanceof Error ? err.message : "Wallet payment failed. Please try again.");
@@ -1723,7 +1788,7 @@ function WalletButtonInner({
1723
1788
  setSubmitting(false);
1724
1789
  }
1725
1790
  },
1726
- [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]
1791
+ [stripe, elements, sessionId, nonce, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]
1727
1792
  );
1728
1793
  const expressMethodMap = (0, import_react8.useMemo)(() => {
1729
1794
  const allKeys = ["applePay", "googlePay", "paypal", "link", "amazonPay", "klarna"];
@@ -1769,7 +1834,8 @@ function WalletButtonInner({
1769
1834
  }
1770
1835
  beforeClickRef.current = {
1771
1836
  accountPatch: beforeClick.accountPatch,
1772
- sessionId: beforeClick.sessionId
1837
+ sessionId: beforeClick.sessionId,
1838
+ nonce: beforeClick.nonce
1773
1839
  };
1774
1840
  onButtonClick?.(lastWalletMethodRef.current);
1775
1841
  event.resolve();
@@ -1924,6 +1990,7 @@ function StripeMethodButton({
1924
1990
  function StripeMethodInlineForm({
1925
1991
  method,
1926
1992
  sessionId,
1993
+ nonce,
1927
1994
  email,
1928
1995
  billingName,
1929
1996
  billingApiUrl,
@@ -1957,6 +2024,7 @@ function StripeMethodInlineForm({
1957
2024
  }
1958
2025
  onButtonClick?.("card");
1959
2026
  const effectiveSessionId = beforeClick.sessionId ?? sessionId;
2027
+ const effectiveNonce = beforeClick.nonce ?? nonce;
1960
2028
  const effectiveEmail = beforeClick.accountPatch?.email ?? email;
1961
2029
  try {
1962
2030
  onErrorChange?.(null);
@@ -1974,9 +2042,11 @@ function StripeMethodInlineForm({
1974
2042
  if (!effectiveSessionId || !effectiveEmail) {
1975
2043
  throw new Error("Missing sessionId or email for payment.");
1976
2044
  }
2045
+ const intentHeaders = { "Content-Type": "application/json" };
2046
+ if (effectiveNonce) intentHeaders["x-checkout-session-token"] = effectiveNonce;
1977
2047
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
1978
2048
  method: "POST",
1979
- headers: { "Content-Type": "application/json" },
2049
+ headers: intentHeaders,
1980
2050
  body: JSON.stringify({
1981
2051
  sessionId: effectiveSessionId,
1982
2052
  email: effectiveEmail,
@@ -1996,7 +2066,10 @@ function StripeMethodInlineForm({
1996
2066
  if (typeof window !== "undefined") {
1997
2067
  try {
1998
2068
  localStorage.setItem(STRIPE_RESUME_KEY, JSON.stringify({
2069
+ clientSecret: intentClientSecret,
1999
2070
  sessionId: effectiveSessionId,
2071
+ nonce: effectiveNonce,
2072
+ accountPatch: beforeClick.accountPatch,
2000
2073
  paymentMethodId: paymentMethod.id,
2001
2074
  paymentMethodType: paymentMethod.type ?? method,
2002
2075
  gateway: "stripe"
@@ -2053,7 +2126,8 @@ function StripeMethodInlineForm({
2053
2126
  paymentMethodType: paymentMethod.type ?? method
2054
2127
  }, {
2055
2128
  accountPatch: beforeClick.accountPatch,
2056
- sessionId: effectiveSessionId
2129
+ sessionId: effectiveSessionId,
2130
+ nonce: effectiveNonce
2057
2131
  });
2058
2132
  } catch (err) {
2059
2133
  onErrorChange?.(err instanceof Error ? err.message : "Payment failed. Please try again.");
@@ -2069,6 +2143,7 @@ function StripeMethodInlineForm({
2069
2143
  runBeforeButtonClick,
2070
2144
  onButtonClick,
2071
2145
  sessionId,
2146
+ nonce,
2072
2147
  email,
2073
2148
  baseUrl,
2074
2149
  method,
@@ -2129,6 +2204,7 @@ function StripeMethodInlineForm({
2129
2204
  }
2130
2205
  function StripePaymentElementInner({
2131
2206
  sessionId,
2207
+ nonce,
2132
2208
  email,
2133
2209
  billingName,
2134
2210
  billingApiUrl,
@@ -2174,15 +2250,18 @@ function StripePaymentElementInner({
2174
2250
  }
2175
2251
  onButtonClick?.("card");
2176
2252
  const effectiveSessionId = beforeClick.sessionId ?? sessionId;
2253
+ const effectiveNonce = beforeClick.nonce ?? nonce;
2177
2254
  const effectiveEmail = beforeClick.accountPatch?.email ?? email;
2178
2255
  try {
2179
2256
  onErrorChange?.(null);
2180
2257
  if (!effectiveSessionId || !effectiveEmail) {
2181
2258
  throw new Error("Missing sessionId or email for payment.");
2182
2259
  }
2260
+ const intentHeaders = { "Content-Type": "application/json" };
2261
+ if (effectiveNonce) intentHeaders["x-checkout-session-token"] = effectiveNonce;
2183
2262
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
2184
2263
  method: "POST",
2185
- headers: { "Content-Type": "application/json" },
2264
+ headers: intentHeaders,
2186
2265
  body: JSON.stringify({
2187
2266
  sessionId: effectiveSessionId,
2188
2267
  email: effectiveEmail,
@@ -2206,7 +2285,10 @@ function StripePaymentElementInner({
2206
2285
  if (typeof window !== "undefined") {
2207
2286
  try {
2208
2287
  localStorage.setItem(STRIPE_RESUME_KEY, JSON.stringify({
2288
+ clientSecret: intentClientSecret,
2209
2289
  sessionId: effectiveSessionId,
2290
+ nonce: effectiveNonce,
2291
+ accountPatch: beforeClick.accountPatch,
2210
2292
  paymentMethodType: method,
2211
2293
  gateway: "stripe"
2212
2294
  }));
@@ -2271,7 +2353,8 @@ function StripePaymentElementInner({
2271
2353
  paymentMethodType: method
2272
2354
  }, {
2273
2355
  accountPatch: beforeClick.accountPatch,
2274
- sessionId: effectiveSessionId
2356
+ sessionId: effectiveSessionId,
2357
+ nonce: effectiveNonce
2275
2358
  });
2276
2359
  } catch (err) {
2277
2360
  onErrorChange?.(err instanceof Error ? err.message : "Payment failed. Please try again.");
@@ -2285,6 +2368,7 @@ function StripePaymentElementInner({
2285
2368
  runBeforeButtonClick,
2286
2369
  onButtonClick,
2287
2370
  sessionId,
2371
+ nonce,
2288
2372
  email,
2289
2373
  baseUrl,
2290
2374
  billingName,
@@ -2376,6 +2460,7 @@ function StripePaymentElementInner({
2376
2460
  }
2377
2461
  function SplitCardFormInner({
2378
2462
  sessionId,
2463
+ nonce,
2379
2464
  billingApiUrl,
2380
2465
  email,
2381
2466
  userId,
@@ -2685,19 +2770,24 @@ function SplitCardFormInner({
2685
2770
  const applyInlineSessionPatch = (0, import_react8.useCallback)(
2686
2771
  (patch, method) => {
2687
2772
  if (!checkout.applyInlineSessionPatch) {
2688
- return Promise.resolve({ error: null, sessionId });
2773
+ return Promise.resolve({ error: null, sessionId, nonce });
2689
2774
  }
2690
2775
  return checkout.applyInlineSessionPatch(patch).then((result) => ({
2691
2776
  error: null,
2692
- sessionId: result.sessionId || sessionId
2777
+ sessionId: result.sessionId || sessionId,
2778
+ // A freshly bootstrapped session carries its own nonce on
2779
+ // `session.clientSecret`. Pair it with the new sessionId so
2780
+ // downstream continuation calls don't send the new id with the
2781
+ // prior session-bound token.
2782
+ nonce: result.session?.clientSecret || nonce
2693
2783
  })).catch((err) => {
2694
2784
  const floPayErr = normalizeBeforeButtonClickError(method, err);
2695
2785
  updateError(floPayErr.message);
2696
2786
  onError?.(floPayErr);
2697
- return { error: floPayErr, sessionId };
2787
+ return { error: floPayErr, sessionId, nonce };
2698
2788
  });
2699
2789
  },
2700
- [checkout.applyInlineSessionPatch, onError, sessionId, updateError]
2790
+ [checkout.applyInlineSessionPatch, nonce, onError, sessionId, updateError]
2701
2791
  );
2702
2792
  const runBeforeButtonClick = (0, import_react8.useCallback)(async (method) => {
2703
2793
  if (!onBeforeButtonClick) return { proceed: true };
@@ -2724,7 +2814,8 @@ function SplitCardFormInner({
2724
2814
  return {
2725
2815
  proceed: true,
2726
2816
  accountPatch: result.account,
2727
- sessionId: patchResult.sessionId
2817
+ sessionId: patchResult.sessionId,
2818
+ nonce: patchResult.nonce
2728
2819
  };
2729
2820
  }
2730
2821
  return { proceed: true };
@@ -2743,6 +2834,7 @@ function SplitCardFormInner({
2743
2834
  setOverlayStatus("processing");
2744
2835
  updateError(null);
2745
2836
  const effectiveSessionId = overrides?.sessionId ?? sessionId;
2837
+ const effectiveNonce = overrides?.nonce ?? nonce;
2746
2838
  const effectiveAccount = mergeAccountPatch(resolvedAccount, overrides?.accountPatch);
2747
2839
  const resolvedCompletionPaymentMethodId = overrides?.completionPaymentMethodId ?? resolveTokenizedPaymentMethodId(tokenizedBody);
2748
2840
  const requestTokenizedBody = tokenizedBody.originalPaymentMethodId ? { ...tokenizedBody, originalPaymentMethodId: void 0 } : tokenizedBody;
@@ -2750,6 +2842,7 @@ function SplitCardFormInner({
2750
2842
  const api = new import_js2.PaymentAPI(baseUrl);
2751
2843
  const response = await api.processPayment(effectiveAccount.userId ?? "", {
2752
2844
  sessionId: effectiveSessionId,
2845
+ nonce: effectiveNonce,
2753
2846
  tokenizedData: requestTokenizedBody,
2754
2847
  accountData: {
2755
2848
  userId: effectiveAccount.userId ?? "",
@@ -2853,6 +2946,12 @@ function SplitCardFormInner({
2853
2946
  type: "card",
2854
2947
  threeDSecureActionResultTokenId: result.paymentIntentId
2855
2948
  }, {
2949
+ // Preserve the session/nonce/account overrides resolved
2950
+ // above so the retry talks to the same (possibly
2951
+ // freshly bootstrapped) session as the original attempt.
2952
+ accountPatch: overrides?.accountPatch,
2953
+ sessionId: effectiveSessionId,
2954
+ nonce: effectiveNonce,
2856
2955
  completionPaymentMethodId: resolvedCompletionPaymentMethodId
2857
2956
  });
2858
2957
  }
@@ -2936,7 +3035,7 @@ function SplitCardFormInner({
2936
3035
  processingRef.current = false;
2937
3036
  }
2938
3037
  },
2939
- [baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, paypalFlopay, onComplete, onError, updateError, emitDecline]
3038
+ [baseUrl, sessionId, nonce, resolvedAccount, fullName, chv, flopay, paypalFlopay, onComplete, onError, updateError, emitDecline]
2940
3039
  );
2941
3040
  const dispatchTokenizedBody = (0, import_react8.useCallback)(
2942
3041
  (tokenizedBody, overrides) => {
@@ -2994,7 +3093,17 @@ function SplitCardFormInner({
2994
3093
  }
2995
3094
  };
2996
3095
  const payload = readPayload(STRIPE_RESUME_KEY) ?? readPayload(LEGACY_WALLET_RESUME_KEY);
2997
- if (payload?.sessionId && payload.sessionId !== sessionId) return;
3096
+ let persistedOverrides;
3097
+ if (payload?.clientSecret) {
3098
+ if (payload.clientSecret !== clientSecret) return;
3099
+ persistedOverrides = {
3100
+ ...payload.accountPatch ? { accountPatch: payload.accountPatch } : {},
3101
+ ...payload.sessionId ? { sessionId: payload.sessionId } : {},
3102
+ ...payload.nonce ? { nonce: payload.nonce } : {}
3103
+ };
3104
+ } else if (payload?.sessionId && payload.sessionId !== sessionId) {
3105
+ return;
3106
+ }
2998
3107
  stripeResumeAttemptedRef.current = true;
2999
3108
  localStorage.removeItem(STRIPE_RESUME_KEY);
3000
3109
  localStorage.removeItem(LEGACY_WALLET_RESUME_KEY);
@@ -3041,7 +3150,7 @@ function SplitCardFormInner({
3041
3150
  threeDSecureActionResultTokenId: paymentIntent.id,
3042
3151
  gateway: payload?.gateway ?? "stripe",
3043
3152
  paymentMethodType
3044
- });
3153
+ }, persistedOverrides);
3045
3154
  })();
3046
3155
  }, [sessionId, dispatchTokenizedBody, flopay, updateError, emitDecline]);
3047
3156
  const handleSubmit = (0, import_react8.useCallback)(
@@ -3112,9 +3221,11 @@ function SplitCardFormInner({
3112
3221
  if (!sessionId || !resolvedAccount.email) {
3113
3222
  throw new import_shared6.FloPayError("Missing sessionId or email", "validation_error");
3114
3223
  }
3224
+ const intentHeaders = { "Content-Type": "application/json" };
3225
+ if (nonce) intentHeaders["x-checkout-session-token"] = nonce;
3115
3226
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
3116
3227
  method: "POST",
3117
- headers: { "Content-Type": "application/json" },
3228
+ headers: intentHeaders,
3118
3229
  body: JSON.stringify({
3119
3230
  sessionId,
3120
3231
  email: resolvedAccount.email,
@@ -3182,7 +3293,7 @@ function SplitCardFormInner({
3182
3293
  }
3183
3294
  }
3184
3295
  },
3185
- [flopay, elements, isSubmitting, sessionId, resolvedAccount.email, baseUrl, isSelfContained, dispatchTokenizedBody, onButtonClick, onError, updateError, emitDecline, layout]
3296
+ [flopay, elements, isSubmitting, sessionId, nonce, resolvedAccount.email, baseUrl, isSelfContained, dispatchTokenizedBody, onButtonClick, onError, updateError, emitDecline, layout]
3186
3297
  );
3187
3298
  const isReady = flopay !== null && elements !== null;
3188
3299
  if (!isReady) {
@@ -3714,6 +3825,7 @@ function SplitCardFormInner({
3714
3825
  DirectPayPalButton,
3715
3826
  {
3716
3827
  sessionId,
3828
+ nonce,
3717
3829
  billingApiUrl: resolvedBillingApiUrl,
3718
3830
  email: resolvedAccount.email,
3719
3831
  clientId: directPaypal.clientId,
@@ -3739,6 +3851,7 @@ function SplitCardFormInner({
3739
3851
  PayPalButtonInner,
3740
3852
  {
3741
3853
  sessionId,
3854
+ nonce,
3742
3855
  email: resolvedAccount.email,
3743
3856
  billingApiUrl: resolvedBillingApiUrl,
3744
3857
  onTokenizedBody: dispatchTokenizedBody,
@@ -3755,6 +3868,7 @@ function SplitCardFormInner({
3755
3868
  WalletButtonInner,
3756
3869
  {
3757
3870
  sessionId,
3871
+ nonce,
3758
3872
  email: resolvedAccount.email,
3759
3873
  billingApiUrl: resolvedBillingApiUrl,
3760
3874
  expressMethods: walletExpressMethods,
@@ -3771,6 +3885,7 @@ function SplitCardFormInner({
3771
3885
  StripePaymentElementInner,
3772
3886
  {
3773
3887
  sessionId,
3888
+ nonce,
3774
3889
  email: resolvedAccount.email,
3775
3890
  billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
3776
3891
  billingApiUrl: resolvedBillingApiUrl,
@@ -3946,6 +4061,7 @@ function SplitCardFormInner({
3946
4061
  {
3947
4062
  method: expandedApmMethod,
3948
4063
  sessionId,
4064
+ nonce,
3949
4065
  email: resolvedAccount.email,
3950
4066
  billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
3951
4067
  billingApiUrl: resolvedBillingApiUrl,
@@ -4044,6 +4160,7 @@ function SplitCardFormInner({
4044
4160
  {
4045
4161
  method: expandedApmMethod,
4046
4162
  sessionId,
4163
+ nonce,
4047
4164
  email: resolvedAccount.email,
4048
4165
  billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
4049
4166
  billingApiUrl: resolvedBillingApiUrl,
@@ -4092,6 +4209,7 @@ function SplitCardFormInner({
4092
4209
  DirectPayPalButton,
4093
4210
  {
4094
4211
  sessionId,
4212
+ nonce,
4095
4213
  billingApiUrl: resolvedBillingApiUrl,
4096
4214
  email: resolvedAccount.email,
4097
4215
  clientId: directPaypal.clientId,
@@ -4117,6 +4235,7 @@ function SplitCardFormInner({
4117
4235
  PayPalButtonInner,
4118
4236
  {
4119
4237
  sessionId,
4238
+ nonce,
4120
4239
  email: resolvedAccount.email,
4121
4240
  billingApiUrl: resolvedBillingApiUrl,
4122
4241
  onTokenizedBody: dispatchTokenizedBody,
@@ -4133,6 +4252,7 @@ function SplitCardFormInner({
4133
4252
  WalletButtonInner,
4134
4253
  {
4135
4254
  sessionId,
4255
+ nonce,
4136
4256
  email: resolvedAccount.email,
4137
4257
  billingApiUrl: resolvedBillingApiUrl,
4138
4258
  expressMethods: walletExpressMethods,
@@ -4149,6 +4269,7 @@ function SplitCardFormInner({
4149
4269
  StripePaymentElementInner,
4150
4270
  {
4151
4271
  sessionId,
4272
+ nonce,
4152
4273
  email: resolvedAccount.email,
4153
4274
  billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
4154
4275
  billingApiUrl: resolvedBillingApiUrl,
@@ -4292,6 +4413,7 @@ function getRedirectTokenFromProcessResponse(json, options) {
4292
4413
  async function recover3DSRedirectResult({
4293
4414
  billingApiUrl,
4294
4415
  sessionId,
4416
+ nonce,
4295
4417
  responseJson
4296
4418
  }) {
4297
4419
  const directToken = getRedirectTokenFromProcessResponse(responseJson, {
@@ -4308,7 +4430,7 @@ async function recover3DSRedirectResult({
4308
4430
  }
4309
4431
  try {
4310
4432
  const api = new import_js3.PaymentAPI(billingApiUrl);
4311
- const unified = await api.getUnifiedCheckoutSession(sessionId);
4433
+ const unified = await api.getUnifiedCheckoutSession(sessionId, nonce);
4312
4434
  const refreshedToken = unified.data.stripe?.clientSecret;
4313
4435
  if (isStripePaymentIntentClientSecret(refreshedToken)) {
4314
4436
  return {
@@ -4338,6 +4460,7 @@ async function processSavedPaymentForMode({
4338
4460
  const api = new import_js3.PaymentAPI(baseUrl);
4339
4461
  const response = await retryOnceOnFetchFailure(() => api.processPayment(customerId, {
4340
4462
  sessionId: resolvedSessionId,
4463
+ nonce: session.clientSecret,
4341
4464
  tokenizedData,
4342
4465
  accountData: {
4343
4466
  userId: customerId,
@@ -4380,6 +4503,7 @@ async function processSavedPaymentForMode({
4380
4503
  const recoveredRedirect = await recover3DSRedirectResult({
4381
4504
  billingApiUrl: baseUrl,
4382
4505
  sessionId: resolvedSessionId,
4506
+ nonce: session.clientSecret,
4383
4507
  responseJson: json
4384
4508
  });
4385
4509
  if (recoveredRedirect) {
@@ -5517,6 +5641,8 @@ function FloPayCheckout({
5517
5641
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5518
5642
  "div",
5519
5643
  {
5644
+ role: "alert",
5645
+ "data-testid": "flopay-load-error",
5520
5646
  style: {
5521
5647
  padding: 24,
5522
5648
  textAlign: "center",
@@ -5572,6 +5698,7 @@ function FloPayCheckout({
5572
5698
  DirectPayPalButton,
5573
5699
  {
5574
5700
  sessionId: activeSessionId,
5701
+ nonce: session.clientSecret || void 0,
5575
5702
  billingApiUrl: resolvedBillingUrl,
5576
5703
  email: session.customer?.email,
5577
5704
  clientId: directPaypalConfig.clientId,
@@ -5657,6 +5784,7 @@ function FloPayCheckout({
5657
5784
  SessionInjector,
5658
5785
  {
5659
5786
  sessionId: activeSessionId,
5787
+ nonce: session?.clientSecret || void 0,
5660
5788
  billingApiUrl: resolvedBillingUrl,
5661
5789
  session,
5662
5790
  children
@@ -5666,6 +5794,7 @@ function FloPayCheckout({
5666
5794
  SplitCardForm,
5667
5795
  {
5668
5796
  sessionId: activeSessionId,
5797
+ nonce: session?.clientSecret || void 0,
5669
5798
  email: session?.customer?.email,
5670
5799
  userId: session?.customer?.id,
5671
5800
  firstName: session?.customer?.firstName,
@@ -5716,6 +5845,7 @@ function FloPayCheckout({
5716
5845
  }
5717
5846
  function SessionInjector({
5718
5847
  sessionId,
5848
+ nonce,
5719
5849
  billingApiUrl,
5720
5850
  session,
5721
5851
  children
@@ -5725,6 +5855,7 @@ function SessionInjector({
5725
5855
  const existing = child.props;
5726
5856
  const injected = {};
5727
5857
  if (!existing.sessionId) injected.sessionId = sessionId;
5858
+ if (!existing.nonce && nonce) injected.nonce = nonce;
5728
5859
  if (!existing.billingApiUrl) injected.billingApiUrl = billingApiUrl;
5729
5860
  if (session?.customer) {
5730
5861
  if (!existing.email) injected.email = session.customer.email;
@@ -5974,6 +6105,7 @@ var CheckoutForm = (0, import_react10.forwardRef)(
5974
6105
  );
5975
6106
  function CheckoutFormInner({
5976
6107
  sessionId,
6108
+ nonce,
5977
6109
  billingApiUrl,
5978
6110
  email,
5979
6111
  userId,
@@ -6028,6 +6160,7 @@ function CheckoutFormInner({
6028
6160
  const api = new import_js5.PaymentAPI(baseUrl);
6029
6161
  const response = await api.processPayment(userId ?? "", {
6030
6162
  sessionId,
6163
+ nonce,
6031
6164
  tokenizedData: requestTokenizedBody,
6032
6165
  accountData: {
6033
6166
  userId: userId ?? "",
@@ -6127,7 +6260,7 @@ function CheckoutFormInner({
6127
6260
  setProcessing(false);
6128
6261
  }
6129
6262
  },
6130
- [baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, paypalFlopay, onComplete, onError, onDecline, updateError, emitDecline]
6263
+ [baseUrl, sessionId, nonce, userId, email, firstName, lastName, chv, flopay, paypalFlopay, onComplete, onError, onDecline, updateError, emitDecline]
6131
6264
  );
6132
6265
  const dispatchTokenizedBody = (0, import_react10.useCallback)(
6133
6266
  (tokenizedBody) => {
@@ -6210,9 +6343,11 @@ function CheckoutFormInner({
6210
6343
  if (!sessionId || !email) {
6211
6344
  throw new import_shared9.FloPayError("Missing sessionId or email", "validation_error");
6212
6345
  }
6346
+ const intentHeaders = { "Content-Type": "application/json" };
6347
+ if (nonce) intentHeaders["x-checkout-session-token"] = nonce;
6213
6348
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
6214
6349
  method: "POST",
6215
- headers: { "Content-Type": "application/json" },
6350
+ headers: intentHeaders,
6216
6351
  body: JSON.stringify({
6217
6352
  sessionId,
6218
6353
  email,
@@ -6271,7 +6406,7 @@ function CheckoutFormInner({
6271
6406
  }
6272
6407
  }
6273
6408
  },
6274
- [flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError, emitDecline]
6409
+ [flopay, elements, isSubmitting, sessionId, nonce, email, firstName, lastName, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError, emitDecline]
6275
6410
  );
6276
6411
  const isReady = flopay !== null && elements !== null;
6277
6412
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
@@ -6309,6 +6444,7 @@ var import_react11 = require("react");
6309
6444
  var import_jsx_runtime9 = require("react/jsx-runtime");
6310
6445
  function PayPalButton({
6311
6446
  sessionId,
6447
+ nonce,
6312
6448
  billingApiUrl,
6313
6449
  email,
6314
6450
  userId,
@@ -6333,6 +6469,7 @@ function PayPalButton({
6333
6469
  const api = new import_js6.PaymentAPI(baseUrl);
6334
6470
  const response = await api.processPayment(userId ?? "", {
6335
6471
  sessionId,
6472
+ nonce,
6336
6473
  tokenizedData: tokenizedBody,
6337
6474
  accountData: {
6338
6475
  userId: userId ?? "",
@@ -6352,7 +6489,7 @@ function PayPalButton({
6352
6489
  onErrorChange?.(err instanceof Error ? err.message : "An unexpected error occurred");
6353
6490
  }
6354
6491
  },
6355
- [baseUrl, sessionId, userId, email, firstName, lastName, chv, onComplete, onErrorChange]
6492
+ [baseUrl, sessionId, nonce, userId, email, firstName, lastName, chv, onComplete, onErrorChange]
6356
6493
  );
6357
6494
  const dispatchTokenizedBody = (0, import_react11.useCallback)(
6358
6495
  (body) => {
@@ -6423,6 +6560,7 @@ function PayPalButton({
6423
6560
  const result = await flopay.confirmPayPalPayment({
6424
6561
  billingApiUrl: baseUrl,
6425
6562
  sessionId,
6563
+ nonce,
6426
6564
  email,
6427
6565
  returnUrl: window.location.href
6428
6566
  });
@@ -6443,7 +6581,7 @@ function PayPalButton({
6443
6581
  } finally {
6444
6582
  setSubmitting(false);
6445
6583
  }
6446
- }, [flopay, elements, sessionId, email, baseUrl, dispatchTokenizedBody, onErrorChange]);
6584
+ }, [flopay, elements, sessionId, nonce, email, baseUrl, dispatchTokenizedBody, onErrorChange]);
6447
6585
  if (!flopay || !elements) {
6448
6586
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { height: 45, background: "#f0f0f0", borderRadius: 6, animation: "pulse 1.5s infinite" } });
6449
6587
  }