@flopay/react 1.1.6 → 1.2.0

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.mjs CHANGED
@@ -234,6 +234,7 @@ var AddressElement = createElementComponent("address", "AddressElement");
234
234
  // src/split-card-form.tsx
235
235
  import {
236
236
  ExpressCheckoutElement,
237
+ PaymentElement as PaymentElement2,
237
238
  Elements as StripeElements,
238
239
  useElements as useStripeElements,
239
240
  useStripe as useStripeRaw
@@ -246,10 +247,19 @@ import {
246
247
  COUNTRY_OPTIONS,
247
248
  resolveAVSConfig,
248
249
  isAVSFieldVisible,
249
- getStateFromPostalCode
250
+ getStateFromPostalCode,
251
+ filterStripeMethodsByCurrency,
252
+ filterStripeMethodsByCountry,
253
+ filterStripeMethodsByAmount,
254
+ getStripeMethodDisplayName,
255
+ hasVendoredStripeMethodLogo,
256
+ needsStripeMethodExplicitConfirm,
257
+ resolveStripeMethodBrandVariant,
258
+ partitionStripeMethods,
259
+ stripeExpressMethodToOptionKey
250
260
  } from "@flopay/shared";
251
261
  import { PaymentAPI as PaymentAPI2 } from "@flopay/js";
252
- import { forwardRef, useCallback, useContext as useContext3, useEffect as useEffect4, useImperativeHandle, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
262
+ import React6, { forwardRef, useCallback, useContext as useContext3, useEffect as useEffect4, useImperativeHandle, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
253
263
 
254
264
  // src/hooks.ts
255
265
  import { useContext as useContext2 } from "react";
@@ -1223,9 +1233,11 @@ ${debugLines.join("\n")}`
1223
1233
  // src/split-card-form.tsx
1224
1234
  import { FloPayError as FloPayError3, resolveTheme } from "@flopay/shared";
1225
1235
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1226
- var WALLET_RESUME_KEY = "flopay_wallet_resume";
1236
+ var STRIPE_RESUME_KEY = "flopay_stripe_resume";
1237
+ var LEGACY_WALLET_RESUME_KEY = "flopay_wallet_resume";
1227
1238
  var FLOPAY_KEYFRAMES = `
1228
1239
  .paypal-buttons { margin: 0 !important; vertical-align: top !important; }
1240
+ @keyframes flopay-spin { to { transform: rotate(360deg); } }
1229
1241
  @keyframes flopay-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
1230
1242
  @keyframes flopay-fade-in { 0% { opacity: 0; } 100% { opacity: 1; } }
1231
1243
  @keyframes flopay-buttons-enter {
@@ -1246,6 +1258,25 @@ var FLOPAY_KEYFRAMES = `
1246
1258
  }
1247
1259
  `;
1248
1260
  var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT = 44;
1261
+ function derivePrimaryTileStyle(opts) {
1262
+ if (!opts.themeBundle) {
1263
+ return {
1264
+ backgroundColor: "white",
1265
+ color: "#262833",
1266
+ border: "1px solid #d1d5db",
1267
+ borderRadius: "8px",
1268
+ boxShadow: "0 1px 2px rgba(0,0,0,0.04)"
1269
+ };
1270
+ }
1271
+ const submit = opts.submitButtonStyle ?? {};
1272
+ return {
1273
+ backgroundColor: submit.backgroundColor ?? opts.resolvedPrimaryColor,
1274
+ color: submit.color ?? "white",
1275
+ border: submit.border ?? "none",
1276
+ borderRadius: submit.borderRadius ?? opts.resolvedBorderRadius,
1277
+ boxShadow: submit.boxShadow
1278
+ };
1279
+ }
1249
1280
  var BUTTONS_PANEL_HIDDEN_STYLE = {
1250
1281
  visibility: "hidden",
1251
1282
  position: "absolute",
@@ -1291,6 +1322,7 @@ function resolveExpressCheckoutLoadState(event, methods) {
1291
1322
  function ExpressCheckoutReadySwap({
1292
1323
  state,
1293
1324
  placeholderTestId,
1325
+ borderRadius = 8,
1294
1326
  children
1295
1327
  }) {
1296
1328
  if (state === "unavailable" || state === "load_error") return null;
@@ -1304,7 +1336,7 @@ function ExpressCheckoutReadySwap({
1304
1336
  position: "absolute",
1305
1337
  inset: 0,
1306
1338
  height: 44,
1307
- borderRadius: 8,
1339
+ borderRadius,
1308
1340
  background: "#e5e7eb",
1309
1341
  animation: state === "ready" ? void 0 : "flopay-pulse 1.5s ease-in-out infinite",
1310
1342
  opacity: state === "ready" ? 0 : 1,
@@ -1347,7 +1379,8 @@ function PayPalButtonInner({
1347
1379
  onButtonClick,
1348
1380
  onDecline,
1349
1381
  runBeforeButtonClick,
1350
- onLoadStateChange
1382
+ onLoadStateChange,
1383
+ placeholderBorderRadius
1351
1384
  }) {
1352
1385
  const stripe = useStripeRaw();
1353
1386
  const elements = useStripeElements();
@@ -1517,31 +1550,39 @@ function PayPalButtonInner({
1517
1550
  }
1518
1551
  }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]);
1519
1552
  return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1520
- /* @__PURE__ */ jsx6(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-paypal-placeholder", children: /* @__PURE__ */ jsx6(
1521
- ExpressCheckoutElement,
1553
+ /* @__PURE__ */ jsx6(
1554
+ ExpressCheckoutReadySwap,
1522
1555
  {
1523
- onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["paypal"])),
1524
- onLoadError: () => setLoadState("load_error"),
1525
- onClick: handlePayPalClick,
1526
- onConfirm: handlePayPalConfirm,
1527
- onCancel: () => {
1528
- beforeClickRef.current = null;
1529
- onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
1530
- },
1531
- options: {
1532
- buttonType: { paypal: "paypal" },
1533
- billingAddressRequired: false,
1534
- phoneNumberRequired: false,
1535
- shippingAddressRequired: false,
1536
- paymentMethods: {
1537
- applePay: "never",
1538
- googlePay: "never",
1539
- paypal: "auto",
1540
- link: "never"
1556
+ state: loadState,
1557
+ placeholderTestId: "flopay-paypal-placeholder",
1558
+ borderRadius: placeholderBorderRadius,
1559
+ children: /* @__PURE__ */ jsx6(
1560
+ ExpressCheckoutElement,
1561
+ {
1562
+ onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["paypal"])),
1563
+ onLoadError: () => setLoadState("load_error"),
1564
+ onClick: handlePayPalClick,
1565
+ onConfirm: handlePayPalConfirm,
1566
+ onCancel: () => {
1567
+ beforeClickRef.current = null;
1568
+ onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
1569
+ },
1570
+ options: {
1571
+ buttonType: { paypal: "paypal" },
1572
+ billingAddressRequired: false,
1573
+ phoneNumberRequired: false,
1574
+ shippingAddressRequired: false,
1575
+ paymentMethods: {
1576
+ applePay: "never",
1577
+ googlePay: "never",
1578
+ paypal: "auto",
1579
+ link: "never"
1580
+ }
1581
+ }
1541
1582
  }
1542
- }
1583
+ )
1543
1584
  }
1544
- ) }),
1585
+ ),
1545
1586
  submitting && /* @__PURE__ */ jsx6(ProcessingOverlay, { status: "processing" })
1546
1587
  ] });
1547
1588
  }
@@ -1549,14 +1590,14 @@ function WalletButtonInner({
1549
1590
  sessionId,
1550
1591
  email,
1551
1592
  billingApiUrl,
1552
- showApplePay = true,
1553
- showGooglePay = true,
1593
+ expressMethods,
1554
1594
  onTokenizedBody,
1555
1595
  onErrorChange,
1556
1596
  onButtonClick,
1557
1597
  onDecline,
1558
1598
  runBeforeButtonClick,
1559
- onLoadStateChange
1599
+ onLoadStateChange,
1600
+ placeholderBorderRadius
1560
1601
  }) {
1561
1602
  const stripe = useStripeRaw();
1562
1603
  const elements = useStripeElements();
@@ -1574,10 +1615,9 @@ function WalletButtonInner({
1574
1615
  const walletType = event.expressPaymentType;
1575
1616
  let prepared = beforeClickRef.current;
1576
1617
  beforeClickRef.current = null;
1618
+ const buttonMethod = walletType === "apple_pay" ? "apple_pay" : "google_pay";
1577
1619
  if (!prepared && runBeforeButtonClick) {
1578
- const beforeClick = await runBeforeButtonClick(
1579
- walletType === "apple_pay" ? "apple_pay" : "google_pay"
1580
- );
1620
+ const beforeClick = await runBeforeButtonClick(buttonMethod);
1581
1621
  if (!beforeClick.proceed) {
1582
1622
  event.paymentFailed({ reason: "fail", message: "Wallet checkout was cancelled." });
1583
1623
  return;
@@ -1587,7 +1627,7 @@ function WalletButtonInner({
1587
1627
  sessionId: beforeClick.sessionId
1588
1628
  };
1589
1629
  }
1590
- const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
1630
+ const method = buttonMethod;
1591
1631
  const effectiveSessionId = prepared?.sessionId ?? sessionId;
1592
1632
  const effectiveEmail = prepared?.accountPatch?.email ?? email;
1593
1633
  try {
@@ -1657,53 +1697,655 @@ function WalletButtonInner({
1657
1697
  },
1658
1698
  [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]
1659
1699
  );
1700
+ const expressMethodMap = useMemo3(() => {
1701
+ const allKeys = ["applePay", "googlePay", "paypal", "link", "amazonPay", "klarna"];
1702
+ const alwaysCapable = /* @__PURE__ */ new Set(["applePay", "googlePay"]);
1703
+ const enabledKeys = new Set(expressMethods.map(stripeExpressMethodToOptionKey));
1704
+ const out = {};
1705
+ for (const key of allKeys) {
1706
+ if (!enabledKeys.has(key)) {
1707
+ out[key] = "never";
1708
+ } else {
1709
+ out[key] = alwaysCapable.has(key) ? "always" : "auto";
1710
+ }
1711
+ }
1712
+ return out;
1713
+ }, [expressMethods]);
1714
+ const availableMethodKeys = useMemo3(
1715
+ () => expressMethods.map(stripeExpressMethodToOptionKey),
1716
+ [expressMethods]
1717
+ );
1660
1718
  return /* @__PURE__ */ jsxs4(Fragment2, { children: [
1661
- /* @__PURE__ */ jsx6(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-wallet-placeholder", children: /* @__PURE__ */ jsx6(
1662
- ExpressCheckoutElement,
1719
+ /* @__PURE__ */ jsx6(
1720
+ ExpressCheckoutReadySwap,
1663
1721
  {
1664
- onReady: (event) => {
1665
- setLoadState(resolveExpressCheckoutLoadState(event, ["applePay", "googlePay"]));
1666
- },
1667
- onLoadError: (e) => {
1668
- setLoadState("load_error");
1669
- },
1670
- onClick: async (event) => {
1671
- lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
1672
- const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick(lastWalletMethodRef.current) : { proceed: true };
1673
- if (!beforeClick.proceed) {
1674
- beforeClickRef.current = null;
1675
- event.reject();
1676
- return;
1722
+ state: loadState,
1723
+ placeholderTestId: "flopay-wallet-placeholder",
1724
+ borderRadius: placeholderBorderRadius,
1725
+ children: /* @__PURE__ */ jsx6(
1726
+ ExpressCheckoutElement,
1727
+ {
1728
+ onReady: (event) => {
1729
+ setLoadState(resolveExpressCheckoutLoadState(event, availableMethodKeys));
1730
+ },
1731
+ onLoadError: (_event) => {
1732
+ setLoadState("load_error");
1733
+ },
1734
+ onClick: async (event) => {
1735
+ lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
1736
+ const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick(lastWalletMethodRef.current) : { proceed: true };
1737
+ if (!beforeClick.proceed) {
1738
+ beforeClickRef.current = null;
1739
+ event.reject();
1740
+ return;
1741
+ }
1742
+ beforeClickRef.current = {
1743
+ accountPatch: beforeClick.accountPatch,
1744
+ sessionId: beforeClick.sessionId
1745
+ };
1746
+ onButtonClick?.(lastWalletMethodRef.current);
1747
+ event.resolve();
1748
+ },
1749
+ onConfirm: handleWalletConfirm,
1750
+ onCancel: () => {
1751
+ beforeClickRef.current = null;
1752
+ onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
1753
+ },
1754
+ options: {
1755
+ buttonType: { applePay: "plain", googlePay: "plain" },
1756
+ paymentMethods: expressMethodMap,
1757
+ layout: { maxColumns: 1, overflow: "never" }
1758
+ }
1677
1759
  }
1678
- beforeClickRef.current = {
1679
- accountPatch: beforeClick.accountPatch,
1680
- sessionId: beforeClick.sessionId
1681
- };
1682
- onButtonClick?.(lastWalletMethodRef.current);
1683
- event.resolve();
1760
+ )
1761
+ }
1762
+ ),
1763
+ submitting && /* @__PURE__ */ jsx6(ProcessingOverlay, { status: "processing" })
1764
+ ] });
1765
+ }
1766
+ function StripeMethodButton({
1767
+ method,
1768
+ themeId,
1769
+ submitting = false,
1770
+ disabled = false,
1771
+ highlighted = false,
1772
+ hasNextStep = false,
1773
+ onClick,
1774
+ backgroundColor,
1775
+ borderColor,
1776
+ textColor,
1777
+ borderRadius,
1778
+ fontFamily
1779
+ }) {
1780
+ const brand = resolveStripeMethodBrandVariant(method, themeId);
1781
+ const resolvedBackground = brand?.backgroundColor ?? backgroundColor ?? "#ffffff";
1782
+ const resolvedBorder = brand?.borderColor ?? borderColor ?? "#d1d5db";
1783
+ const resolvedTextColor = brand?.textColor ?? textColor ?? "#262833";
1784
+ return /* @__PURE__ */ jsx6(
1785
+ "button",
1786
+ {
1787
+ type: "button",
1788
+ "data-testid": `flopay-stripe-method-button-${method}`,
1789
+ "data-method": method,
1790
+ onClick: () => {
1791
+ if (!submitting && !disabled) onClick();
1792
+ },
1793
+ disabled: submitting || disabled,
1794
+ "aria-busy": submitting || void 0,
1795
+ style: {
1796
+ position: "relative",
1797
+ width: "100%",
1798
+ boxSizing: "border-box",
1799
+ height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT,
1800
+ padding: "0 1rem",
1801
+ backgroundColor: resolvedBackground,
1802
+ color: resolvedTextColor,
1803
+ border: `1px solid ${resolvedBorder}`,
1804
+ borderRadius: borderRadius ?? 8,
1805
+ fontSize: "0.95rem",
1806
+ fontWeight: 600,
1807
+ fontFamily,
1808
+ cursor: submitting || disabled ? "not-allowed" : "pointer",
1809
+ opacity: disabled && !submitting ? 0.55 : 1,
1810
+ // Submitting state pulses the whole button background, mirroring
1811
+ // the wallet ECE's `flopay-pulse` skeleton. This keeps the loading
1812
+ // affordance consistent between embedded wallets (Apple Pay,
1813
+ // Google Pay) and these custom APM tile buttons — same animation
1814
+ // curve, same duration, only the surface colour changes (grey for
1815
+ // the wallet skeleton vs. brand colour for an in-flight tile).
1816
+ animation: submitting ? "flopay-pulse 1.5s ease-in-out infinite" : void 0,
1817
+ display: "flex",
1818
+ alignItems: "center",
1819
+ // Always center the logo+name group. The drill-in chevron (input
1820
+ // methods) is absolutely positioned at the right edge below so it
1821
+ // doesn't pull the label off-center.
1822
+ justifyContent: "center",
1823
+ gap: 8,
1824
+ transition: "transform 0.1s, opacity 140ms ease-out, border-color 140ms ease-out",
1825
+ boxShadow: highlighted ? "0 0 0 2px rgba(74, 73, 255, 0.15)" : void 0
1826
+ },
1827
+ children: submitting ? (
1828
+ // Pulse-skeleton parity with the wallet ECE: no spinner, just the
1829
+ // status text on top of the pulsing background. Keeps the loading
1830
+ // affordance shape-equivalent across both kinds of tile.
1831
+ /* @__PURE__ */ jsx6("span", { style: { margin: "0 auto" }, children: `Connecting to ${getStripeMethodDisplayName(method)}\u2026` })
1832
+ ) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
1833
+ /* @__PURE__ */ jsxs4(
1834
+ "span",
1835
+ {
1836
+ style: {
1837
+ display: "inline-flex",
1838
+ alignItems: "center",
1839
+ // Real vendored logos are self-contained brand cards, so they
1840
+ // get an 8px gap from the label. The generated placeholder
1841
+ // monogram reads as part of the label (the tile's first letter),
1842
+ // so it sits flush (0px) against the name.
1843
+ gap: hasVendoredStripeMethodLogo(method) ? 8 : 0
1844
+ },
1845
+ children: [
1846
+ brand?.logoSvg && /* @__PURE__ */ jsx6(
1847
+ "span",
1848
+ {
1849
+ "aria-hidden": "true",
1850
+ style: {
1851
+ // 3:2 box matching the vendored datatrans logos' 120×80
1852
+ // viewBox. Each logo ships with its own white rounded-rect
1853
+ // background baked in, so the mark stays legible on any
1854
+ // brand-coloured tile without an extra chip wrapper here.
1855
+ width: 30,
1856
+ height: 20,
1857
+ flexShrink: 0,
1858
+ display: "inline-flex",
1859
+ borderRadius: 3,
1860
+ overflow: "hidden"
1861
+ },
1862
+ dangerouslySetInnerHTML: { __html: brand.logoSvg }
1863
+ }
1864
+ ),
1865
+ /* @__PURE__ */ jsx6("span", { children: getStripeMethodDisplayName(method) })
1866
+ ]
1867
+ }
1868
+ ),
1869
+ hasNextStep && /* @__PURE__ */ jsx6(
1870
+ "svg",
1871
+ {
1872
+ width: "14",
1873
+ height: "14",
1874
+ viewBox: "0 0 24 24",
1875
+ fill: "none",
1876
+ stroke: resolvedTextColor,
1877
+ strokeWidth: "2.5",
1878
+ strokeLinecap: "round",
1879
+ strokeLinejoin: "round",
1880
+ style: {
1881
+ position: "absolute",
1882
+ right: "1rem",
1883
+ top: "50%",
1884
+ transform: "translateY(-50%)",
1885
+ flexShrink: 0,
1886
+ opacity: 0.7
1887
+ },
1888
+ "aria-hidden": "true",
1889
+ children: /* @__PURE__ */ jsx6("path", { d: "M9 18l6-6-6-6" })
1890
+ }
1891
+ )
1892
+ ] })
1893
+ }
1894
+ );
1895
+ }
1896
+ function StripeMethodInlineForm({
1897
+ method,
1898
+ sessionId,
1899
+ email,
1900
+ billingName,
1901
+ billingApiUrl,
1902
+ onTokenizedBody,
1903
+ onErrorChange,
1904
+ onDecline,
1905
+ onCancel,
1906
+ runBeforeButtonClick,
1907
+ onButtonClick,
1908
+ isProcessing,
1909
+ submitButtonColor,
1910
+ submitButtonBorderRadius,
1911
+ submitButtonStyle
1912
+ }) {
1913
+ const stripe = useStripeRaw();
1914
+ const elements = useStripeElements();
1915
+ const [submitting, setSubmitting] = useState3(false);
1916
+ const submittingRef = useRef3(false);
1917
+ const [isMethodComplete, setIsMethodComplete] = useState3(false);
1918
+ const [loadState, setLoadState] = useState3("loading");
1919
+ const baseUrl = billingApiUrl.replace(/\/+$/, "");
1920
+ const handlePay = useCallback(async () => {
1921
+ if (!stripe || !elements || isProcessing || submittingRef.current || !isMethodComplete) return;
1922
+ submittingRef.current = true;
1923
+ setSubmitting(true);
1924
+ const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick("card") : { proceed: true };
1925
+ if (!beforeClick.proceed) {
1926
+ submittingRef.current = false;
1927
+ setSubmitting(false);
1928
+ return;
1929
+ }
1930
+ onButtonClick?.("card");
1931
+ const effectiveSessionId = beforeClick.sessionId ?? sessionId;
1932
+ const effectiveEmail = beforeClick.accountPatch?.email ?? email;
1933
+ try {
1934
+ onErrorChange?.(null);
1935
+ const submitRes = await elements.submit();
1936
+ if (submitRes.error) {
1937
+ onErrorChange?.(submitRes.error.message ?? "Payment failed.");
1938
+ return;
1939
+ }
1940
+ const pmRes = await stripe.createPaymentMethod({ elements });
1941
+ if (pmRes.error || !pmRes.paymentMethod) {
1942
+ onErrorChange?.(pmRes.error?.message ?? "Failed to create payment method.");
1943
+ return;
1944
+ }
1945
+ const paymentMethod = pmRes.paymentMethod;
1946
+ if (!effectiveSessionId || !effectiveEmail) {
1947
+ throw new Error("Missing sessionId or email for payment.");
1948
+ }
1949
+ const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
1950
+ method: "POST",
1951
+ headers: { "Content-Type": "application/json" },
1952
+ body: JSON.stringify({
1953
+ sessionId: effectiveSessionId,
1954
+ email: effectiveEmail,
1955
+ paymentMethodType: paymentMethod.type || method,
1956
+ isPaypal: false
1957
+ })
1958
+ });
1959
+ if (!intentResponse.ok) {
1960
+ const intentError = await buildFloPayApiErrorFromResponse(intentResponse, "Failed to create payment intent");
1961
+ onErrorChange?.(intentError.message);
1962
+ onDecline?.(buildDeclineEvent("card", intentError));
1963
+ return;
1964
+ }
1965
+ const intentJson = await intentResponse.json();
1966
+ const intentClientSecret = intentJson.data?.id;
1967
+ if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
1968
+ if (typeof window !== "undefined") {
1969
+ try {
1970
+ localStorage.setItem(STRIPE_RESUME_KEY, JSON.stringify({
1971
+ sessionId: effectiveSessionId,
1972
+ paymentMethodId: paymentMethod.id,
1973
+ paymentMethodType: paymentMethod.type ?? method,
1974
+ gateway: "stripe"
1975
+ }));
1976
+ } catch {
1977
+ }
1978
+ }
1979
+ const { error: confirmError, paymentIntent } = await stripe.confirmPayment({
1980
+ clientSecret: intentClientSecret,
1981
+ confirmParams: {
1982
+ return_url: window.location.href,
1983
+ payment_method: paymentMethod.id
1684
1984
  },
1685
- onConfirm: handleWalletConfirm,
1686
- onCancel: () => {
1687
- beforeClickRef.current = null;
1688
- onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
1985
+ redirect: "if_required"
1986
+ });
1987
+ if (confirmError) {
1988
+ if (typeof window !== "undefined") {
1989
+ try {
1990
+ localStorage.removeItem(STRIPE_RESUME_KEY);
1991
+ } catch {
1992
+ }
1993
+ }
1994
+ const message = confirmError.message ?? "Payment failed.";
1995
+ onErrorChange?.(message);
1996
+ onDecline?.(buildDeclineEvent("card", message, { code: confirmError.code }));
1997
+ return;
1998
+ }
1999
+ const SUCCESSFUL_PI_STATUSES = /* @__PURE__ */ new Set(["succeeded", "requires_capture", "processing"]);
2000
+ const piStatus = paymentIntent?.status;
2001
+ if (!piStatus || !SUCCESSFUL_PI_STATUSES.has(piStatus)) {
2002
+ if (typeof window !== "undefined") {
2003
+ try {
2004
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2005
+ } catch {
2006
+ }
2007
+ }
2008
+ const message = piStatus === "canceled" ? "Payment was canceled." : "Payment was not completed. Please try again.";
2009
+ onErrorChange?.(message);
2010
+ onDecline?.(buildDeclineEvent("card", message, { code: piStatus ?? "missing_payment_intent" }));
2011
+ return;
2012
+ }
2013
+ if (typeof window !== "undefined") {
2014
+ try {
2015
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2016
+ } catch {
2017
+ }
2018
+ }
2019
+ const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
2020
+ onTokenizedBody({
2021
+ id: confirmedPmId ?? paymentMethod.id,
2022
+ type: "card",
2023
+ threeDSecureActionResultTokenId: paymentIntent?.id,
2024
+ gateway: "stripe",
2025
+ paymentMethodType: paymentMethod.type ?? method
2026
+ }, {
2027
+ accountPatch: beforeClick.accountPatch,
2028
+ sessionId: effectiveSessionId
2029
+ });
2030
+ } catch (err) {
2031
+ onErrorChange?.(err instanceof Error ? err.message : "Payment failed. Please try again.");
2032
+ } finally {
2033
+ submittingRef.current = false;
2034
+ setSubmitting(false);
2035
+ }
2036
+ }, [
2037
+ stripe,
2038
+ elements,
2039
+ isProcessing,
2040
+ isMethodComplete,
2041
+ runBeforeButtonClick,
2042
+ onButtonClick,
2043
+ sessionId,
2044
+ email,
2045
+ baseUrl,
2046
+ method,
2047
+ onTokenizedBody,
2048
+ onErrorChange,
2049
+ onDecline
2050
+ ]);
2051
+ void onCancel;
2052
+ return /* @__PURE__ */ jsxs4("div", { "data-testid": `flopay-stripe-method-form-${method}`, style: { display: "flex", flexDirection: "column" }, children: [
2053
+ /* @__PURE__ */ jsx6(
2054
+ PaymentElement2,
2055
+ {
2056
+ onReady: () => setLoadState("ready"),
2057
+ onLoadError: () => setLoadState("load_error"),
2058
+ onChange: (event) => {
2059
+ const evRecord = event;
2060
+ setIsMethodComplete(!!evRecord.complete);
1689
2061
  },
1690
2062
  options: {
1691
- buttonType: { applePay: "plain", googlePay: "plain" },
1692
- paymentMethods: {
1693
- applePay: showApplePay ? "always" : "never",
1694
- googlePay: showGooglePay ? "always" : "never",
1695
- paypal: "never",
1696
- link: "never",
1697
- amazonPay: "never",
1698
- klarna: "never"
1699
- },
1700
- layout: { maxColumns: 1, overflow: "never" }
2063
+ layout: { type: "accordion", defaultCollapsed: false, radios: "never" },
2064
+ defaultValues: {
2065
+ billingDetails: {
2066
+ name: billingName && billingName.trim().length >= 3 ? billingName.trim() : "FloPay Customer",
2067
+ ...email ? { email } : {}
2068
+ }
2069
+ }
1701
2070
  }
1702
2071
  }
1703
- ) }),
1704
- submitting && /* @__PURE__ */ jsx6(ProcessingOverlay, { status: "processing" })
2072
+ ),
2073
+ /* @__PURE__ */ jsx6(
2074
+ "button",
2075
+ {
2076
+ type: "button",
2077
+ "data-testid": `flopay-stripe-method-pay-${method}`,
2078
+ onClick: () => {
2079
+ void handlePay();
2080
+ },
2081
+ disabled: !isMethodComplete || submitting || isProcessing || loadState !== "ready",
2082
+ style: {
2083
+ width: "100%",
2084
+ marginTop: "0.75rem",
2085
+ padding: "0.875rem",
2086
+ backgroundColor: submitButtonColor,
2087
+ color: "white",
2088
+ border: "none",
2089
+ borderRadius: submitButtonBorderRadius,
2090
+ fontSize: "1rem",
2091
+ fontWeight: 600,
2092
+ cursor: !isMethodComplete || submitting || isProcessing || loadState !== "ready" ? "not-allowed" : "pointer",
2093
+ opacity: !isMethodComplete || submitting || isProcessing || loadState !== "ready" ? 0.5 : 1,
2094
+ transition: "opacity 120ms ease-out",
2095
+ ...submitButtonStyle ?? {}
2096
+ },
2097
+ children: submitting ? "Processing\u2026" : `Pay with ${getStripeMethodDisplayName(method)}`
2098
+ }
2099
+ )
1705
2100
  ] });
1706
2101
  }
2102
+ function StripePaymentElementInner({
2103
+ sessionId,
2104
+ email,
2105
+ billingName,
2106
+ billingApiUrl,
2107
+ paymentElementMethods,
2108
+ stripeInstance,
2109
+ paymentElementBaseOptions,
2110
+ onTokenizedBody,
2111
+ onErrorChange,
2112
+ onButtonClick,
2113
+ onDecline,
2114
+ runBeforeButtonClick,
2115
+ onLoadStateChange,
2116
+ isProcessing = false,
2117
+ submitButtonColor = "#4A49FF",
2118
+ submitButtonBorderRadius = 8,
2119
+ submitButtonStyle,
2120
+ buttonAppearance,
2121
+ themeId,
2122
+ onExpandApm,
2123
+ expandedApmMethod
2124
+ }) {
2125
+ const baseUrl = billingApiUrl.replace(/\/+$/, "");
2126
+ const [expandedMethod, setExpandedMethod] = useState3(null);
2127
+ const [submittingMethod, setSubmittingMethod] = useState3(null);
2128
+ const submittingRef = useRef3(false);
2129
+ useEffect4(() => {
2130
+ if (paymentElementMethods.length > 0 && stripeInstance) {
2131
+ onLoadStateChange?.("ready");
2132
+ } else if (!stripeInstance) {
2133
+ onLoadStateChange?.("loading");
2134
+ }
2135
+ }, [paymentElementMethods.length, stripeInstance, onLoadStateChange]);
2136
+ const handleAutoConfirm = useCallback(async (method) => {
2137
+ if (!stripeInstance) return;
2138
+ if (submittingRef.current || isProcessing) return;
2139
+ submittingRef.current = true;
2140
+ setSubmittingMethod(method);
2141
+ const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick("card") : { proceed: true };
2142
+ if (!beforeClick.proceed) {
2143
+ submittingRef.current = false;
2144
+ setSubmittingMethod(null);
2145
+ return;
2146
+ }
2147
+ onButtonClick?.("card");
2148
+ const effectiveSessionId = beforeClick.sessionId ?? sessionId;
2149
+ const effectiveEmail = beforeClick.accountPatch?.email ?? email;
2150
+ try {
2151
+ onErrorChange?.(null);
2152
+ if (!effectiveSessionId || !effectiveEmail) {
2153
+ throw new Error("Missing sessionId or email for payment.");
2154
+ }
2155
+ const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
2156
+ method: "POST",
2157
+ headers: { "Content-Type": "application/json" },
2158
+ body: JSON.stringify({
2159
+ sessionId: effectiveSessionId,
2160
+ email: effectiveEmail,
2161
+ // Wire type, not pm_xxx — backend scopes `payment_method_types`
2162
+ // to this single method so unrelated account-wide methods
2163
+ // (apple_pay without domain verification, …) don't poison PI
2164
+ // creation.
2165
+ paymentMethodType: method,
2166
+ isPaypal: false
2167
+ })
2168
+ });
2169
+ if (!intentResponse.ok) {
2170
+ const intentError = await buildFloPayApiErrorFromResponse(intentResponse, "Failed to create payment intent");
2171
+ onErrorChange?.(intentError.message);
2172
+ onDecline?.(buildDeclineEvent("card", intentError));
2173
+ return;
2174
+ }
2175
+ const intentJson = await intentResponse.json();
2176
+ const intentClientSecret = intentJson.data?.id;
2177
+ if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
2178
+ if (typeof window !== "undefined") {
2179
+ try {
2180
+ localStorage.setItem(STRIPE_RESUME_KEY, JSON.stringify({
2181
+ sessionId: effectiveSessionId,
2182
+ paymentMethodType: method,
2183
+ gateway: "stripe"
2184
+ }));
2185
+ } catch {
2186
+ }
2187
+ }
2188
+ const { error: confirmError, paymentIntent } = await stripeInstance.confirmPayment({
2189
+ clientSecret: intentClientSecret,
2190
+ confirmParams: {
2191
+ return_url: window.location.href,
2192
+ // Inline the method data — no Elements / PaymentElement
2193
+ // required. Stripe creates the PaymentMethod during confirm
2194
+ // and proceeds to its popup/redirect UI.
2195
+ payment_method_data: {
2196
+ type: method,
2197
+ billing_details: {
2198
+ name: billingName && billingName.trim().length >= 3 ? billingName.trim() : "FloPay Customer",
2199
+ ...effectiveEmail ? { email: effectiveEmail } : {}
2200
+ }
2201
+ }
2202
+ },
2203
+ redirect: "if_required"
2204
+ });
2205
+ if (confirmError) {
2206
+ if (typeof window !== "undefined") {
2207
+ try {
2208
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2209
+ } catch {
2210
+ }
2211
+ }
2212
+ const message = confirmError.message ?? "Payment failed.";
2213
+ onErrorChange?.(message);
2214
+ onDecline?.(buildDeclineEvent("card", message, { code: confirmError.code }));
2215
+ return;
2216
+ }
2217
+ const SUCCESSFUL_PI_STATUSES = /* @__PURE__ */ new Set(["succeeded", "requires_capture", "processing"]);
2218
+ const piStatus = paymentIntent?.status;
2219
+ if (!piStatus || !SUCCESSFUL_PI_STATUSES.has(piStatus)) {
2220
+ if (typeof window !== "undefined") {
2221
+ try {
2222
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2223
+ } catch {
2224
+ }
2225
+ }
2226
+ const message = piStatus === "canceled" ? "Payment was canceled." : "Payment was not completed. Please try again.";
2227
+ onErrorChange?.(message);
2228
+ onDecline?.(buildDeclineEvent("card", message, { code: piStatus ?? "missing_payment_intent" }));
2229
+ return;
2230
+ }
2231
+ if (typeof window !== "undefined") {
2232
+ try {
2233
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2234
+ } catch {
2235
+ }
2236
+ }
2237
+ const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
2238
+ onTokenizedBody({
2239
+ id: confirmedPmId ?? paymentIntent?.id,
2240
+ type: "card",
2241
+ threeDSecureActionResultTokenId: paymentIntent?.id,
2242
+ gateway: "stripe",
2243
+ paymentMethodType: method
2244
+ }, {
2245
+ accountPatch: beforeClick.accountPatch,
2246
+ sessionId: effectiveSessionId
2247
+ });
2248
+ } catch (err) {
2249
+ onErrorChange?.(err instanceof Error ? err.message : "Payment failed. Please try again.");
2250
+ } finally {
2251
+ submittingRef.current = false;
2252
+ setSubmittingMethod(null);
2253
+ }
2254
+ }, [
2255
+ stripeInstance,
2256
+ isProcessing,
2257
+ runBeforeButtonClick,
2258
+ onButtonClick,
2259
+ sessionId,
2260
+ email,
2261
+ baseUrl,
2262
+ billingName,
2263
+ onTokenizedBody,
2264
+ onErrorChange,
2265
+ onDecline
2266
+ ]);
2267
+ const [localExpandedMethod, setLocalExpandedMethod] = useState3(null);
2268
+ const activeExpandedMethod = onExpandApm ? expandedApmMethod ?? null : localExpandedMethod;
2269
+ const handleMethodClick = useCallback((method) => {
2270
+ if (submittingRef.current || isProcessing) return;
2271
+ if (activeExpandedMethod && activeExpandedMethod !== method) return;
2272
+ if (needsStripeMethodExplicitConfirm(method)) {
2273
+ if (onExpandApm) {
2274
+ onExpandApm(method);
2275
+ } else {
2276
+ setLocalExpandedMethod(method);
2277
+ }
2278
+ } else {
2279
+ void handleAutoConfirm(method);
2280
+ }
2281
+ }, [activeExpandedMethod, isProcessing, handleAutoConfirm, onExpandApm]);
2282
+ const localInlineElementsOptions = useMemo3(() => {
2283
+ if (!localExpandedMethod) return null;
2284
+ return {
2285
+ ...paymentElementBaseOptions,
2286
+ paymentMethodTypes: [localExpandedMethod]
2287
+ };
2288
+ }, [localExpandedMethod, paymentElementBaseOptions]);
2289
+ return /* @__PURE__ */ jsx6(
2290
+ "div",
2291
+ {
2292
+ "data-testid": "flopay-stripe-payment-element-region",
2293
+ style: { display: "flex", flexDirection: "column", gap: "0.5rem" },
2294
+ children: paymentElementMethods.map((method) => {
2295
+ const isExpanded = expandedApmMethod === method;
2296
+ const isSubmittingThis = submittingMethod === method;
2297
+ const otherInProgress = submittingMethod !== null && submittingMethod !== method || expandedApmMethod !== null && expandedApmMethod !== void 0 && expandedApmMethod !== method;
2298
+ return /* @__PURE__ */ jsxs4(React6.Fragment, { children: [
2299
+ /* @__PURE__ */ jsx6(
2300
+ StripeMethodButton,
2301
+ {
2302
+ method,
2303
+ themeId,
2304
+ submitting: isSubmittingThis,
2305
+ disabled: otherInProgress,
2306
+ highlighted: isExpanded,
2307
+ hasNextStep: needsStripeMethodExplicitConfirm(method),
2308
+ onClick: () => handleMethodClick(method),
2309
+ backgroundColor: buttonAppearance?.backgroundColor,
2310
+ borderColor: buttonAppearance?.borderColor,
2311
+ textColor: buttonAppearance?.textColor,
2312
+ borderRadius: buttonAppearance?.borderRadius,
2313
+ fontFamily: buttonAppearance?.fontFamily
2314
+ }
2315
+ ),
2316
+ !onExpandApm && localExpandedMethod === method && localInlineElementsOptions && stripeInstance && /* @__PURE__ */ jsx6(
2317
+ StripeElements,
2318
+ {
2319
+ stripe: stripeInstance,
2320
+ options: localInlineElementsOptions,
2321
+ children: /* @__PURE__ */ jsx6(
2322
+ StripeMethodInlineForm,
2323
+ {
2324
+ method,
2325
+ sessionId,
2326
+ email,
2327
+ billingName,
2328
+ billingApiUrl,
2329
+ onTokenizedBody,
2330
+ onErrorChange,
2331
+ onDecline,
2332
+ onCancel: () => setLocalExpandedMethod(null),
2333
+ runBeforeButtonClick,
2334
+ onButtonClick,
2335
+ isProcessing,
2336
+ submitButtonColor,
2337
+ submitButtonBorderRadius,
2338
+ submitButtonStyle
2339
+ }
2340
+ )
2341
+ },
2342
+ method
2343
+ )
2344
+ ] }, method);
2345
+ })
2346
+ }
2347
+ );
2348
+ }
1707
2349
  function SplitCardFormInner({
1708
2350
  sessionId,
1709
2351
  billingApiUrl,
@@ -1726,6 +2368,8 @@ function SplitCardFormInner({
1726
2368
  onFirstNameChange,
1727
2369
  onLastNameChange,
1728
2370
  showPayPal = true,
2371
+ showStripe = true,
2372
+ enabledPaymentMethods,
1729
2373
  showApplePay = true,
1730
2374
  showGooglePay = true,
1731
2375
  layout = "default",
@@ -1785,6 +2429,7 @@ function SplitCardFormInner({
1785
2429
  const avsConfig = useMemo3(() => resolveAVSConfig(enableAVSProp), [enableAVSProp]);
1786
2430
  const enableAVS = avsConfig !== null;
1787
2431
  const [viewState, setViewState] = useState3(initialCardOpen ? "card" : "buttons");
2432
+ const [expandedApmMethod, setExpandedApmMethod] = useState3(null);
1788
2433
  const showCardForm = viewState === "expanding" || viewState === "card";
1789
2434
  const TRANSITION_MS = 280;
1790
2435
  const expandToCard = useCallback(() => {
@@ -1795,6 +2440,18 @@ function SplitCardFormInner({
1795
2440
  setViewState("collapsing");
1796
2441
  setTimeout(() => setViewState("buttons"), TRANSITION_MS);
1797
2442
  }, []);
2443
+ const expandToApm = useCallback((method) => {
2444
+ setExpandedApmMethod(method);
2445
+ setViewState("apm-expanding");
2446
+ setTimeout(() => setViewState("apm-form"), TRANSITION_MS);
2447
+ }, []);
2448
+ const collapseFromApm = useCallback(() => {
2449
+ setViewState("apm-collapsing");
2450
+ setTimeout(() => {
2451
+ setViewState("buttons");
2452
+ setExpandedApmMethod(null);
2453
+ }, TRANSITION_MS);
2454
+ }, []);
1798
2455
  useEffect4(() => {
1799
2456
  if (layout === "buttons" && initialCardOpen) {
1800
2457
  setViewState("card");
@@ -1848,20 +2505,43 @@ function SplitCardFormInner({
1848
2505
  return paypalFlopay.getRawProvider();
1849
2506
  }, [paypalFlopay]);
1850
2507
  const amountInCents = totalAmount || 100;
2508
+ const stripeAppearanceProp = appearance ? { appearance } : null;
2509
+ const paymentElementAppearance = useMemo3(() => {
2510
+ const base = appearance ?? {};
2511
+ const baseRules = base.rules ?? {};
2512
+ return {
2513
+ ...base,
2514
+ rules: {
2515
+ ...baseRules,
2516
+ ".AccordionItem": {
2517
+ padding: "10px 14px",
2518
+ boxShadow: "none",
2519
+ ...baseRules[".AccordionItem"] ?? {}
2520
+ },
2521
+ ".AccordionItemContents": {
2522
+ padding: "6px 14px 12px",
2523
+ boxShadow: "none",
2524
+ ...baseRules[".AccordionItemContents"] ?? {}
2525
+ }
2526
+ }
2527
+ };
2528
+ }, [appearance]);
1851
2529
  const walletOptions = useMemo3(() => ({
1852
2530
  mode: "payment",
1853
2531
  amount: amountInCents,
1854
2532
  currency: currency.toLowerCase(),
1855
2533
  paymentMethodCreation: "manual",
1856
- captureMethod: "manual"
1857
- }), [amountInCents, currency]);
2534
+ captureMethod: "manual",
2535
+ ...stripeAppearanceProp
2536
+ }), [amountInCents, currency, stripeAppearanceProp]);
1858
2537
  const paypalOptions = useMemo3(() => ({
1859
2538
  mode: "payment",
1860
2539
  amount: amountInCents,
1861
2540
  currency: currency.toLowerCase(),
1862
2541
  captureMethod: "manual",
1863
- setupFutureUsage: "off_session"
1864
- }), [amountInCents, currency]);
2542
+ setupFutureUsage: "off_session",
2543
+ ...stripeAppearanceProp
2544
+ }), [amountInCents, currency, stripeAppearanceProp]);
1865
2545
  const updateError = useCallback(
1866
2546
  (err) => {
1867
2547
  setError(err);
@@ -1875,22 +2555,98 @@ function SplitCardFormInner({
1875
2555
  },
1876
2556
  [onDecline]
1877
2557
  );
1878
- const showWallets = showApplePay || showGooglePay;
2558
+ const directPaypalConfigured = !!directPaypal?.clientId;
2559
+ const hasEnabledMethodsPayload = Array.isArray(enabledPaymentMethods);
2560
+ const hasEnabledMethods = hasEnabledMethodsPayload && enabledPaymentMethods.length > 0;
2561
+ const paypalEnabled = hasEnabledMethodsPayload && enabledPaymentMethods.includes("paypal");
2562
+ const { expressMethods, paymentElementMethods } = useMemo3(
2563
+ () => partitionStripeMethods(enabledPaymentMethods, {
2564
+ excludePaypal: directPaypalConfigured
2565
+ }),
2566
+ [enabledPaymentMethods, directPaypalConfigured]
2567
+ );
2568
+ const paypalRenderedSeparately = directPaypalConfigured || !!paypalFlopay;
2569
+ const expressMethodsForWalletRow = useMemo3(
2570
+ () => paypalRenderedSeparately ? expressMethods.filter((m) => m !== "paypal") : expressMethods,
2571
+ [expressMethods, paypalRenderedSeparately]
2572
+ );
2573
+ const legacyExpressMethods = useMemo3(() => {
2574
+ const out = [];
2575
+ if (showApplePay) out.push("apple_pay");
2576
+ if (showGooglePay) out.push("google_pay");
2577
+ return out;
2578
+ }, [showApplePay, showGooglePay]);
2579
+ const walletExpressMethods = hasEnabledMethodsPayload ? expressMethodsForWalletRow : legacyExpressMethods;
2580
+ const showWallets = showStripe && walletExpressMethods.length > 0;
2581
+ const paymentElementMethodsForCurrency = useMemo3(
2582
+ () => filterStripeMethodsByAmount(
2583
+ filterStripeMethodsByCountry(
2584
+ filterStripeMethodsByCurrency(paymentElementMethods, currency),
2585
+ countryProp
2586
+ ),
2587
+ currency,
2588
+ amountInCents
2589
+ ),
2590
+ [paymentElementMethods, currency, countryProp, amountInCents]
2591
+ );
2592
+ const paymentElementBaseOptions = useMemo3(() => ({
2593
+ mode: "payment",
2594
+ amount: amountInCents,
2595
+ currency: currency.toLowerCase(),
2596
+ paymentMethodCreation: "manual",
2597
+ appearance: paymentElementAppearance
2598
+ }), [amountInCents, currency, paymentElementAppearance]);
1879
2599
  const [paypalLoadState, setPaypalLoadState] = useState3("loading");
1880
2600
  const [walletLoadState, setWalletLoadState] = useState3("loading");
2601
+ const [paymentElementLoadState, setPaymentElementLoadState] = useState3("loading");
1881
2602
  const [directPaypalReady, setDirectPaypalReady] = useState3(false);
1882
2603
  const [inAppBrowserDetected, setInAppBrowserDetected] = useState3();
1883
2604
  useEffect4(() => {
1884
2605
  setInAppBrowserDetected(isInAppBrowser());
1885
2606
  }, []);
1886
- const directPaypalConfigured = !!directPaypal?.clientId;
1887
- const shouldShowPayPal = showPayPal && (directPaypalConfigured || inAppBrowserDetected === false);
1888
- const shouldShowWallets = showWallets;
2607
+ const shouldShowPayPal = showPayPal && (directPaypalConfigured || (hasEnabledMethodsPayload ? paypalEnabled : inAppBrowserDetected === false));
2608
+ const shouldShowWallets = showStripe && showWallets;
1889
2609
  const shouldRenderDirectPayPal = shouldShowPayPal && directPaypalConfigured;
1890
2610
  const shouldRenderStripePayPal = shouldShowPayPal && !directPaypalConfigured && !!paypalStripeInstance;
1891
2611
  const shouldRenderWallets = shouldShowWallets && !!stripeInstance;
2612
+ const shouldRenderPaymentElement = showStripe && hasEnabledMethods && paymentElementMethodsForCurrency.length > 0 && !!stripeInstance;
1892
2613
  const shouldDisplayPayPalRow = shouldRenderDirectPayPal ? directPaypalReady : shouldRenderStripePayPal && isExpressCheckoutRowVisible(paypalLoadState);
1893
2614
  const shouldDisplayWalletRow = shouldRenderWallets && isExpressCheckoutRowVisible(walletLoadState);
2615
+ const shouldDisplayPaymentElementRow = shouldRenderPaymentElement && paymentElementLoadState !== "load_error";
2616
+ const validationFiredRef = useRef3(false);
2617
+ useEffect4(() => {
2618
+ if (validationFiredRef.current) return;
2619
+ if (!showStripe && !showPayPal) {
2620
+ validationFiredRef.current = true;
2621
+ const err = new FloPayError3(
2622
+ "FloPay: both `showStripe` and `showPayPal` are false \u2014 nothing to render.",
2623
+ "validation_error"
2624
+ );
2625
+ onError?.(err);
2626
+ updateError(err.message);
2627
+ } else if (!showStripe && showPayPal && !directPaypalConfigured && !paypalStripeInstance) {
2628
+ validationFiredRef.current = true;
2629
+ const err = new FloPayError3(
2630
+ "FloPay: `showStripe` is false and no PayPal gateway is configured for this session \u2014 nothing to render.",
2631
+ "validation_error"
2632
+ );
2633
+ onError?.(err);
2634
+ updateError(err.message);
2635
+ }
2636
+ }, [showStripe, showPayPal, directPaypalConfigured, paypalStripeInstance, onError, updateError]);
2637
+ const deprecationLoggedRef = useRef3(false);
2638
+ useEffect4(() => {
2639
+ if (deprecationLoggedRef.current) return;
2640
+ if (!hasEnabledMethods) return;
2641
+ const stale = [];
2642
+ if (showApplePay !== true) stale.push("showApplePay");
2643
+ if (showGooglePay !== true) stale.push("showGooglePay");
2644
+ if (stale.length === 0) return;
2645
+ deprecationLoggedRef.current = true;
2646
+ console.warn(
2647
+ `[FloPay] ${stale.join(" / ")} ${stale.length === 1 ? "is" : "are"} deprecated: the Apple Pay / Google Pay surface is now driven by \`gateways.stripe.enabledPaymentMethods\` on the session response. Remove the legacy prop(s) to silence this warning.`
2648
+ );
2649
+ }, [hasEnabledMethods, showApplePay, showGooglePay]);
1894
2650
  const handleNameChange = useCallback((value) => {
1895
2651
  setFullName(value);
1896
2652
  onFullNameChange?.(value);
@@ -2191,24 +2947,75 @@ function SplitCardFormInner({
2191
2947
  }
2192
2948
  }
2193
2949
  }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
2950
+ const stripeResumeAttemptedRef = useRef3(false);
2194
2951
  useEffect4(() => {
2195
- if (typeof window === "undefined") return;
2196
- const stored = localStorage.getItem(WALLET_RESUME_KEY);
2197
- if (!stored) return;
2198
- try {
2199
- const payload = JSON.parse(stored);
2200
- if (payload.sessionId === sessionId) {
2201
- localStorage.removeItem(WALLET_RESUME_KEY);
2202
- dispatchTokenizedBody({
2203
- id: payload.tokenId,
2204
- type: payload.tokenType,
2205
- threeDSecureActionResultTokenId: payload.paymentIntentId
2206
- });
2952
+ if (typeof window === "undefined" || stripeResumeAttemptedRef.current) return;
2953
+ const params = new URLSearchParams(window.location.search);
2954
+ const paymentIntentId = params.get("payment_intent");
2955
+ const clientSecret = params.get("payment_intent_client_secret");
2956
+ const redirectStatus = params.get("redirect_status");
2957
+ if (!paymentIntentId || !clientSecret) return;
2958
+ const readPayload = (key) => {
2959
+ const raw = localStorage.getItem(key);
2960
+ if (!raw) return null;
2961
+ try {
2962
+ return JSON.parse(raw);
2963
+ } catch {
2964
+ localStorage.removeItem(key);
2965
+ return null;
2207
2966
  }
2208
- } catch {
2209
- localStorage.removeItem(WALLET_RESUME_KEY);
2210
- }
2211
- }, [sessionId, dispatchTokenizedBody]);
2967
+ };
2968
+ const payload = readPayload(STRIPE_RESUME_KEY) ?? readPayload(LEGACY_WALLET_RESUME_KEY);
2969
+ if (payload?.sessionId && payload.sessionId !== sessionId) return;
2970
+ stripeResumeAttemptedRef.current = true;
2971
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2972
+ localStorage.removeItem(LEGACY_WALLET_RESUME_KEY);
2973
+ const cleanedUrl = new URL(window.location.href);
2974
+ cleanedUrl.searchParams.delete("payment_intent");
2975
+ cleanedUrl.searchParams.delete("payment_intent_client_secret");
2976
+ cleanedUrl.searchParams.delete("redirect_status");
2977
+ window.history.replaceState({}, "", cleanedUrl.toString());
2978
+ const paymentMethodType = payload?.paymentMethodType ?? payload?.tokenType ?? "card";
2979
+ const declineMethod = "card";
2980
+ (async () => {
2981
+ const rawProvider = flopay?.getRawProvider();
2982
+ if (!rawProvider) {
2983
+ const message = "Payment is not available \u2014 please refresh and try again.";
2984
+ updateError(message);
2985
+ emitDecline(declineMethod, message);
2986
+ return;
2987
+ }
2988
+ if (redirectStatus === "failed") {
2989
+ const message = "Payment was declined. Please try again.";
2990
+ updateError(message);
2991
+ emitDecline(declineMethod, message);
2992
+ return;
2993
+ }
2994
+ const { paymentIntent, error: error2 } = await rawProvider.retrievePaymentIntent(clientSecret);
2995
+ if (error2) {
2996
+ const message = error2.message ?? "Failed to retrieve payment status.";
2997
+ updateError(message);
2998
+ emitDecline(declineMethod, message, { code: error2.code });
2999
+ return;
3000
+ }
3001
+ const piStatus = paymentIntent?.status;
3002
+ const successful = piStatus === "succeeded" || piStatus === "requires_capture" || piStatus === "processing";
3003
+ if (!paymentIntent || !successful) {
3004
+ const message = piStatus === "canceled" ? "Payment was canceled." : "Payment was not completed. Please try again.";
3005
+ updateError(message);
3006
+ emitDecline(declineMethod, message, { code: piStatus ?? "missing_payment_intent" });
3007
+ return;
3008
+ }
3009
+ const resolvedPmId = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
3010
+ dispatchTokenizedBody({
3011
+ id: resolvedPmId ?? payload?.paymentMethodId ?? payload?.tokenId ?? paymentIntent.id,
3012
+ type: "card",
3013
+ threeDSecureActionResultTokenId: paymentIntent.id,
3014
+ gateway: payload?.gateway ?? "stripe",
3015
+ paymentMethodType
3016
+ });
3017
+ })();
3018
+ }, [sessionId, dispatchTokenizedBody, flopay, updateError, emitDecline]);
2212
3019
  const handleSubmit = useCallback(
2213
3020
  async (e) => {
2214
3021
  e.preventDefault();
@@ -2491,17 +3298,23 @@ function SplitCardFormInner({
2491
3298
  /* @__PURE__ */ jsx6("div", { style: {
2492
3299
  flex: 1,
2493
3300
  backgroundColor: cardInputBg,
2494
- border: `1px solid ${resolvedBorder}`,
3301
+ // Longhand only — mixing `border` shorthand with per-side
3302
+ // overrides triggers React's "shorthand vs longhand" warning
3303
+ // because render order isn't deterministic.
2495
3304
  borderTop: "none",
2496
3305
  borderRight: "none",
3306
+ borderBottom: `1px solid ${resolvedBorder}`,
3307
+ borderLeft: `1px solid ${resolvedBorder}`,
2497
3308
  borderBottomLeftRadius: "8px",
2498
3309
  padding: "10px"
2499
3310
  }, children: /* @__PURE__ */ jsx6(CardExpiryElement, { options: stripeElementStyle }) }),
2500
3311
  /* @__PURE__ */ jsx6("div", { style: {
2501
3312
  flex: 1,
2502
3313
  backgroundColor: cardInputBg,
2503
- border: `1px solid ${resolvedBorder}`,
2504
3314
  borderTop: "none",
3315
+ borderRight: `1px solid ${resolvedBorder}`,
3316
+ borderBottom: `1px solid ${resolvedBorder}`,
3317
+ borderLeft: `1px solid ${resolvedBorder}`,
2505
3318
  borderBottomRightRadius: "8px",
2506
3319
  padding: "10px"
2507
3320
  }, children: /* @__PURE__ */ jsx6(CardCvcElement, { options: stripeElementStyle }) })
@@ -2593,7 +3406,10 @@ function SplitCardFormInner({
2593
3406
  isAVSFieldVisible(avsConfig.city, cc) && /* @__PURE__ */ jsx6("div", { style: {
2594
3407
  flex: 1,
2595
3408
  backgroundColor: cardInputBg,
2596
- border: `1px solid ${resolvedBorder}`,
3409
+ borderTop: `1px solid ${resolvedBorder}`,
3410
+ borderRight: `1px solid ${resolvedBorder}`,
3411
+ borderBottom: `1px solid ${resolvedBorder}`,
3412
+ borderLeft: `1px solid ${resolvedBorder}`,
2597
3413
  padding: "10px",
2598
3414
  borderTopLeftRadius: "8px",
2599
3415
  borderBottomLeftRadius: "8px",
@@ -2670,7 +3486,10 @@ function SplitCardFormInner({
2670
3486
  isAVSFieldVisible(avsConfig.country, cc) && /* @__PURE__ */ jsx6("div", { style: {
2671
3487
  flex: avsLayoutProp === "row" ? 1 : void 0,
2672
3488
  backgroundColor: cardInputBg,
2673
- border: `1px solid ${resolvedBorder}`,
3489
+ borderTop: `1px solid ${resolvedBorder}`,
3490
+ borderRight: `1px solid ${resolvedBorder}`,
3491
+ borderBottom: `1px solid ${resolvedBorder}`,
3492
+ borderLeft: `1px solid ${resolvedBorder}`,
2674
3493
  padding: "10px",
2675
3494
  ...avsLayoutProp === "row" && isAVSFieldVisible(avsConfig.postal_code, cc) ? { borderRadius: "0", borderTopLeftRadius: "8px", borderBottomLeftRadius: "8px", borderRight: "none" } : { borderRadius: "8px" },
2676
3495
  ...isButtons && bStyles.countrySelect ? bStyles.countrySelect : {}
@@ -2765,11 +3584,65 @@ function SplitCardFormInner({
2765
3584
  }
2766
3585
  )
2767
3586
  ] });
3587
+ const gateDebugStyle = {
3588
+ margin: 0,
3589
+ padding: "6px 8px",
3590
+ background: "#eef2ff",
3591
+ border: "1px solid #c7d2fe",
3592
+ borderRadius: 6,
3593
+ color: "#111827",
3594
+ font: "11px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace",
3595
+ whiteSpace: "pre-wrap",
3596
+ wordBreak: "break-word"
3597
+ };
3598
+ const renderDirectPaypalGateDebug = (testId) => {
3599
+ if (!debug) return null;
3600
+ if (!showPayPal || !directPaypalConfigured) {
3601
+ return /* @__PURE__ */ jsx6("pre", { "data-testid": testId, style: gateDebugStyle, children: "FloPay/DirectPayPal-debug - not enabled" });
3602
+ }
3603
+ return /* @__PURE__ */ jsx6("pre", { "data-testid": testId, style: gateDebugStyle, children: [
3604
+ "FloPay/DirectPayPal-debug (parent gate)",
3605
+ ` showPayPal=${showPayPal}`,
3606
+ ` directPaypalConfigured=${directPaypalConfigured}`,
3607
+ ` inAppBrowserDetected=${String(inAppBrowserDetected)}`,
3608
+ ` shouldRenderDirectPayPal=${shouldRenderDirectPayPal}`,
3609
+ ` shouldRenderStripePayPal=${shouldRenderStripePayPal}`,
3610
+ ` hasPaypalStripeInstance=${!!paypalStripeInstance}`
3611
+ ].join("\n") });
3612
+ };
3613
+ const renderStripeGateDebug = (testId) => {
3614
+ if (!debug) return null;
3615
+ if (!showStripe || !stripeInstance) {
3616
+ return /* @__PURE__ */ jsx6("pre", { "data-testid": testId, style: gateDebugStyle, children: "FloPay/Stripe-debug - not enabled" });
3617
+ }
3618
+ return /* @__PURE__ */ jsx6("pre", { "data-testid": testId, style: gateDebugStyle, children: [
3619
+ "FloPay/Stripe-debug (parent gate)",
3620
+ ` showStripe=${showStripe}`,
3621
+ ` currency=${currency}`,
3622
+ ` amountInCents=${amountInCents}`,
3623
+ ` enabledPaymentMethods: ${JSON.stringify(enabledPaymentMethods ?? [])}`,
3624
+ ` enabledPaymentMethodsProvided=${hasEnabledMethodsPayload}`,
3625
+ ` hasEnabledMethods=${hasEnabledMethods}`,
3626
+ ` expressMethods: ${JSON.stringify(expressMethods)}`,
3627
+ ` walletExpressMethods: ${JSON.stringify(walletExpressMethods)}`,
3628
+ ` paymentElementMethods: ${JSON.stringify(paymentElementMethods)}`,
3629
+ ` paymentElementMethodsForCurrency: ${JSON.stringify(paymentElementMethodsForCurrency)}`,
3630
+ ` hasStripeInstance=${!!stripeInstance}`,
3631
+ ` shouldRenderWallets=${shouldRenderWallets}`,
3632
+ ` shouldRenderPaymentElement=${shouldRenderPaymentElement}`
3633
+ ].join("\n") });
3634
+ };
3635
+ const isApmView = viewState === "apm-expanding" || viewState === "apm-form" || viewState === "apm-collapsing";
3636
+ const apmAnim = viewState === "apm-expanding" ? `flopay-card-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : viewState === "apm-collapsing" ? `flopay-card-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : void 0;
3637
+ const apmInlineOptions = expandedApmMethod ? {
3638
+ ...paymentElementBaseOptions,
3639
+ paymentMethodTypes: [expandedApmMethod]
3640
+ } : null;
2768
3641
  if (layout === "buttons") {
2769
- const isButtonsView = viewState === "buttons" || viewState === "expanding";
3642
+ const isButtonsView = viewState === "buttons" || viewState === "expanding" || viewState === "apm-expanding";
2770
3643
  const isCardView = viewState === "expanding" || viewState === "card" || viewState === "collapsing";
2771
3644
  const cardButtonSizing = cardButtonContent === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
2772
- const buttonsAnim = viewState === "expanding" ? `flopay-buttons-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : viewState === "collapsing" ? `flopay-buttons-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : void 0;
3645
+ const buttonsAnim = viewState === "expanding" || viewState === "apm-expanding" ? `flopay-buttons-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : viewState === "collapsing" || viewState === "apm-collapsing" ? `flopay-buttons-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : void 0;
2773
3646
  const cardAnim = viewState === "expanding" ? `flopay-card-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : viewState === "collapsing" ? `flopay-card-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : void 0;
2774
3647
  return /* @__PURE__ */ jsxs4("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
2775
3648
  /* @__PURE__ */ jsx6(FloPayKeyframes, {}),
@@ -2790,32 +3663,8 @@ function SplitCardFormInner({
2790
3663
  ...buttonsAnim ? { animation: buttonsAnim, pointerEvents: "none" } : {}
2791
3664
  },
2792
3665
  children: [
2793
- debug && showPayPal && /* @__PURE__ */ jsx6(
2794
- "pre",
2795
- {
2796
- "data-testid": "flopay-direct-paypal-gate-debug",
2797
- style: {
2798
- margin: 0,
2799
- padding: "6px 8px",
2800
- background: "#eef2ff",
2801
- border: "1px solid #c7d2fe",
2802
- borderRadius: 6,
2803
- color: "#111827",
2804
- font: "11px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace",
2805
- whiteSpace: "pre-wrap",
2806
- wordBreak: "break-word"
2807
- },
2808
- children: [
2809
- "FloPay/DirectPayPal-debug (parent gate)",
2810
- ` showPayPal=${showPayPal}`,
2811
- ` directPaypalConfigured=${directPaypalConfigured}`,
2812
- ` inAppBrowserDetected=${String(inAppBrowserDetected)}`,
2813
- ` shouldRenderDirectPayPal=${shouldRenderDirectPayPal}`,
2814
- ` shouldRenderStripePayPal=${shouldRenderStripePayPal}`,
2815
- ` hasPaypalStripeInstance=${!!paypalStripeInstance}`
2816
- ].join("\n")
2817
- }
2818
- ),
3666
+ renderDirectPaypalGateDebug("flopay-direct-paypal-gate-debug"),
3667
+ renderStripeGateDebug("flopay-stripe-gate-debug"),
2819
3668
  shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2820
3669
  paypalDirectRetry && /* @__PURE__ */ jsx6(
2821
3670
  "div",
@@ -2870,7 +3719,8 @@ function SplitCardFormInner({
2870
3719
  onButtonClick,
2871
3720
  onDecline,
2872
3721
  runBeforeButtonClick,
2873
- onLoadStateChange: setPaypalLoadState
3722
+ onLoadStateChange: setPaypalLoadState,
3723
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
2874
3724
  }
2875
3725
  ) }),
2876
3726
  shouldRenderWallets ? /* @__PURE__ */ jsx6(StripeElements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ jsx6(
@@ -2879,17 +3729,48 @@ function SplitCardFormInner({
2879
3729
  sessionId,
2880
3730
  email: resolvedAccount.email,
2881
3731
  billingApiUrl: resolvedBillingApiUrl,
2882
- showApplePay,
2883
- showGooglePay,
3732
+ expressMethods: walletExpressMethods,
3733
+ onTokenizedBody: dispatchTokenizedBody,
3734
+ onErrorChange: updateError,
3735
+ onButtonClick,
3736
+ onDecline,
3737
+ runBeforeButtonClick,
3738
+ onLoadStateChange: setWalletLoadState,
3739
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
3740
+ }
3741
+ ) }) : shouldShowWallets ? /* @__PURE__ */ jsx6("div", { style: { height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, borderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
3742
+ shouldRenderPaymentElement && /* @__PURE__ */ jsx6(
3743
+ StripePaymentElementInner,
3744
+ {
3745
+ sessionId,
3746
+ email: resolvedAccount.email,
3747
+ billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
3748
+ billingApiUrl: resolvedBillingApiUrl,
3749
+ paymentElementMethods: paymentElementMethodsForCurrency,
3750
+ stripeInstance,
3751
+ paymentElementBaseOptions,
2884
3752
  onTokenizedBody: dispatchTokenizedBody,
2885
3753
  onErrorChange: updateError,
2886
3754
  onButtonClick,
2887
3755
  onDecline,
2888
3756
  runBeforeButtonClick,
2889
- onLoadStateChange: setWalletLoadState
3757
+ onLoadStateChange: setPaymentElementLoadState,
3758
+ isProcessing: isSubmitting,
3759
+ submitButtonColor: resolvedPrimaryColor,
3760
+ submitButtonBorderRadius: resolvedBorderRadius,
3761
+ submitButtonStyle: bStyles.submitButton,
3762
+ onExpandApm: expandToApm,
3763
+ expandedApmMethod,
3764
+ themeId: theme,
3765
+ buttonAppearance: {
3766
+ backgroundColor: bStyles.cardButton?.backgroundColor,
3767
+ borderColor: bStyles.cardInputBorder,
3768
+ textColor: bStyles.cardButton?.color,
3769
+ borderRadius: bStyles.cardButton?.borderRadius
3770
+ }
2890
3771
  }
2891
- ) }) : shouldShowWallets ? /* @__PURE__ */ jsx6("div", { style: { height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
2892
- /* @__PURE__ */ jsx6(
3772
+ ),
3773
+ showStripe && /* @__PURE__ */ jsx6(
2893
3774
  "button",
2894
3775
  {
2895
3776
  type: "button",
@@ -2904,10 +3785,21 @@ function SplitCardFormInner({
2904
3785
  style: {
2905
3786
  width: "100%",
2906
3787
  ...cardButtonSizing,
2907
- backgroundColor: "white",
2908
- color: "#262833",
2909
- border: "1px solid #d1d5db",
2910
- borderRadius: "8px",
3788
+ // `bStyles.cardButton` first — supplies the bundle's
3789
+ // padding / typography / border-radius — then the
3790
+ // primary surface overrides on top, so the card button
3791
+ // ends up wearing the *submit* button's colours rather
3792
+ // than the bundle's neutral white-tile `cardButton`
3793
+ // colours. Without this order flip, every non-classic
3794
+ // bundle's `cardButton.backgroundColor` was winning
3795
+ // over the primary fill we just derived above it.
3796
+ ...bStyles.cardButton,
3797
+ ...derivePrimaryTileStyle({
3798
+ themeBundle,
3799
+ resolvedPrimaryColor,
3800
+ resolvedBorderRadius,
3801
+ submitButtonStyle: bStyles.submitButton
3802
+ }),
2911
3803
  fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
2912
3804
  fontWeight: 600,
2913
3805
  cursor: isSubmitting ? "not-allowed" : "pointer",
@@ -2916,10 +3808,8 @@ function SplitCardFormInner({
2916
3808
  justifyContent: "center",
2917
3809
  gap: "0.625rem",
2918
3810
  transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
2919
- boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
2920
3811
  position: "relative",
2921
- opacity: isSubmitting ? 0.6 : 1,
2922
- ...bStyles.cardButton
3812
+ opacity: isSubmitting ? 0.6 : 1
2923
3813
  },
2924
3814
  onMouseDown: (e) => {
2925
3815
  e.currentTarget.style.transform = "scale(0.985)";
@@ -2950,44 +3840,209 @@ function SplitCardFormInner({
2950
3840
  ]
2951
3841
  }
2952
3842
  ),
2953
- isCardView && /* @__PURE__ */ jsx6("div", { style: {
3843
+ showStripe && isCardView && /* @__PURE__ */ jsx6("div", { style: {
2954
3844
  gridArea: "1 / 1",
2955
3845
  ...cardAnim ? { animation: cardAnim } : {},
2956
3846
  ...viewState === "collapsing" ? { pointerEvents: "none" } : {}
2957
- }, children: cardFormBlock })
3847
+ }, children: cardFormBlock }),
3848
+ showStripe && isApmView && expandedApmMethod && apmInlineOptions && stripeInstance && /* @__PURE__ */ jsx6("div", { style: {
3849
+ gridArea: "1 / 1",
3850
+ ...apmAnim ? { animation: apmAnim } : {},
3851
+ ...viewState === "apm-collapsing" ? { pointerEvents: "none" } : {}
3852
+ }, children: /* @__PURE__ */ jsxs4("div", { style: {
3853
+ backgroundColor: cardBg,
3854
+ borderRadius: containerRadius,
3855
+ ...containerOverrides,
3856
+ padding: containerPadding
3857
+ }, children: [
3858
+ /* @__PURE__ */ jsxs4("div", { style: {
3859
+ display: "flex",
3860
+ alignItems: "center",
3861
+ padding: "0.75rem 0 0.625rem"
3862
+ }, children: [
3863
+ /* @__PURE__ */ jsxs4(
3864
+ "button",
3865
+ {
3866
+ type: "button",
3867
+ "data-testid": "flopay-apm-back-button",
3868
+ onClick: collapseFromApm,
3869
+ style: {
3870
+ display: "inline-flex",
3871
+ alignItems: "center",
3872
+ gap: hideBackButtonLabel ? 0 : "0.5rem",
3873
+ background: "none",
3874
+ border: "none",
3875
+ cursor: "pointer",
3876
+ color: "#4b5563",
3877
+ fontSize: bStyles.backButtonFontSize ?? "0.85rem",
3878
+ fontWeight: 500,
3879
+ padding: 0,
3880
+ transition: "color 0.15s",
3881
+ flexShrink: 0,
3882
+ ...bStyles.backButton
3883
+ },
3884
+ "aria-label": "Back to payment methods",
3885
+ children: [
3886
+ /* @__PURE__ */ jsx6("span", { style: {
3887
+ display: "inline-flex",
3888
+ alignItems: "center",
3889
+ justifyContent: "center",
3890
+ width: 28,
3891
+ height: 28,
3892
+ borderRadius: "50%",
3893
+ backgroundColor: "#f3f4f6",
3894
+ transition: "background-color 0.15s",
3895
+ ...bStyles.backButtonIcon
3896
+ }, children: /* @__PURE__ */ jsx6("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx6("path", { d: "M15 18l-6-6 6-6" }) }) }),
3897
+ /* @__PURE__ */ jsx6(BackButtonContentSlot, { content: cardBackButtonContent })
3898
+ ]
3899
+ }
3900
+ ),
3901
+ /* @__PURE__ */ jsx6("div", { style: {
3902
+ flex: 1,
3903
+ textAlign: "center",
3904
+ fontWeight: 600,
3905
+ fontSize: bStyles.titleFontSize ?? "1.05rem",
3906
+ color: resolvedTitleColor,
3907
+ paddingRight: 80,
3908
+ ...bStyles.title
3909
+ }, children: `Pay with ${getStripeMethodDisplayName(expandedApmMethod)}` })
3910
+ ] }),
3911
+ /* @__PURE__ */ jsx6(
3912
+ StripeElements,
3913
+ {
3914
+ stripe: stripeInstance,
3915
+ options: apmInlineOptions,
3916
+ children: /* @__PURE__ */ jsx6(
3917
+ StripeMethodInlineForm,
3918
+ {
3919
+ method: expandedApmMethod,
3920
+ sessionId,
3921
+ email: resolvedAccount.email,
3922
+ billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
3923
+ billingApiUrl: resolvedBillingApiUrl,
3924
+ onTokenizedBody: dispatchTokenizedBody,
3925
+ onErrorChange: updateError,
3926
+ onDecline,
3927
+ onCancel: collapseFromApm,
3928
+ runBeforeButtonClick,
3929
+ onButtonClick,
3930
+ isProcessing: isSubmitting,
3931
+ submitButtonColor: resolvedPrimaryColor,
3932
+ submitButtonBorderRadius: resolvedBorderRadius,
3933
+ submitButtonStyle: bStyles.submitButton
3934
+ }
3935
+ )
3936
+ },
3937
+ expandedApmMethod
3938
+ )
3939
+ ] }) })
2958
3940
  ] })
2959
3941
  ] });
2960
3942
  }
3943
+ if (isApmView && expandedApmMethod && apmInlineOptions && stripeInstance && showStripe) {
3944
+ return /* @__PURE__ */ jsxs4("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
3945
+ /* @__PURE__ */ jsx6(FloPayKeyframes, {}),
3946
+ overlayStatus && /* @__PURE__ */ jsx6(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
3947
+ /* @__PURE__ */ jsx6("div", { style: {
3948
+ ...apmAnim ? { animation: apmAnim } : {},
3949
+ ...viewState === "apm-collapsing" ? { pointerEvents: "none" } : {}
3950
+ }, children: /* @__PURE__ */ jsxs4("div", { style: {
3951
+ backgroundColor: cardBg,
3952
+ borderRadius: containerRadius,
3953
+ ...containerOverrides,
3954
+ padding: containerPadding
3955
+ }, children: [
3956
+ /* @__PURE__ */ jsxs4("div", { style: {
3957
+ display: "flex",
3958
+ alignItems: "center",
3959
+ padding: "0.75rem 0 0.625rem"
3960
+ }, children: [
3961
+ /* @__PURE__ */ jsxs4(
3962
+ "button",
3963
+ {
3964
+ type: "button",
3965
+ "data-testid": "flopay-apm-back-button-default",
3966
+ onClick: collapseFromApm,
3967
+ style: {
3968
+ display: "inline-flex",
3969
+ alignItems: "center",
3970
+ gap: hideBackButtonLabel ? 0 : "0.5rem",
3971
+ background: "none",
3972
+ border: "none",
3973
+ cursor: "pointer",
3974
+ color: "#4b5563",
3975
+ fontSize: bStyles.backButtonFontSize ?? "0.85rem",
3976
+ fontWeight: 500,
3977
+ padding: 0,
3978
+ transition: "color 0.15s",
3979
+ flexShrink: 0,
3980
+ ...bStyles.backButton
3981
+ },
3982
+ "aria-label": "Back to payment methods",
3983
+ children: [
3984
+ /* @__PURE__ */ jsx6("span", { style: {
3985
+ display: "inline-flex",
3986
+ alignItems: "center",
3987
+ justifyContent: "center",
3988
+ width: 28,
3989
+ height: 28,
3990
+ borderRadius: "50%",
3991
+ backgroundColor: "#f3f4f6",
3992
+ transition: "background-color 0.15s",
3993
+ ...bStyles.backButtonIcon
3994
+ }, children: /* @__PURE__ */ jsx6("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx6("path", { d: "M15 18l-6-6 6-6" }) }) }),
3995
+ /* @__PURE__ */ jsx6(BackButtonContentSlot, { content: cardBackButtonContent })
3996
+ ]
3997
+ }
3998
+ ),
3999
+ /* @__PURE__ */ jsx6("div", { style: {
4000
+ flex: 1,
4001
+ textAlign: "center",
4002
+ fontWeight: 600,
4003
+ fontSize: bStyles.titleFontSize ?? "1.05rem",
4004
+ color: resolvedTitleColor,
4005
+ paddingRight: 80,
4006
+ ...bStyles.title
4007
+ }, children: `Pay with ${getStripeMethodDisplayName(expandedApmMethod)}` })
4008
+ ] }),
4009
+ /* @__PURE__ */ jsx6(
4010
+ StripeElements,
4011
+ {
4012
+ stripe: stripeInstance,
4013
+ options: apmInlineOptions,
4014
+ children: /* @__PURE__ */ jsx6(
4015
+ StripeMethodInlineForm,
4016
+ {
4017
+ method: expandedApmMethod,
4018
+ sessionId,
4019
+ email: resolvedAccount.email,
4020
+ billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
4021
+ billingApiUrl: resolvedBillingApiUrl,
4022
+ onTokenizedBody: dispatchTokenizedBody,
4023
+ onErrorChange: updateError,
4024
+ onDecline,
4025
+ onCancel: collapseFromApm,
4026
+ runBeforeButtonClick,
4027
+ onButtonClick,
4028
+ isProcessing: isSubmitting,
4029
+ submitButtonColor: resolvedPrimaryColor,
4030
+ submitButtonBorderRadius: resolvedBorderRadius,
4031
+ submitButtonStyle: bStyles.submitButton
4032
+ }
4033
+ )
4034
+ },
4035
+ expandedApmMethod
4036
+ )
4037
+ ] }) })
4038
+ ] });
4039
+ }
2961
4040
  return /* @__PURE__ */ jsxs4("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
2962
4041
  /* @__PURE__ */ jsx6(FloPayKeyframes, {}),
2963
4042
  overlayStatus && /* @__PURE__ */ jsx6(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
2964
4043
  /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
2965
- debug && showPayPal && /* @__PURE__ */ jsx6(
2966
- "pre",
2967
- {
2968
- "data-testid": "flopay-direct-paypal-gate-debug-default",
2969
- style: {
2970
- margin: 0,
2971
- padding: "6px 8px",
2972
- background: "#eef2ff",
2973
- border: "1px solid #c7d2fe",
2974
- borderRadius: 6,
2975
- color: "#111827",
2976
- font: "11px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace",
2977
- whiteSpace: "pre-wrap",
2978
- wordBreak: "break-word"
2979
- },
2980
- children: [
2981
- "FloPay/DirectPayPal-debug (parent gate)",
2982
- ` showPayPal=${showPayPal}`,
2983
- ` directPaypalConfigured=${directPaypalConfigured}`,
2984
- ` inAppBrowserDetected=${String(inAppBrowserDetected)}`,
2985
- ` shouldRenderDirectPayPal=${shouldRenderDirectPayPal}`,
2986
- ` shouldRenderStripePayPal=${shouldRenderStripePayPal}`,
2987
- ` hasPaypalStripeInstance=${!!paypalStripeInstance}`
2988
- ].join("\n")
2989
- }
2990
- ),
4044
+ renderDirectPaypalGateDebug("flopay-direct-paypal-gate-debug-default"),
4045
+ renderStripeGateDebug("flopay-stripe-gate-debug-default"),
2991
4046
  shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2992
4047
  paypalDirectRetry && /* @__PURE__ */ jsx6(
2993
4048
  "div",
@@ -3042,7 +4097,8 @@ function SplitCardFormInner({
3042
4097
  onButtonClick,
3043
4098
  onDecline,
3044
4099
  runBeforeButtonClick,
3045
- onLoadStateChange: setPaypalLoadState
4100
+ onLoadStateChange: setPaypalLoadState,
4101
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
3046
4102
  }
3047
4103
  ) }),
3048
4104
  shouldRenderWallets && /* @__PURE__ */ jsx6(StripeElements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ jsx6(
@@ -3051,18 +4107,49 @@ function SplitCardFormInner({
3051
4107
  sessionId,
3052
4108
  email: resolvedAccount.email,
3053
4109
  billingApiUrl: resolvedBillingApiUrl,
3054
- showApplePay,
3055
- showGooglePay,
4110
+ expressMethods: walletExpressMethods,
4111
+ onTokenizedBody: dispatchTokenizedBody,
4112
+ onErrorChange: updateError,
4113
+ onButtonClick,
4114
+ onDecline,
4115
+ runBeforeButtonClick,
4116
+ onLoadStateChange: setWalletLoadState,
4117
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
4118
+ }
4119
+ ) }),
4120
+ shouldRenderPaymentElement && /* @__PURE__ */ jsx6(
4121
+ StripePaymentElementInner,
4122
+ {
4123
+ sessionId,
4124
+ email: resolvedAccount.email,
4125
+ billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
4126
+ billingApiUrl: resolvedBillingApiUrl,
4127
+ paymentElementMethods: paymentElementMethodsForCurrency,
4128
+ stripeInstance,
4129
+ paymentElementBaseOptions,
3056
4130
  onTokenizedBody: dispatchTokenizedBody,
3057
4131
  onErrorChange: updateError,
3058
4132
  onButtonClick,
3059
4133
  onDecline,
3060
4134
  runBeforeButtonClick,
3061
- onLoadStateChange: setWalletLoadState
4135
+ onLoadStateChange: setPaymentElementLoadState,
4136
+ isProcessing: isSubmitting,
4137
+ submitButtonColor: resolvedPrimaryColor,
4138
+ submitButtonBorderRadius: resolvedBorderRadius,
4139
+ submitButtonStyle: bStyles.submitButton,
4140
+ themeId: theme,
4141
+ onExpandApm: expandToApm,
4142
+ expandedApmMethod,
4143
+ buttonAppearance: {
4144
+ backgroundColor: bStyles.cardButton?.backgroundColor,
4145
+ borderColor: bStyles.cardInputBorder,
4146
+ textColor: bStyles.cardButton?.color,
4147
+ borderRadius: bStyles.cardButton?.borderRadius
4148
+ }
3062
4149
  }
3063
- ) })
4150
+ )
3064
4151
  ] }),
3065
- (shouldDisplayWalletRow || shouldDisplayPayPalRow) && /* @__PURE__ */ jsxs4("div", { style: {
4152
+ showStripe && (shouldDisplayWalletRow || shouldDisplayPayPalRow || shouldDisplayPaymentElementRow) && /* @__PURE__ */ jsxs4("div", { style: {
3066
4153
  display: "flex",
3067
4154
  alignItems: "center",
3068
4155
  gap: "0.75rem",
@@ -3074,7 +4161,7 @@ function SplitCardFormInner({
3074
4161
  /* @__PURE__ */ jsx6("span", { children: "or pay with card" }),
3075
4162
  /* @__PURE__ */ jsx6("div", { style: { flex: 1, height: 1, backgroundColor: "#ddd" } })
3076
4163
  ] }),
3077
- cardFormBlock
4164
+ showStripe && cardFormBlock
3078
4165
  ] });
3079
4166
  }
3080
4167
 
@@ -3607,6 +4694,7 @@ function hasInlineSessionPatchData(patch) {
3607
4694
  }
3608
4695
  function FloPayCheckout({
3609
4696
  sessionId: sessionIdProp,
4697
+ nonce: nonceProp,
3610
4698
  createSession: createSessionParams,
3611
4699
  billingApiUrl,
3612
4700
  appearance: appearanceOverride,
@@ -3620,6 +4708,7 @@ function FloPayCheckout({
3620
4708
  onCountryChange,
3621
4709
  onZipChange,
3622
4710
  showPayPal = true,
4711
+ showStripe = true,
3623
4712
  showApplePay = true,
3624
4713
  showGooglePay = true,
3625
4714
  debug = false,
@@ -3966,6 +5055,14 @@ function FloPayCheckout({
3966
5055
  },
3967
5056
  successUrl: params?.successUrl,
3968
5057
  cancelUrl: params?.cancelUrl,
5058
+ // Unified products[] is the post-#760 wire shape. Must be included or
5059
+ // carts that only set `products` (no legacy items/subscriptions) all
5060
+ // hash to the same key — the bootstrap effect's `createSessionHash`
5061
+ // dep then never changes when products change, the session-id cache
5062
+ // hits the wrong session, and the backend's enabledPaymentMethods
5063
+ // result for the *previous* cart sticks until a fresh page-load with
5064
+ // the session cache cleared.
5065
+ p: params?.products?.map((x) => `${x.type ?? ""}:${x.code ?? x.providerItemId ?? x.providerPlanId ?? ""}:${x.totalAmount ?? ""}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
3969
5066
  i: params?.items?.map((x) => `${x.code ?? x.providerItemId ?? ""}:${x.totalAmount ?? ""}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
3970
5067
  s: params?.subscriptions?.map((x) => `${x.code ?? x.providerPlanId ?? ""}:${x.totalAmount ?? ""}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
3971
5068
  couponCodes: params?.couponCodes,
@@ -4192,7 +5289,7 @@ function FloPayCheckout({
4192
5289
  async function init() {
4193
5290
  try {
4194
5291
  const api = new PaymentAPI4(resolvedBillingUrl);
4195
- const result = await api.getUnifiedCheckoutSession(activeSessionId);
5292
+ const result = await api.getUnifiedCheckoutSession(activeSessionId, nonceProp);
4196
5293
  if (cancelled) return;
4197
5294
  setUnified(result);
4198
5295
  const sess = result.data.session ?? null;
@@ -4279,6 +5376,7 @@ function FloPayCheckout({
4279
5376
  createSessionHash,
4280
5377
  effectiveCreateSessionMode,
4281
5378
  initSessionDependency,
5379
+ nonceProp,
4282
5380
  runSavedPaymentFlow
4283
5381
  ]);
4284
5382
  const handleConfirmCheckout = useCallback2(async () => {
@@ -4352,17 +5450,18 @@ function FloPayCheckout({
4352
5450
  ] });
4353
5451
  }
4354
5452
  if (layout === "buttons") {
5453
+ const bundleRadius = themeBundle?.buttonsLayout?.cardButton?.borderRadius ?? themeBundle?.appearance.variables?.borderRadius ?? 8;
4355
5454
  const skeletonBar = (h) => /* @__PURE__ */ jsx7("div", { style: {
4356
5455
  height: h,
4357
- borderRadius: 8,
5456
+ borderRadius: bundleRadius,
4358
5457
  background: "#e5e7eb",
4359
5458
  animation: "flopay-loading-pulse 1.5s ease-in-out infinite"
4360
5459
  } });
4361
5460
  return /* @__PURE__ */ jsxs5(Fragment3, { children: [
4362
5461
  /* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
4363
5462
  showPayPal && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4364
- (showApplePay || showGooglePay) && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4365
- skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
5463
+ showStripe && (showApplePay || showGooglePay) && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
5464
+ showStripe && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4366
5465
  /* @__PURE__ */ jsx7("style", { children: `@keyframes flopay-loading-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
4367
5466
  ] }),
4368
5467
  modeOverlay
@@ -4407,6 +5506,7 @@ function FloPayCheckout({
4407
5506
  {
4408
5507
  onButtonClick,
4409
5508
  showPayPal,
5509
+ showStripe,
4410
5510
  showApplePay,
4411
5511
  showGooglePay,
4412
5512
  theme,
@@ -4551,6 +5651,8 @@ function FloPayCheckout({
4551
5651
  onErrorChange: setModeError,
4552
5652
  onFullNameChange,
4553
5653
  showPayPal,
5654
+ showStripe,
5655
+ enabledPaymentMethods: unified?.data.stripe?.enabledPaymentMethods,
4554
5656
  showApplePay,
4555
5657
  showGooglePay,
4556
5658
  layout,
@@ -4615,6 +5717,7 @@ function InterimButtonsView({
4615
5717
  cardOpen,
4616
5718
  errorMessage,
4617
5719
  showPayPal,
5720
+ showStripe = true,
4618
5721
  showApplePay,
4619
5722
  showGooglePay,
4620
5723
  theme,
@@ -4646,9 +5749,10 @@ function InterimButtonsView({
4646
5749
  title: { ...base.title, ...stylesOverride.title }
4647
5750
  };
4648
5751
  }, [themeBundle, buttonsTheme, stylesOverride]);
5752
+ const skeletonRadius = themeBundle?.buttonsLayout?.cardButton?.borderRadius ?? themeBundle?.appearance.variables?.borderRadius ?? 8;
4649
5753
  const skeleton = (h) => /* @__PURE__ */ jsx7("div", { style: {
4650
5754
  height: h,
4651
- borderRadius: 8,
5755
+ borderRadius: skeletonRadius,
4652
5756
  background: "#e5e7eb",
4653
5757
  animation: "flopay-interim-pulse 1.5s ease-in-out infinite"
4654
5758
  } });
@@ -4719,8 +5823,28 @@ function InterimButtonsView({
4719
5823
  ] }),
4720
5824
  /* @__PURE__ */ jsx7("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTopLeftRadius: 8, borderTopRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx7("div", { style: { width: "60%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
4721
5825
  /* @__PURE__ */ jsxs5("div", { style: { display: "flex" }, children: [
4722
- /* @__PURE__ */ jsx7("div", { style: { flex: 1, backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTop: "none", borderRight: "none", borderBottomLeftRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx7("div", { style: { width: "50%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
4723
- /* @__PURE__ */ jsx7("div", { style: { flex: 1, backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTop: "none", borderBottomRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx7("div", { style: { width: "40%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) })
5826
+ /* @__PURE__ */ jsx7("div", { style: {
5827
+ flex: 1,
5828
+ backgroundColor: inputBg,
5829
+ borderTop: "none",
5830
+ borderRight: "none",
5831
+ borderBottom: `1px solid ${inputBorder}`,
5832
+ borderLeft: `1px solid ${inputBorder}`,
5833
+ borderBottomLeftRadius: 8,
5834
+ padding: 12,
5835
+ height: 45
5836
+ }, children: /* @__PURE__ */ jsx7("div", { style: { width: "50%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
5837
+ /* @__PURE__ */ jsx7("div", { style: {
5838
+ flex: 1,
5839
+ backgroundColor: inputBg,
5840
+ borderTop: "none",
5841
+ borderRight: `1px solid ${inputBorder}`,
5842
+ borderBottom: `1px solid ${inputBorder}`,
5843
+ borderLeft: `1px solid ${inputBorder}`,
5844
+ borderBottomRightRadius: 8,
5845
+ padding: 12,
5846
+ height: 45
5847
+ }, children: /* @__PURE__ */ jsx7("div", { style: { width: "40%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) })
4724
5848
  ] }),
4725
5849
  /* @__PURE__ */ jsx7("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderRadius: 8, marginTop: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx7("div", { style: { width: "45%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
4726
5850
  /* @__PURE__ */ jsx7("div", { style: {
@@ -4744,8 +5868,8 @@ function InterimButtonsView({
4744
5868
  }
4745
5869
  return /* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
4746
5870
  showPayPal && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4747
- (showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4748
- /* @__PURE__ */ jsx7(
5871
+ showStripe && (showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
5872
+ showStripe && /* @__PURE__ */ jsx7(
4749
5873
  "button",
4750
5874
  {
4751
5875
  type: "button",
@@ -4814,7 +5938,7 @@ import { PaymentAPI as PaymentAPI5 } from "@flopay/js";
4814
5938
  import { FloPayError as FloPayError6 } from "@flopay/shared";
4815
5939
  import { forwardRef as forwardRef2, useCallback as useCallback3, useEffect as useEffect6, useImperativeHandle as useImperativeHandle2, useState as useState5 } from "react";
4816
5940
  import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
4817
- var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
5941
+ var WALLET_RESUME_KEY = "flopay_wallet_resume";
4818
5942
  var CheckoutForm = forwardRef2(
4819
5943
  function CheckoutForm2(props, ref) {
4820
5944
  return /* @__PURE__ */ jsx8(CheckoutFormInner, { ...props, innerRef: ref });
@@ -4940,7 +6064,7 @@ function CheckoutFormInner({
4940
6064
  onDecline?.(buildDeclineEvent("paypal", message));
4941
6065
  return;
4942
6066
  }
4943
- localStorage.setItem(WALLET_RESUME_KEY2, JSON.stringify({
6067
+ localStorage.setItem(WALLET_RESUME_KEY, JSON.stringify({
4944
6068
  sessionId,
4945
6069
  paymentIntentId: "",
4946
6070
  tokenType: "card",
@@ -5016,12 +6140,12 @@ function CheckoutFormInner({
5016
6140
  }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
5017
6141
  useEffect6(() => {
5018
6142
  if (typeof window === "undefined") return;
5019
- const stored = localStorage.getItem(WALLET_RESUME_KEY2);
6143
+ const stored = localStorage.getItem(WALLET_RESUME_KEY);
5020
6144
  if (!stored) return;
5021
6145
  try {
5022
6146
  const payload = JSON.parse(stored);
5023
6147
  if (payload.sessionId === sessionId) {
5024
- localStorage.removeItem(WALLET_RESUME_KEY2);
6148
+ localStorage.removeItem(WALLET_RESUME_KEY);
5025
6149
  dispatchTokenizedBody({
5026
6150
  id: payload.tokenId,
5027
6151
  type: payload.tokenType,
@@ -5029,7 +6153,7 @@ function CheckoutFormInner({
5029
6153
  });
5030
6154
  }
5031
6155
  } catch {
5032
- localStorage.removeItem(WALLET_RESUME_KEY2);
6156
+ localStorage.removeItem(WALLET_RESUME_KEY);
5033
6157
  }
5034
6158
  }, [sessionId, dispatchTokenizedBody]);
5035
6159
  const handleSubmit = useCallback3(
@@ -5374,10 +6498,12 @@ function resolveCreateSessionDraft(props) {
5374
6498
  if (!props.clientId || !props.account || !props.successUrl || !props.cancelUrl) {
5375
6499
  return null;
5376
6500
  }
6501
+ const hasProducts = props.products != null;
5377
6502
  return {
5378
6503
  clientId: props.clientId,
5379
- items: props.items,
5380
- subscriptions: props.subscriptions,
6504
+ products: props.products,
6505
+ items: hasProducts ? void 0 : props.items,
6506
+ subscriptions: hasProducts ? void 0 : props.subscriptions,
5381
6507
  account: props.account,
5382
6508
  successUrl: props.successUrl,
5383
6509
  cancelUrl: props.cancelUrl,
@@ -5395,6 +6521,7 @@ function FloPayAutomaticPaymentButton({
5395
6521
  paymentMethodId: _deprecatedPaymentMethodId,
5396
6522
  checkoutMethod: _deprecatedCheckoutMethod,
5397
6523
  clientId,
6524
+ products,
5398
6525
  items,
5399
6526
  subscriptions,
5400
6527
  account,
@@ -5425,6 +6552,7 @@ function FloPayAutomaticPaymentButton({
5425
6552
  () => resolveCreateSessionDraft({
5426
6553
  createSession,
5427
6554
  clientId,
6555
+ products,
5428
6556
  items,
5429
6557
  subscriptions,
5430
6558
  account,
@@ -5440,6 +6568,7 @@ function FloPayAutomaticPaymentButton({
5440
6568
  clientId,
5441
6569
  couponCodes,
5442
6570
  createSession,
6571
+ products,
5443
6572
  items,
5444
6573
  subscriptions,
5445
6574
  successUrl,
@@ -5728,10 +6857,20 @@ function FloPayAutomaticPaymentButton({
5728
6857
  style: {
5729
6858
  width: "100%",
5730
6859
  ...cardButtonSizing,
5731
- backgroundColor: "white",
5732
- color: "#262833",
5733
- border: "1px solid #d1d5db",
5734
- borderRadius: "8px",
6860
+ // `bStyles.cardButton` first so the bundle's padding / typography
6861
+ // / radius / shadow come through, then `derivePrimaryTileStyle`
6862
+ // on top so the auto-pay button carries the *submit* button's
6863
+ // background and text colours — making it visually identical to
6864
+ // the "Confirm Payment" / "Pay with X" actions it stands in for.
6865
+ // Inline `style` consumer override stays at the end so explicit
6866
+ // per-button overrides still win.
6867
+ ...bStyles.cardButton,
6868
+ ...derivePrimaryTileStyle({
6869
+ themeBundle,
6870
+ resolvedPrimaryColor: themeBundle?.appearance.variables?.colorPrimary ?? "#4A49FF",
6871
+ resolvedBorderRadius: themeBundle?.appearance.variables?.borderRadius ?? "8px",
6872
+ submitButtonStyle: bStyles.submitButton
6873
+ }),
5735
6874
  fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
5736
6875
  fontWeight: 600,
5737
6876
  cursor: disabled || isProcessing ? "not-allowed" : "pointer",
@@ -5739,11 +6878,9 @@ function FloPayAutomaticPaymentButton({
5739
6878
  alignItems: "center",
5740
6879
  justifyContent: "center",
5741
6880
  gap: "0.625rem",
5742
- boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
5743
6881
  transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
5744
6882
  position: "relative",
5745
6883
  opacity: disabled || isProcessing ? 0.6 : 1,
5746
- ...bStyles.cardButton,
5747
6884
  ...style
5748
6885
  },
5749
6886
  onMouseDown: (e) => {