@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.cjs CHANGED
@@ -287,7 +287,7 @@ var AddressElement = createElementComponent("address", "AddressElement");
287
287
  var import_react_stripe_js = require("@stripe/react-stripe-js");
288
288
  var import_shared5 = require("@flopay/shared");
289
289
  var import_js2 = require("@flopay/js");
290
- var import_react8 = require("react");
290
+ var import_react8 = __toESM(require("react"), 1);
291
291
 
292
292
  // src/hooks.ts
293
293
  var import_react5 = require("react");
@@ -1261,9 +1261,11 @@ ${debugLines.join("\n")}`
1261
1261
  // src/split-card-form.tsx
1262
1262
  var import_shared6 = require("@flopay/shared");
1263
1263
  var import_jsx_runtime6 = require("react/jsx-runtime");
1264
- var WALLET_RESUME_KEY = "flopay_wallet_resume";
1264
+ var STRIPE_RESUME_KEY = "flopay_stripe_resume";
1265
+ var LEGACY_WALLET_RESUME_KEY = "flopay_wallet_resume";
1265
1266
  var FLOPAY_KEYFRAMES = `
1266
1267
  .paypal-buttons { margin: 0 !important; vertical-align: top !important; }
1268
+ @keyframes flopay-spin { to { transform: rotate(360deg); } }
1267
1269
  @keyframes flopay-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
1268
1270
  @keyframes flopay-fade-in { 0% { opacity: 0; } 100% { opacity: 1; } }
1269
1271
  @keyframes flopay-buttons-enter {
@@ -1284,6 +1286,25 @@ var FLOPAY_KEYFRAMES = `
1284
1286
  }
1285
1287
  `;
1286
1288
  var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT = 44;
1289
+ function derivePrimaryTileStyle(opts) {
1290
+ if (!opts.themeBundle) {
1291
+ return {
1292
+ backgroundColor: "white",
1293
+ color: "#262833",
1294
+ border: "1px solid #d1d5db",
1295
+ borderRadius: "8px",
1296
+ boxShadow: "0 1px 2px rgba(0,0,0,0.04)"
1297
+ };
1298
+ }
1299
+ const submit = opts.submitButtonStyle ?? {};
1300
+ return {
1301
+ backgroundColor: submit.backgroundColor ?? opts.resolvedPrimaryColor,
1302
+ color: submit.color ?? "white",
1303
+ border: submit.border ?? "none",
1304
+ borderRadius: submit.borderRadius ?? opts.resolvedBorderRadius,
1305
+ boxShadow: submit.boxShadow
1306
+ };
1307
+ }
1287
1308
  var BUTTONS_PANEL_HIDDEN_STYLE = {
1288
1309
  visibility: "hidden",
1289
1310
  position: "absolute",
@@ -1329,6 +1350,7 @@ function resolveExpressCheckoutLoadState(event, methods) {
1329
1350
  function ExpressCheckoutReadySwap({
1330
1351
  state,
1331
1352
  placeholderTestId,
1353
+ borderRadius = 8,
1332
1354
  children
1333
1355
  }) {
1334
1356
  if (state === "unavailable" || state === "load_error") return null;
@@ -1342,7 +1364,7 @@ function ExpressCheckoutReadySwap({
1342
1364
  position: "absolute",
1343
1365
  inset: 0,
1344
1366
  height: 44,
1345
- borderRadius: 8,
1367
+ borderRadius,
1346
1368
  background: "#e5e7eb",
1347
1369
  animation: state === "ready" ? void 0 : "flopay-pulse 1.5s ease-in-out infinite",
1348
1370
  opacity: state === "ready" ? 0 : 1,
@@ -1385,7 +1407,8 @@ function PayPalButtonInner({
1385
1407
  onButtonClick,
1386
1408
  onDecline,
1387
1409
  runBeforeButtonClick,
1388
- onLoadStateChange
1410
+ onLoadStateChange,
1411
+ placeholderBorderRadius
1389
1412
  }) {
1390
1413
  const stripe = (0, import_react_stripe_js.useStripe)();
1391
1414
  const elements = (0, import_react_stripe_js.useElements)();
@@ -1555,31 +1578,39 @@ function PayPalButtonInner({
1555
1578
  }
1556
1579
  }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]);
1557
1580
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1558
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-paypal-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1559
- import_react_stripe_js.ExpressCheckoutElement,
1581
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1582
+ ExpressCheckoutReadySwap,
1560
1583
  {
1561
- onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["paypal"])),
1562
- onLoadError: () => setLoadState("load_error"),
1563
- onClick: handlePayPalClick,
1564
- onConfirm: handlePayPalConfirm,
1565
- onCancel: () => {
1566
- beforeClickRef.current = null;
1567
- onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
1568
- },
1569
- options: {
1570
- buttonType: { paypal: "paypal" },
1571
- billingAddressRequired: false,
1572
- phoneNumberRequired: false,
1573
- shippingAddressRequired: false,
1574
- paymentMethods: {
1575
- applePay: "never",
1576
- googlePay: "never",
1577
- paypal: "auto",
1578
- link: "never"
1584
+ state: loadState,
1585
+ placeholderTestId: "flopay-paypal-placeholder",
1586
+ borderRadius: placeholderBorderRadius,
1587
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1588
+ import_react_stripe_js.ExpressCheckoutElement,
1589
+ {
1590
+ onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["paypal"])),
1591
+ onLoadError: () => setLoadState("load_error"),
1592
+ onClick: handlePayPalClick,
1593
+ onConfirm: handlePayPalConfirm,
1594
+ onCancel: () => {
1595
+ beforeClickRef.current = null;
1596
+ onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
1597
+ },
1598
+ options: {
1599
+ buttonType: { paypal: "paypal" },
1600
+ billingAddressRequired: false,
1601
+ phoneNumberRequired: false,
1602
+ shippingAddressRequired: false,
1603
+ paymentMethods: {
1604
+ applePay: "never",
1605
+ googlePay: "never",
1606
+ paypal: "auto",
1607
+ link: "never"
1608
+ }
1609
+ }
1579
1610
  }
1580
- }
1611
+ )
1581
1612
  }
1582
- ) }),
1613
+ ),
1583
1614
  submitting && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProcessingOverlay, { status: "processing" })
1584
1615
  ] });
1585
1616
  }
@@ -1587,14 +1618,14 @@ function WalletButtonInner({
1587
1618
  sessionId,
1588
1619
  email,
1589
1620
  billingApiUrl,
1590
- showApplePay = true,
1591
- showGooglePay = true,
1621
+ expressMethods,
1592
1622
  onTokenizedBody,
1593
1623
  onErrorChange,
1594
1624
  onButtonClick,
1595
1625
  onDecline,
1596
1626
  runBeforeButtonClick,
1597
- onLoadStateChange
1627
+ onLoadStateChange,
1628
+ placeholderBorderRadius
1598
1629
  }) {
1599
1630
  const stripe = (0, import_react_stripe_js.useStripe)();
1600
1631
  const elements = (0, import_react_stripe_js.useElements)();
@@ -1612,10 +1643,9 @@ function WalletButtonInner({
1612
1643
  const walletType = event.expressPaymentType;
1613
1644
  let prepared = beforeClickRef.current;
1614
1645
  beforeClickRef.current = null;
1646
+ const buttonMethod = walletType === "apple_pay" ? "apple_pay" : "google_pay";
1615
1647
  if (!prepared && runBeforeButtonClick) {
1616
- const beforeClick = await runBeforeButtonClick(
1617
- walletType === "apple_pay" ? "apple_pay" : "google_pay"
1618
- );
1648
+ const beforeClick = await runBeforeButtonClick(buttonMethod);
1619
1649
  if (!beforeClick.proceed) {
1620
1650
  event.paymentFailed({ reason: "fail", message: "Wallet checkout was cancelled." });
1621
1651
  return;
@@ -1625,7 +1655,7 @@ function WalletButtonInner({
1625
1655
  sessionId: beforeClick.sessionId
1626
1656
  };
1627
1657
  }
1628
- const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
1658
+ const method = buttonMethod;
1629
1659
  const effectiveSessionId = prepared?.sessionId ?? sessionId;
1630
1660
  const effectiveEmail = prepared?.accountPatch?.email ?? email;
1631
1661
  try {
@@ -1695,53 +1725,655 @@ function WalletButtonInner({
1695
1725
  },
1696
1726
  [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]
1697
1727
  );
1728
+ const expressMethodMap = (0, import_react8.useMemo)(() => {
1729
+ const allKeys = ["applePay", "googlePay", "paypal", "link", "amazonPay", "klarna"];
1730
+ const alwaysCapable = /* @__PURE__ */ new Set(["applePay", "googlePay"]);
1731
+ const enabledKeys = new Set(expressMethods.map(import_shared5.stripeExpressMethodToOptionKey));
1732
+ const out = {};
1733
+ for (const key of allKeys) {
1734
+ if (!enabledKeys.has(key)) {
1735
+ out[key] = "never";
1736
+ } else {
1737
+ out[key] = alwaysCapable.has(key) ? "always" : "auto";
1738
+ }
1739
+ }
1740
+ return out;
1741
+ }, [expressMethods]);
1742
+ const availableMethodKeys = (0, import_react8.useMemo)(
1743
+ () => expressMethods.map(import_shared5.stripeExpressMethodToOptionKey),
1744
+ [expressMethods]
1745
+ );
1698
1746
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1699
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-wallet-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1700
- import_react_stripe_js.ExpressCheckoutElement,
1747
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1748
+ ExpressCheckoutReadySwap,
1701
1749
  {
1702
- onReady: (event) => {
1703
- setLoadState(resolveExpressCheckoutLoadState(event, ["applePay", "googlePay"]));
1704
- },
1705
- onLoadError: (e) => {
1706
- setLoadState("load_error");
1707
- },
1708
- onClick: async (event) => {
1709
- lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
1710
- const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick(lastWalletMethodRef.current) : { proceed: true };
1711
- if (!beforeClick.proceed) {
1712
- beforeClickRef.current = null;
1713
- event.reject();
1714
- return;
1750
+ state: loadState,
1751
+ placeholderTestId: "flopay-wallet-placeholder",
1752
+ borderRadius: placeholderBorderRadius,
1753
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1754
+ import_react_stripe_js.ExpressCheckoutElement,
1755
+ {
1756
+ onReady: (event) => {
1757
+ setLoadState(resolveExpressCheckoutLoadState(event, availableMethodKeys));
1758
+ },
1759
+ onLoadError: (_event) => {
1760
+ setLoadState("load_error");
1761
+ },
1762
+ onClick: async (event) => {
1763
+ lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
1764
+ const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick(lastWalletMethodRef.current) : { proceed: true };
1765
+ if (!beforeClick.proceed) {
1766
+ beforeClickRef.current = null;
1767
+ event.reject();
1768
+ return;
1769
+ }
1770
+ beforeClickRef.current = {
1771
+ accountPatch: beforeClick.accountPatch,
1772
+ sessionId: beforeClick.sessionId
1773
+ };
1774
+ onButtonClick?.(lastWalletMethodRef.current);
1775
+ event.resolve();
1776
+ },
1777
+ onConfirm: handleWalletConfirm,
1778
+ onCancel: () => {
1779
+ beforeClickRef.current = null;
1780
+ onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
1781
+ },
1782
+ options: {
1783
+ buttonType: { applePay: "plain", googlePay: "plain" },
1784
+ paymentMethods: expressMethodMap,
1785
+ layout: { maxColumns: 1, overflow: "never" }
1786
+ }
1715
1787
  }
1716
- beforeClickRef.current = {
1717
- accountPatch: beforeClick.accountPatch,
1718
- sessionId: beforeClick.sessionId
1719
- };
1720
- onButtonClick?.(lastWalletMethodRef.current);
1721
- event.resolve();
1788
+ )
1789
+ }
1790
+ ),
1791
+ submitting && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProcessingOverlay, { status: "processing" })
1792
+ ] });
1793
+ }
1794
+ function StripeMethodButton({
1795
+ method,
1796
+ themeId,
1797
+ submitting = false,
1798
+ disabled = false,
1799
+ highlighted = false,
1800
+ hasNextStep = false,
1801
+ onClick,
1802
+ backgroundColor,
1803
+ borderColor,
1804
+ textColor,
1805
+ borderRadius,
1806
+ fontFamily
1807
+ }) {
1808
+ const brand = (0, import_shared5.resolveStripeMethodBrandVariant)(method, themeId);
1809
+ const resolvedBackground = brand?.backgroundColor ?? backgroundColor ?? "#ffffff";
1810
+ const resolvedBorder = brand?.borderColor ?? borderColor ?? "#d1d5db";
1811
+ const resolvedTextColor = brand?.textColor ?? textColor ?? "#262833";
1812
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1813
+ "button",
1814
+ {
1815
+ type: "button",
1816
+ "data-testid": `flopay-stripe-method-button-${method}`,
1817
+ "data-method": method,
1818
+ onClick: () => {
1819
+ if (!submitting && !disabled) onClick();
1820
+ },
1821
+ disabled: submitting || disabled,
1822
+ "aria-busy": submitting || void 0,
1823
+ style: {
1824
+ position: "relative",
1825
+ width: "100%",
1826
+ boxSizing: "border-box",
1827
+ height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT,
1828
+ padding: "0 1rem",
1829
+ backgroundColor: resolvedBackground,
1830
+ color: resolvedTextColor,
1831
+ border: `1px solid ${resolvedBorder}`,
1832
+ borderRadius: borderRadius ?? 8,
1833
+ fontSize: "0.95rem",
1834
+ fontWeight: 600,
1835
+ fontFamily,
1836
+ cursor: submitting || disabled ? "not-allowed" : "pointer",
1837
+ opacity: disabled && !submitting ? 0.55 : 1,
1838
+ // Submitting state pulses the whole button background, mirroring
1839
+ // the wallet ECE's `flopay-pulse` skeleton. This keeps the loading
1840
+ // affordance consistent between embedded wallets (Apple Pay,
1841
+ // Google Pay) and these custom APM tile buttons — same animation
1842
+ // curve, same duration, only the surface colour changes (grey for
1843
+ // the wallet skeleton vs. brand colour for an in-flight tile).
1844
+ animation: submitting ? "flopay-pulse 1.5s ease-in-out infinite" : void 0,
1845
+ display: "flex",
1846
+ alignItems: "center",
1847
+ // Always center the logo+name group. The drill-in chevron (input
1848
+ // methods) is absolutely positioned at the right edge below so it
1849
+ // doesn't pull the label off-center.
1850
+ justifyContent: "center",
1851
+ gap: 8,
1852
+ transition: "transform 0.1s, opacity 140ms ease-out, border-color 140ms ease-out",
1853
+ boxShadow: highlighted ? "0 0 0 2px rgba(74, 73, 255, 0.15)" : void 0
1854
+ },
1855
+ children: submitting ? (
1856
+ // Pulse-skeleton parity with the wallet ECE: no spinner, just the
1857
+ // status text on top of the pulsing background. Keeps the loading
1858
+ // affordance shape-equivalent across both kinds of tile.
1859
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: { margin: "0 auto" }, children: `Connecting to ${(0, import_shared5.getStripeMethodDisplayName)(method)}\u2026` })
1860
+ ) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1861
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1862
+ "span",
1863
+ {
1864
+ style: {
1865
+ display: "inline-flex",
1866
+ alignItems: "center",
1867
+ // Real vendored logos are self-contained brand cards, so they
1868
+ // get an 8px gap from the label. The generated placeholder
1869
+ // monogram reads as part of the label (the tile's first letter),
1870
+ // so it sits flush (0px) against the name.
1871
+ gap: (0, import_shared5.hasVendoredStripeMethodLogo)(method) ? 8 : 0
1872
+ },
1873
+ children: [
1874
+ brand?.logoSvg && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1875
+ "span",
1876
+ {
1877
+ "aria-hidden": "true",
1878
+ style: {
1879
+ // 3:2 box matching the vendored datatrans logos' 120×80
1880
+ // viewBox. Each logo ships with its own white rounded-rect
1881
+ // background baked in, so the mark stays legible on any
1882
+ // brand-coloured tile without an extra chip wrapper here.
1883
+ width: 30,
1884
+ height: 20,
1885
+ flexShrink: 0,
1886
+ display: "inline-flex",
1887
+ borderRadius: 3,
1888
+ overflow: "hidden"
1889
+ },
1890
+ dangerouslySetInnerHTML: { __html: brand.logoSvg }
1891
+ }
1892
+ ),
1893
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: (0, import_shared5.getStripeMethodDisplayName)(method) })
1894
+ ]
1895
+ }
1896
+ ),
1897
+ hasNextStep && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1898
+ "svg",
1899
+ {
1900
+ width: "14",
1901
+ height: "14",
1902
+ viewBox: "0 0 24 24",
1903
+ fill: "none",
1904
+ stroke: resolvedTextColor,
1905
+ strokeWidth: "2.5",
1906
+ strokeLinecap: "round",
1907
+ strokeLinejoin: "round",
1908
+ style: {
1909
+ position: "absolute",
1910
+ right: "1rem",
1911
+ top: "50%",
1912
+ transform: "translateY(-50%)",
1913
+ flexShrink: 0,
1914
+ opacity: 0.7
1915
+ },
1916
+ "aria-hidden": "true",
1917
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M9 18l6-6-6-6" })
1918
+ }
1919
+ )
1920
+ ] })
1921
+ }
1922
+ );
1923
+ }
1924
+ function StripeMethodInlineForm({
1925
+ method,
1926
+ sessionId,
1927
+ email,
1928
+ billingName,
1929
+ billingApiUrl,
1930
+ onTokenizedBody,
1931
+ onErrorChange,
1932
+ onDecline,
1933
+ onCancel,
1934
+ runBeforeButtonClick,
1935
+ onButtonClick,
1936
+ isProcessing,
1937
+ submitButtonColor,
1938
+ submitButtonBorderRadius,
1939
+ submitButtonStyle
1940
+ }) {
1941
+ const stripe = (0, import_react_stripe_js.useStripe)();
1942
+ const elements = (0, import_react_stripe_js.useElements)();
1943
+ const [submitting, setSubmitting] = (0, import_react8.useState)(false);
1944
+ const submittingRef = (0, import_react8.useRef)(false);
1945
+ const [isMethodComplete, setIsMethodComplete] = (0, import_react8.useState)(false);
1946
+ const [loadState, setLoadState] = (0, import_react8.useState)("loading");
1947
+ const baseUrl = billingApiUrl.replace(/\/+$/, "");
1948
+ const handlePay = (0, import_react8.useCallback)(async () => {
1949
+ if (!stripe || !elements || isProcessing || submittingRef.current || !isMethodComplete) return;
1950
+ submittingRef.current = true;
1951
+ setSubmitting(true);
1952
+ const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick("card") : { proceed: true };
1953
+ if (!beforeClick.proceed) {
1954
+ submittingRef.current = false;
1955
+ setSubmitting(false);
1956
+ return;
1957
+ }
1958
+ onButtonClick?.("card");
1959
+ const effectiveSessionId = beforeClick.sessionId ?? sessionId;
1960
+ const effectiveEmail = beforeClick.accountPatch?.email ?? email;
1961
+ try {
1962
+ onErrorChange?.(null);
1963
+ const submitRes = await elements.submit();
1964
+ if (submitRes.error) {
1965
+ onErrorChange?.(submitRes.error.message ?? "Payment failed.");
1966
+ return;
1967
+ }
1968
+ const pmRes = await stripe.createPaymentMethod({ elements });
1969
+ if (pmRes.error || !pmRes.paymentMethod) {
1970
+ onErrorChange?.(pmRes.error?.message ?? "Failed to create payment method.");
1971
+ return;
1972
+ }
1973
+ const paymentMethod = pmRes.paymentMethod;
1974
+ if (!effectiveSessionId || !effectiveEmail) {
1975
+ throw new Error("Missing sessionId or email for payment.");
1976
+ }
1977
+ const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
1978
+ method: "POST",
1979
+ headers: { "Content-Type": "application/json" },
1980
+ body: JSON.stringify({
1981
+ sessionId: effectiveSessionId,
1982
+ email: effectiveEmail,
1983
+ paymentMethodType: paymentMethod.type || method,
1984
+ isPaypal: false
1985
+ })
1986
+ });
1987
+ if (!intentResponse.ok) {
1988
+ const intentError = await buildFloPayApiErrorFromResponse(intentResponse, "Failed to create payment intent");
1989
+ onErrorChange?.(intentError.message);
1990
+ onDecline?.(buildDeclineEvent("card", intentError));
1991
+ return;
1992
+ }
1993
+ const intentJson = await intentResponse.json();
1994
+ const intentClientSecret = intentJson.data?.id;
1995
+ if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
1996
+ if (typeof window !== "undefined") {
1997
+ try {
1998
+ localStorage.setItem(STRIPE_RESUME_KEY, JSON.stringify({
1999
+ sessionId: effectiveSessionId,
2000
+ paymentMethodId: paymentMethod.id,
2001
+ paymentMethodType: paymentMethod.type ?? method,
2002
+ gateway: "stripe"
2003
+ }));
2004
+ } catch {
2005
+ }
2006
+ }
2007
+ const { error: confirmError, paymentIntent } = await stripe.confirmPayment({
2008
+ clientSecret: intentClientSecret,
2009
+ confirmParams: {
2010
+ return_url: window.location.href,
2011
+ payment_method: paymentMethod.id
1722
2012
  },
1723
- onConfirm: handleWalletConfirm,
1724
- onCancel: () => {
1725
- beforeClickRef.current = null;
1726
- onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
2013
+ redirect: "if_required"
2014
+ });
2015
+ if (confirmError) {
2016
+ if (typeof window !== "undefined") {
2017
+ try {
2018
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2019
+ } catch {
2020
+ }
2021
+ }
2022
+ const message = confirmError.message ?? "Payment failed.";
2023
+ onErrorChange?.(message);
2024
+ onDecline?.(buildDeclineEvent("card", message, { code: confirmError.code }));
2025
+ return;
2026
+ }
2027
+ const SUCCESSFUL_PI_STATUSES = /* @__PURE__ */ new Set(["succeeded", "requires_capture", "processing"]);
2028
+ const piStatus = paymentIntent?.status;
2029
+ if (!piStatus || !SUCCESSFUL_PI_STATUSES.has(piStatus)) {
2030
+ if (typeof window !== "undefined") {
2031
+ try {
2032
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2033
+ } catch {
2034
+ }
2035
+ }
2036
+ const message = piStatus === "canceled" ? "Payment was canceled." : "Payment was not completed. Please try again.";
2037
+ onErrorChange?.(message);
2038
+ onDecline?.(buildDeclineEvent("card", message, { code: piStatus ?? "missing_payment_intent" }));
2039
+ return;
2040
+ }
2041
+ if (typeof window !== "undefined") {
2042
+ try {
2043
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2044
+ } catch {
2045
+ }
2046
+ }
2047
+ const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
2048
+ onTokenizedBody({
2049
+ id: confirmedPmId ?? paymentMethod.id,
2050
+ type: "card",
2051
+ threeDSecureActionResultTokenId: paymentIntent?.id,
2052
+ gateway: "stripe",
2053
+ paymentMethodType: paymentMethod.type ?? method
2054
+ }, {
2055
+ accountPatch: beforeClick.accountPatch,
2056
+ sessionId: effectiveSessionId
2057
+ });
2058
+ } catch (err) {
2059
+ onErrorChange?.(err instanceof Error ? err.message : "Payment failed. Please try again.");
2060
+ } finally {
2061
+ submittingRef.current = false;
2062
+ setSubmitting(false);
2063
+ }
2064
+ }, [
2065
+ stripe,
2066
+ elements,
2067
+ isProcessing,
2068
+ isMethodComplete,
2069
+ runBeforeButtonClick,
2070
+ onButtonClick,
2071
+ sessionId,
2072
+ email,
2073
+ baseUrl,
2074
+ method,
2075
+ onTokenizedBody,
2076
+ onErrorChange,
2077
+ onDecline
2078
+ ]);
2079
+ void onCancel;
2080
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { "data-testid": `flopay-stripe-method-form-${method}`, style: { display: "flex", flexDirection: "column" }, children: [
2081
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2082
+ import_react_stripe_js.PaymentElement,
2083
+ {
2084
+ onReady: () => setLoadState("ready"),
2085
+ onLoadError: () => setLoadState("load_error"),
2086
+ onChange: (event) => {
2087
+ const evRecord = event;
2088
+ setIsMethodComplete(!!evRecord.complete);
1727
2089
  },
1728
2090
  options: {
1729
- buttonType: { applePay: "plain", googlePay: "plain" },
1730
- paymentMethods: {
1731
- applePay: showApplePay ? "always" : "never",
1732
- googlePay: showGooglePay ? "always" : "never",
1733
- paypal: "never",
1734
- link: "never",
1735
- amazonPay: "never",
1736
- klarna: "never"
1737
- },
1738
- layout: { maxColumns: 1, overflow: "never" }
2091
+ layout: { type: "accordion", defaultCollapsed: false, radios: "never" },
2092
+ defaultValues: {
2093
+ billingDetails: {
2094
+ name: billingName && billingName.trim().length >= 3 ? billingName.trim() : "FloPay Customer",
2095
+ ...email ? { email } : {}
2096
+ }
2097
+ }
1739
2098
  }
1740
2099
  }
1741
- ) }),
1742
- submitting && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProcessingOverlay, { status: "processing" })
2100
+ ),
2101
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2102
+ "button",
2103
+ {
2104
+ type: "button",
2105
+ "data-testid": `flopay-stripe-method-pay-${method}`,
2106
+ onClick: () => {
2107
+ void handlePay();
2108
+ },
2109
+ disabled: !isMethodComplete || submitting || isProcessing || loadState !== "ready",
2110
+ style: {
2111
+ width: "100%",
2112
+ marginTop: "0.75rem",
2113
+ padding: "0.875rem",
2114
+ backgroundColor: submitButtonColor,
2115
+ color: "white",
2116
+ border: "none",
2117
+ borderRadius: submitButtonBorderRadius,
2118
+ fontSize: "1rem",
2119
+ fontWeight: 600,
2120
+ cursor: !isMethodComplete || submitting || isProcessing || loadState !== "ready" ? "not-allowed" : "pointer",
2121
+ opacity: !isMethodComplete || submitting || isProcessing || loadState !== "ready" ? 0.5 : 1,
2122
+ transition: "opacity 120ms ease-out",
2123
+ ...submitButtonStyle ?? {}
2124
+ },
2125
+ children: submitting ? "Processing\u2026" : `Pay with ${(0, import_shared5.getStripeMethodDisplayName)(method)}`
2126
+ }
2127
+ )
1743
2128
  ] });
1744
2129
  }
2130
+ function StripePaymentElementInner({
2131
+ sessionId,
2132
+ email,
2133
+ billingName,
2134
+ billingApiUrl,
2135
+ paymentElementMethods,
2136
+ stripeInstance,
2137
+ paymentElementBaseOptions,
2138
+ onTokenizedBody,
2139
+ onErrorChange,
2140
+ onButtonClick,
2141
+ onDecline,
2142
+ runBeforeButtonClick,
2143
+ onLoadStateChange,
2144
+ isProcessing = false,
2145
+ submitButtonColor = "#4A49FF",
2146
+ submitButtonBorderRadius = 8,
2147
+ submitButtonStyle,
2148
+ buttonAppearance,
2149
+ themeId,
2150
+ onExpandApm,
2151
+ expandedApmMethod
2152
+ }) {
2153
+ const baseUrl = billingApiUrl.replace(/\/+$/, "");
2154
+ const [expandedMethod, setExpandedMethod] = (0, import_react8.useState)(null);
2155
+ const [submittingMethod, setSubmittingMethod] = (0, import_react8.useState)(null);
2156
+ const submittingRef = (0, import_react8.useRef)(false);
2157
+ (0, import_react8.useEffect)(() => {
2158
+ if (paymentElementMethods.length > 0 && stripeInstance) {
2159
+ onLoadStateChange?.("ready");
2160
+ } else if (!stripeInstance) {
2161
+ onLoadStateChange?.("loading");
2162
+ }
2163
+ }, [paymentElementMethods.length, stripeInstance, onLoadStateChange]);
2164
+ const handleAutoConfirm = (0, import_react8.useCallback)(async (method) => {
2165
+ if (!stripeInstance) return;
2166
+ if (submittingRef.current || isProcessing) return;
2167
+ submittingRef.current = true;
2168
+ setSubmittingMethod(method);
2169
+ const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick("card") : { proceed: true };
2170
+ if (!beforeClick.proceed) {
2171
+ submittingRef.current = false;
2172
+ setSubmittingMethod(null);
2173
+ return;
2174
+ }
2175
+ onButtonClick?.("card");
2176
+ const effectiveSessionId = beforeClick.sessionId ?? sessionId;
2177
+ const effectiveEmail = beforeClick.accountPatch?.email ?? email;
2178
+ try {
2179
+ onErrorChange?.(null);
2180
+ if (!effectiveSessionId || !effectiveEmail) {
2181
+ throw new Error("Missing sessionId or email for payment.");
2182
+ }
2183
+ const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
2184
+ method: "POST",
2185
+ headers: { "Content-Type": "application/json" },
2186
+ body: JSON.stringify({
2187
+ sessionId: effectiveSessionId,
2188
+ email: effectiveEmail,
2189
+ // Wire type, not pm_xxx — backend scopes `payment_method_types`
2190
+ // to this single method so unrelated account-wide methods
2191
+ // (apple_pay without domain verification, …) don't poison PI
2192
+ // creation.
2193
+ paymentMethodType: method,
2194
+ isPaypal: false
2195
+ })
2196
+ });
2197
+ if (!intentResponse.ok) {
2198
+ const intentError = await buildFloPayApiErrorFromResponse(intentResponse, "Failed to create payment intent");
2199
+ onErrorChange?.(intentError.message);
2200
+ onDecline?.(buildDeclineEvent("card", intentError));
2201
+ return;
2202
+ }
2203
+ const intentJson = await intentResponse.json();
2204
+ const intentClientSecret = intentJson.data?.id;
2205
+ if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
2206
+ if (typeof window !== "undefined") {
2207
+ try {
2208
+ localStorage.setItem(STRIPE_RESUME_KEY, JSON.stringify({
2209
+ sessionId: effectiveSessionId,
2210
+ paymentMethodType: method,
2211
+ gateway: "stripe"
2212
+ }));
2213
+ } catch {
2214
+ }
2215
+ }
2216
+ const { error: confirmError, paymentIntent } = await stripeInstance.confirmPayment({
2217
+ clientSecret: intentClientSecret,
2218
+ confirmParams: {
2219
+ return_url: window.location.href,
2220
+ // Inline the method data — no Elements / PaymentElement
2221
+ // required. Stripe creates the PaymentMethod during confirm
2222
+ // and proceeds to its popup/redirect UI.
2223
+ payment_method_data: {
2224
+ type: method,
2225
+ billing_details: {
2226
+ name: billingName && billingName.trim().length >= 3 ? billingName.trim() : "FloPay Customer",
2227
+ ...effectiveEmail ? { email: effectiveEmail } : {}
2228
+ }
2229
+ }
2230
+ },
2231
+ redirect: "if_required"
2232
+ });
2233
+ if (confirmError) {
2234
+ if (typeof window !== "undefined") {
2235
+ try {
2236
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2237
+ } catch {
2238
+ }
2239
+ }
2240
+ const message = confirmError.message ?? "Payment failed.";
2241
+ onErrorChange?.(message);
2242
+ onDecline?.(buildDeclineEvent("card", message, { code: confirmError.code }));
2243
+ return;
2244
+ }
2245
+ const SUCCESSFUL_PI_STATUSES = /* @__PURE__ */ new Set(["succeeded", "requires_capture", "processing"]);
2246
+ const piStatus = paymentIntent?.status;
2247
+ if (!piStatus || !SUCCESSFUL_PI_STATUSES.has(piStatus)) {
2248
+ if (typeof window !== "undefined") {
2249
+ try {
2250
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2251
+ } catch {
2252
+ }
2253
+ }
2254
+ const message = piStatus === "canceled" ? "Payment was canceled." : "Payment was not completed. Please try again.";
2255
+ onErrorChange?.(message);
2256
+ onDecline?.(buildDeclineEvent("card", message, { code: piStatus ?? "missing_payment_intent" }));
2257
+ return;
2258
+ }
2259
+ if (typeof window !== "undefined") {
2260
+ try {
2261
+ localStorage.removeItem(STRIPE_RESUME_KEY);
2262
+ } catch {
2263
+ }
2264
+ }
2265
+ const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
2266
+ onTokenizedBody({
2267
+ id: confirmedPmId ?? paymentIntent?.id,
2268
+ type: "card",
2269
+ threeDSecureActionResultTokenId: paymentIntent?.id,
2270
+ gateway: "stripe",
2271
+ paymentMethodType: method
2272
+ }, {
2273
+ accountPatch: beforeClick.accountPatch,
2274
+ sessionId: effectiveSessionId
2275
+ });
2276
+ } catch (err) {
2277
+ onErrorChange?.(err instanceof Error ? err.message : "Payment failed. Please try again.");
2278
+ } finally {
2279
+ submittingRef.current = false;
2280
+ setSubmittingMethod(null);
2281
+ }
2282
+ }, [
2283
+ stripeInstance,
2284
+ isProcessing,
2285
+ runBeforeButtonClick,
2286
+ onButtonClick,
2287
+ sessionId,
2288
+ email,
2289
+ baseUrl,
2290
+ billingName,
2291
+ onTokenizedBody,
2292
+ onErrorChange,
2293
+ onDecline
2294
+ ]);
2295
+ const [localExpandedMethod, setLocalExpandedMethod] = (0, import_react8.useState)(null);
2296
+ const activeExpandedMethod = onExpandApm ? expandedApmMethod ?? null : localExpandedMethod;
2297
+ const handleMethodClick = (0, import_react8.useCallback)((method) => {
2298
+ if (submittingRef.current || isProcessing) return;
2299
+ if (activeExpandedMethod && activeExpandedMethod !== method) return;
2300
+ if ((0, import_shared5.needsStripeMethodExplicitConfirm)(method)) {
2301
+ if (onExpandApm) {
2302
+ onExpandApm(method);
2303
+ } else {
2304
+ setLocalExpandedMethod(method);
2305
+ }
2306
+ } else {
2307
+ void handleAutoConfirm(method);
2308
+ }
2309
+ }, [activeExpandedMethod, isProcessing, handleAutoConfirm, onExpandApm]);
2310
+ const localInlineElementsOptions = (0, import_react8.useMemo)(() => {
2311
+ if (!localExpandedMethod) return null;
2312
+ return {
2313
+ ...paymentElementBaseOptions,
2314
+ paymentMethodTypes: [localExpandedMethod]
2315
+ };
2316
+ }, [localExpandedMethod, paymentElementBaseOptions]);
2317
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2318
+ "div",
2319
+ {
2320
+ "data-testid": "flopay-stripe-payment-element-region",
2321
+ style: { display: "flex", flexDirection: "column", gap: "0.5rem" },
2322
+ children: paymentElementMethods.map((method) => {
2323
+ const isExpanded = expandedApmMethod === method;
2324
+ const isSubmittingThis = submittingMethod === method;
2325
+ const otherInProgress = submittingMethod !== null && submittingMethod !== method || expandedApmMethod !== null && expandedApmMethod !== void 0 && expandedApmMethod !== method;
2326
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react8.default.Fragment, { children: [
2327
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2328
+ StripeMethodButton,
2329
+ {
2330
+ method,
2331
+ themeId,
2332
+ submitting: isSubmittingThis,
2333
+ disabled: otherInProgress,
2334
+ highlighted: isExpanded,
2335
+ hasNextStep: (0, import_shared5.needsStripeMethodExplicitConfirm)(method),
2336
+ onClick: () => handleMethodClick(method),
2337
+ backgroundColor: buttonAppearance?.backgroundColor,
2338
+ borderColor: buttonAppearance?.borderColor,
2339
+ textColor: buttonAppearance?.textColor,
2340
+ borderRadius: buttonAppearance?.borderRadius,
2341
+ fontFamily: buttonAppearance?.fontFamily
2342
+ }
2343
+ ),
2344
+ !onExpandApm && localExpandedMethod === method && localInlineElementsOptions && stripeInstance && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2345
+ import_react_stripe_js.Elements,
2346
+ {
2347
+ stripe: stripeInstance,
2348
+ options: localInlineElementsOptions,
2349
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2350
+ StripeMethodInlineForm,
2351
+ {
2352
+ method,
2353
+ sessionId,
2354
+ email,
2355
+ billingName,
2356
+ billingApiUrl,
2357
+ onTokenizedBody,
2358
+ onErrorChange,
2359
+ onDecline,
2360
+ onCancel: () => setLocalExpandedMethod(null),
2361
+ runBeforeButtonClick,
2362
+ onButtonClick,
2363
+ isProcessing,
2364
+ submitButtonColor,
2365
+ submitButtonBorderRadius,
2366
+ submitButtonStyle
2367
+ }
2368
+ )
2369
+ },
2370
+ method
2371
+ )
2372
+ ] }, method);
2373
+ })
2374
+ }
2375
+ );
2376
+ }
1745
2377
  function SplitCardFormInner({
1746
2378
  sessionId,
1747
2379
  billingApiUrl,
@@ -1764,6 +2396,8 @@ function SplitCardFormInner({
1764
2396
  onFirstNameChange,
1765
2397
  onLastNameChange,
1766
2398
  showPayPal = true,
2399
+ showStripe = true,
2400
+ enabledPaymentMethods,
1767
2401
  showApplePay = true,
1768
2402
  showGooglePay = true,
1769
2403
  layout = "default",
@@ -1823,6 +2457,7 @@ function SplitCardFormInner({
1823
2457
  const avsConfig = (0, import_react8.useMemo)(() => (0, import_shared5.resolveAVSConfig)(enableAVSProp), [enableAVSProp]);
1824
2458
  const enableAVS = avsConfig !== null;
1825
2459
  const [viewState, setViewState] = (0, import_react8.useState)(initialCardOpen ? "card" : "buttons");
2460
+ const [expandedApmMethod, setExpandedApmMethod] = (0, import_react8.useState)(null);
1826
2461
  const showCardForm = viewState === "expanding" || viewState === "card";
1827
2462
  const TRANSITION_MS = 280;
1828
2463
  const expandToCard = (0, import_react8.useCallback)(() => {
@@ -1833,6 +2468,18 @@ function SplitCardFormInner({
1833
2468
  setViewState("collapsing");
1834
2469
  setTimeout(() => setViewState("buttons"), TRANSITION_MS);
1835
2470
  }, []);
2471
+ const expandToApm = (0, import_react8.useCallback)((method) => {
2472
+ setExpandedApmMethod(method);
2473
+ setViewState("apm-expanding");
2474
+ setTimeout(() => setViewState("apm-form"), TRANSITION_MS);
2475
+ }, []);
2476
+ const collapseFromApm = (0, import_react8.useCallback)(() => {
2477
+ setViewState("apm-collapsing");
2478
+ setTimeout(() => {
2479
+ setViewState("buttons");
2480
+ setExpandedApmMethod(null);
2481
+ }, TRANSITION_MS);
2482
+ }, []);
1836
2483
  (0, import_react8.useEffect)(() => {
1837
2484
  if (layout === "buttons" && initialCardOpen) {
1838
2485
  setViewState("card");
@@ -1886,20 +2533,43 @@ function SplitCardFormInner({
1886
2533
  return paypalFlopay.getRawProvider();
1887
2534
  }, [paypalFlopay]);
1888
2535
  const amountInCents = totalAmount || 100;
2536
+ const stripeAppearanceProp = appearance ? { appearance } : null;
2537
+ const paymentElementAppearance = (0, import_react8.useMemo)(() => {
2538
+ const base = appearance ?? {};
2539
+ const baseRules = base.rules ?? {};
2540
+ return {
2541
+ ...base,
2542
+ rules: {
2543
+ ...baseRules,
2544
+ ".AccordionItem": {
2545
+ padding: "10px 14px",
2546
+ boxShadow: "none",
2547
+ ...baseRules[".AccordionItem"] ?? {}
2548
+ },
2549
+ ".AccordionItemContents": {
2550
+ padding: "6px 14px 12px",
2551
+ boxShadow: "none",
2552
+ ...baseRules[".AccordionItemContents"] ?? {}
2553
+ }
2554
+ }
2555
+ };
2556
+ }, [appearance]);
1889
2557
  const walletOptions = (0, import_react8.useMemo)(() => ({
1890
2558
  mode: "payment",
1891
2559
  amount: amountInCents,
1892
2560
  currency: currency.toLowerCase(),
1893
2561
  paymentMethodCreation: "manual",
1894
- captureMethod: "manual"
1895
- }), [amountInCents, currency]);
2562
+ captureMethod: "manual",
2563
+ ...stripeAppearanceProp
2564
+ }), [amountInCents, currency, stripeAppearanceProp]);
1896
2565
  const paypalOptions = (0, import_react8.useMemo)(() => ({
1897
2566
  mode: "payment",
1898
2567
  amount: amountInCents,
1899
2568
  currency: currency.toLowerCase(),
1900
2569
  captureMethod: "manual",
1901
- setupFutureUsage: "off_session"
1902
- }), [amountInCents, currency]);
2570
+ setupFutureUsage: "off_session",
2571
+ ...stripeAppearanceProp
2572
+ }), [amountInCents, currency, stripeAppearanceProp]);
1903
2573
  const updateError = (0, import_react8.useCallback)(
1904
2574
  (err) => {
1905
2575
  setError(err);
@@ -1913,22 +2583,98 @@ function SplitCardFormInner({
1913
2583
  },
1914
2584
  [onDecline]
1915
2585
  );
1916
- const showWallets = showApplePay || showGooglePay;
2586
+ const directPaypalConfigured = !!directPaypal?.clientId;
2587
+ const hasEnabledMethodsPayload = Array.isArray(enabledPaymentMethods);
2588
+ const hasEnabledMethods = hasEnabledMethodsPayload && enabledPaymentMethods.length > 0;
2589
+ const paypalEnabled = hasEnabledMethodsPayload && enabledPaymentMethods.includes("paypal");
2590
+ const { expressMethods, paymentElementMethods } = (0, import_react8.useMemo)(
2591
+ () => (0, import_shared5.partitionStripeMethods)(enabledPaymentMethods, {
2592
+ excludePaypal: directPaypalConfigured
2593
+ }),
2594
+ [enabledPaymentMethods, directPaypalConfigured]
2595
+ );
2596
+ const paypalRenderedSeparately = directPaypalConfigured || !!paypalFlopay;
2597
+ const expressMethodsForWalletRow = (0, import_react8.useMemo)(
2598
+ () => paypalRenderedSeparately ? expressMethods.filter((m) => m !== "paypal") : expressMethods,
2599
+ [expressMethods, paypalRenderedSeparately]
2600
+ );
2601
+ const legacyExpressMethods = (0, import_react8.useMemo)(() => {
2602
+ const out = [];
2603
+ if (showApplePay) out.push("apple_pay");
2604
+ if (showGooglePay) out.push("google_pay");
2605
+ return out;
2606
+ }, [showApplePay, showGooglePay]);
2607
+ const walletExpressMethods = hasEnabledMethodsPayload ? expressMethodsForWalletRow : legacyExpressMethods;
2608
+ const showWallets = showStripe && walletExpressMethods.length > 0;
2609
+ const paymentElementMethodsForCurrency = (0, import_react8.useMemo)(
2610
+ () => (0, import_shared5.filterStripeMethodsByAmount)(
2611
+ (0, import_shared5.filterStripeMethodsByCountry)(
2612
+ (0, import_shared5.filterStripeMethodsByCurrency)(paymentElementMethods, currency),
2613
+ countryProp
2614
+ ),
2615
+ currency,
2616
+ amountInCents
2617
+ ),
2618
+ [paymentElementMethods, currency, countryProp, amountInCents]
2619
+ );
2620
+ const paymentElementBaseOptions = (0, import_react8.useMemo)(() => ({
2621
+ mode: "payment",
2622
+ amount: amountInCents,
2623
+ currency: currency.toLowerCase(),
2624
+ paymentMethodCreation: "manual",
2625
+ appearance: paymentElementAppearance
2626
+ }), [amountInCents, currency, paymentElementAppearance]);
1917
2627
  const [paypalLoadState, setPaypalLoadState] = (0, import_react8.useState)("loading");
1918
2628
  const [walletLoadState, setWalletLoadState] = (0, import_react8.useState)("loading");
2629
+ const [paymentElementLoadState, setPaymentElementLoadState] = (0, import_react8.useState)("loading");
1919
2630
  const [directPaypalReady, setDirectPaypalReady] = (0, import_react8.useState)(false);
1920
2631
  const [inAppBrowserDetected, setInAppBrowserDetected] = (0, import_react8.useState)();
1921
2632
  (0, import_react8.useEffect)(() => {
1922
2633
  setInAppBrowserDetected(isInAppBrowser());
1923
2634
  }, []);
1924
- const directPaypalConfigured = !!directPaypal?.clientId;
1925
- const shouldShowPayPal = showPayPal && (directPaypalConfigured || inAppBrowserDetected === false);
1926
- const shouldShowWallets = showWallets;
2635
+ const shouldShowPayPal = showPayPal && (directPaypalConfigured || (hasEnabledMethodsPayload ? paypalEnabled : inAppBrowserDetected === false));
2636
+ const shouldShowWallets = showStripe && showWallets;
1927
2637
  const shouldRenderDirectPayPal = shouldShowPayPal && directPaypalConfigured;
1928
2638
  const shouldRenderStripePayPal = shouldShowPayPal && !directPaypalConfigured && !!paypalStripeInstance;
1929
2639
  const shouldRenderWallets = shouldShowWallets && !!stripeInstance;
2640
+ const shouldRenderPaymentElement = showStripe && hasEnabledMethods && paymentElementMethodsForCurrency.length > 0 && !!stripeInstance;
1930
2641
  const shouldDisplayPayPalRow = shouldRenderDirectPayPal ? directPaypalReady : shouldRenderStripePayPal && isExpressCheckoutRowVisible(paypalLoadState);
1931
2642
  const shouldDisplayWalletRow = shouldRenderWallets && isExpressCheckoutRowVisible(walletLoadState);
2643
+ const shouldDisplayPaymentElementRow = shouldRenderPaymentElement && paymentElementLoadState !== "load_error";
2644
+ const validationFiredRef = (0, import_react8.useRef)(false);
2645
+ (0, import_react8.useEffect)(() => {
2646
+ if (validationFiredRef.current) return;
2647
+ if (!showStripe && !showPayPal) {
2648
+ validationFiredRef.current = true;
2649
+ const err = new import_shared6.FloPayError(
2650
+ "FloPay: both `showStripe` and `showPayPal` are false \u2014 nothing to render.",
2651
+ "validation_error"
2652
+ );
2653
+ onError?.(err);
2654
+ updateError(err.message);
2655
+ } else if (!showStripe && showPayPal && !directPaypalConfigured && !paypalStripeInstance) {
2656
+ validationFiredRef.current = true;
2657
+ const err = new import_shared6.FloPayError(
2658
+ "FloPay: `showStripe` is false and no PayPal gateway is configured for this session \u2014 nothing to render.",
2659
+ "validation_error"
2660
+ );
2661
+ onError?.(err);
2662
+ updateError(err.message);
2663
+ }
2664
+ }, [showStripe, showPayPal, directPaypalConfigured, paypalStripeInstance, onError, updateError]);
2665
+ const deprecationLoggedRef = (0, import_react8.useRef)(false);
2666
+ (0, import_react8.useEffect)(() => {
2667
+ if (deprecationLoggedRef.current) return;
2668
+ if (!hasEnabledMethods) return;
2669
+ const stale = [];
2670
+ if (showApplePay !== true) stale.push("showApplePay");
2671
+ if (showGooglePay !== true) stale.push("showGooglePay");
2672
+ if (stale.length === 0) return;
2673
+ deprecationLoggedRef.current = true;
2674
+ console.warn(
2675
+ `[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.`
2676
+ );
2677
+ }, [hasEnabledMethods, showApplePay, showGooglePay]);
1932
2678
  const handleNameChange = (0, import_react8.useCallback)((value) => {
1933
2679
  setFullName(value);
1934
2680
  onFullNameChange?.(value);
@@ -2229,24 +2975,75 @@ function SplitCardFormInner({
2229
2975
  }
2230
2976
  }
2231
2977
  }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
2978
+ const stripeResumeAttemptedRef = (0, import_react8.useRef)(false);
2232
2979
  (0, import_react8.useEffect)(() => {
2233
- if (typeof window === "undefined") return;
2234
- const stored = localStorage.getItem(WALLET_RESUME_KEY);
2235
- if (!stored) return;
2236
- try {
2237
- const payload = JSON.parse(stored);
2238
- if (payload.sessionId === sessionId) {
2239
- localStorage.removeItem(WALLET_RESUME_KEY);
2240
- dispatchTokenizedBody({
2241
- id: payload.tokenId,
2242
- type: payload.tokenType,
2243
- threeDSecureActionResultTokenId: payload.paymentIntentId
2244
- });
2980
+ if (typeof window === "undefined" || stripeResumeAttemptedRef.current) return;
2981
+ const params = new URLSearchParams(window.location.search);
2982
+ const paymentIntentId = params.get("payment_intent");
2983
+ const clientSecret = params.get("payment_intent_client_secret");
2984
+ const redirectStatus = params.get("redirect_status");
2985
+ if (!paymentIntentId || !clientSecret) return;
2986
+ const readPayload = (key) => {
2987
+ const raw = localStorage.getItem(key);
2988
+ if (!raw) return null;
2989
+ try {
2990
+ return JSON.parse(raw);
2991
+ } catch {
2992
+ localStorage.removeItem(key);
2993
+ return null;
2245
2994
  }
2246
- } catch {
2247
- localStorage.removeItem(WALLET_RESUME_KEY);
2248
- }
2249
- }, [sessionId, dispatchTokenizedBody]);
2995
+ };
2996
+ const payload = readPayload(STRIPE_RESUME_KEY) ?? readPayload(LEGACY_WALLET_RESUME_KEY);
2997
+ if (payload?.sessionId && payload.sessionId !== sessionId) return;
2998
+ stripeResumeAttemptedRef.current = true;
2999
+ localStorage.removeItem(STRIPE_RESUME_KEY);
3000
+ localStorage.removeItem(LEGACY_WALLET_RESUME_KEY);
3001
+ const cleanedUrl = new URL(window.location.href);
3002
+ cleanedUrl.searchParams.delete("payment_intent");
3003
+ cleanedUrl.searchParams.delete("payment_intent_client_secret");
3004
+ cleanedUrl.searchParams.delete("redirect_status");
3005
+ window.history.replaceState({}, "", cleanedUrl.toString());
3006
+ const paymentMethodType = payload?.paymentMethodType ?? payload?.tokenType ?? "card";
3007
+ const declineMethod = "card";
3008
+ (async () => {
3009
+ const rawProvider = flopay?.getRawProvider();
3010
+ if (!rawProvider) {
3011
+ const message = "Payment is not available \u2014 please refresh and try again.";
3012
+ updateError(message);
3013
+ emitDecline(declineMethod, message);
3014
+ return;
3015
+ }
3016
+ if (redirectStatus === "failed") {
3017
+ const message = "Payment was declined. Please try again.";
3018
+ updateError(message);
3019
+ emitDecline(declineMethod, message);
3020
+ return;
3021
+ }
3022
+ const { paymentIntent, error: error2 } = await rawProvider.retrievePaymentIntent(clientSecret);
3023
+ if (error2) {
3024
+ const message = error2.message ?? "Failed to retrieve payment status.";
3025
+ updateError(message);
3026
+ emitDecline(declineMethod, message, { code: error2.code });
3027
+ return;
3028
+ }
3029
+ const piStatus = paymentIntent?.status;
3030
+ const successful = piStatus === "succeeded" || piStatus === "requires_capture" || piStatus === "processing";
3031
+ if (!paymentIntent || !successful) {
3032
+ const message = piStatus === "canceled" ? "Payment was canceled." : "Payment was not completed. Please try again.";
3033
+ updateError(message);
3034
+ emitDecline(declineMethod, message, { code: piStatus ?? "missing_payment_intent" });
3035
+ return;
3036
+ }
3037
+ const resolvedPmId = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
3038
+ dispatchTokenizedBody({
3039
+ id: resolvedPmId ?? payload?.paymentMethodId ?? payload?.tokenId ?? paymentIntent.id,
3040
+ type: "card",
3041
+ threeDSecureActionResultTokenId: paymentIntent.id,
3042
+ gateway: payload?.gateway ?? "stripe",
3043
+ paymentMethodType
3044
+ });
3045
+ })();
3046
+ }, [sessionId, dispatchTokenizedBody, flopay, updateError, emitDecline]);
2250
3047
  const handleSubmit = (0, import_react8.useCallback)(
2251
3048
  async (e) => {
2252
3049
  e.preventDefault();
@@ -2529,17 +3326,23 @@ function SplitCardFormInner({
2529
3326
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2530
3327
  flex: 1,
2531
3328
  backgroundColor: cardInputBg,
2532
- border: `1px solid ${resolvedBorder}`,
3329
+ // Longhand only — mixing `border` shorthand with per-side
3330
+ // overrides triggers React's "shorthand vs longhand" warning
3331
+ // because render order isn't deterministic.
2533
3332
  borderTop: "none",
2534
3333
  borderRight: "none",
3334
+ borderBottom: `1px solid ${resolvedBorder}`,
3335
+ borderLeft: `1px solid ${resolvedBorder}`,
2535
3336
  borderBottomLeftRadius: "8px",
2536
3337
  padding: "10px"
2537
3338
  }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardExpiryElement, { options: stripeElementStyle }) }),
2538
3339
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2539
3340
  flex: 1,
2540
3341
  backgroundColor: cardInputBg,
2541
- border: `1px solid ${resolvedBorder}`,
2542
3342
  borderTop: "none",
3343
+ borderRight: `1px solid ${resolvedBorder}`,
3344
+ borderBottom: `1px solid ${resolvedBorder}`,
3345
+ borderLeft: `1px solid ${resolvedBorder}`,
2543
3346
  borderBottomRightRadius: "8px",
2544
3347
  padding: "10px"
2545
3348
  }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardCvcElement, { options: stripeElementStyle }) })
@@ -2631,7 +3434,10 @@ function SplitCardFormInner({
2631
3434
  (0, import_shared5.isAVSFieldVisible)(avsConfig.city, cc) && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2632
3435
  flex: 1,
2633
3436
  backgroundColor: cardInputBg,
2634
- border: `1px solid ${resolvedBorder}`,
3437
+ borderTop: `1px solid ${resolvedBorder}`,
3438
+ borderRight: `1px solid ${resolvedBorder}`,
3439
+ borderBottom: `1px solid ${resolvedBorder}`,
3440
+ borderLeft: `1px solid ${resolvedBorder}`,
2635
3441
  padding: "10px",
2636
3442
  borderTopLeftRadius: "8px",
2637
3443
  borderBottomLeftRadius: "8px",
@@ -2708,7 +3514,10 @@ function SplitCardFormInner({
2708
3514
  (0, import_shared5.isAVSFieldVisible)(avsConfig.country, cc) && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2709
3515
  flex: avsLayoutProp === "row" ? 1 : void 0,
2710
3516
  backgroundColor: cardInputBg,
2711
- border: `1px solid ${resolvedBorder}`,
3517
+ borderTop: `1px solid ${resolvedBorder}`,
3518
+ borderRight: `1px solid ${resolvedBorder}`,
3519
+ borderBottom: `1px solid ${resolvedBorder}`,
3520
+ borderLeft: `1px solid ${resolvedBorder}`,
2712
3521
  padding: "10px",
2713
3522
  ...avsLayoutProp === "row" && (0, import_shared5.isAVSFieldVisible)(avsConfig.postal_code, cc) ? { borderRadius: "0", borderTopLeftRadius: "8px", borderBottomLeftRadius: "8px", borderRight: "none" } : { borderRadius: "8px" },
2714
3523
  ...isButtons && bStyles.countrySelect ? bStyles.countrySelect : {}
@@ -2803,11 +3612,65 @@ function SplitCardFormInner({
2803
3612
  }
2804
3613
  )
2805
3614
  ] });
3615
+ const gateDebugStyle = {
3616
+ margin: 0,
3617
+ padding: "6px 8px",
3618
+ background: "#eef2ff",
3619
+ border: "1px solid #c7d2fe",
3620
+ borderRadius: 6,
3621
+ color: "#111827",
3622
+ font: "11px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace",
3623
+ whiteSpace: "pre-wrap",
3624
+ wordBreak: "break-word"
3625
+ };
3626
+ const renderDirectPaypalGateDebug = (testId) => {
3627
+ if (!debug) return null;
3628
+ if (!showPayPal || !directPaypalConfigured) {
3629
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { "data-testid": testId, style: gateDebugStyle, children: "FloPay/DirectPayPal-debug - not enabled" });
3630
+ }
3631
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { "data-testid": testId, style: gateDebugStyle, children: [
3632
+ "FloPay/DirectPayPal-debug (parent gate)",
3633
+ ` showPayPal=${showPayPal}`,
3634
+ ` directPaypalConfigured=${directPaypalConfigured}`,
3635
+ ` inAppBrowserDetected=${String(inAppBrowserDetected)}`,
3636
+ ` shouldRenderDirectPayPal=${shouldRenderDirectPayPal}`,
3637
+ ` shouldRenderStripePayPal=${shouldRenderStripePayPal}`,
3638
+ ` hasPaypalStripeInstance=${!!paypalStripeInstance}`
3639
+ ].join("\n") });
3640
+ };
3641
+ const renderStripeGateDebug = (testId) => {
3642
+ if (!debug) return null;
3643
+ if (!showStripe || !stripeInstance) {
3644
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { "data-testid": testId, style: gateDebugStyle, children: "FloPay/Stripe-debug - not enabled" });
3645
+ }
3646
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("pre", { "data-testid": testId, style: gateDebugStyle, children: [
3647
+ "FloPay/Stripe-debug (parent gate)",
3648
+ ` showStripe=${showStripe}`,
3649
+ ` currency=${currency}`,
3650
+ ` amountInCents=${amountInCents}`,
3651
+ ` enabledPaymentMethods: ${JSON.stringify(enabledPaymentMethods ?? [])}`,
3652
+ ` enabledPaymentMethodsProvided=${hasEnabledMethodsPayload}`,
3653
+ ` hasEnabledMethods=${hasEnabledMethods}`,
3654
+ ` expressMethods: ${JSON.stringify(expressMethods)}`,
3655
+ ` walletExpressMethods: ${JSON.stringify(walletExpressMethods)}`,
3656
+ ` paymentElementMethods: ${JSON.stringify(paymentElementMethods)}`,
3657
+ ` paymentElementMethodsForCurrency: ${JSON.stringify(paymentElementMethodsForCurrency)}`,
3658
+ ` hasStripeInstance=${!!stripeInstance}`,
3659
+ ` shouldRenderWallets=${shouldRenderWallets}`,
3660
+ ` shouldRenderPaymentElement=${shouldRenderPaymentElement}`
3661
+ ].join("\n") });
3662
+ };
3663
+ const isApmView = viewState === "apm-expanding" || viewState === "apm-form" || viewState === "apm-collapsing";
3664
+ 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;
3665
+ const apmInlineOptions = expandedApmMethod ? {
3666
+ ...paymentElementBaseOptions,
3667
+ paymentMethodTypes: [expandedApmMethod]
3668
+ } : null;
2806
3669
  if (layout === "buttons") {
2807
- const isButtonsView = viewState === "buttons" || viewState === "expanding";
3670
+ const isButtonsView = viewState === "buttons" || viewState === "expanding" || viewState === "apm-expanding";
2808
3671
  const isCardView = viewState === "expanding" || viewState === "card" || viewState === "collapsing";
2809
3672
  const cardButtonSizing = cardButtonContent === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
2810
- 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;
3673
+ 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;
2811
3674
  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;
2812
3675
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
2813
3676
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FloPayKeyframes, {}),
@@ -2828,32 +3691,8 @@ function SplitCardFormInner({
2828
3691
  ...buttonsAnim ? { animation: buttonsAnim, pointerEvents: "none" } : {}
2829
3692
  },
2830
3693
  children: [
2831
- debug && showPayPal && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2832
- "pre",
2833
- {
2834
- "data-testid": "flopay-direct-paypal-gate-debug",
2835
- style: {
2836
- margin: 0,
2837
- padding: "6px 8px",
2838
- background: "#eef2ff",
2839
- border: "1px solid #c7d2fe",
2840
- borderRadius: 6,
2841
- color: "#111827",
2842
- font: "11px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace",
2843
- whiteSpace: "pre-wrap",
2844
- wordBreak: "break-word"
2845
- },
2846
- children: [
2847
- "FloPay/DirectPayPal-debug (parent gate)",
2848
- ` showPayPal=${showPayPal}`,
2849
- ` directPaypalConfigured=${directPaypalConfigured}`,
2850
- ` inAppBrowserDetected=${String(inAppBrowserDetected)}`,
2851
- ` shouldRenderDirectPayPal=${shouldRenderDirectPayPal}`,
2852
- ` shouldRenderStripePayPal=${shouldRenderStripePayPal}`,
2853
- ` hasPaypalStripeInstance=${!!paypalStripeInstance}`
2854
- ].join("\n")
2855
- }
2856
- ),
3694
+ renderDirectPaypalGateDebug("flopay-direct-paypal-gate-debug"),
3695
+ renderStripeGateDebug("flopay-stripe-gate-debug"),
2857
3696
  shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2858
3697
  paypalDirectRetry && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2859
3698
  "div",
@@ -2908,7 +3747,8 @@ function SplitCardFormInner({
2908
3747
  onButtonClick,
2909
3748
  onDecline,
2910
3749
  runBeforeButtonClick,
2911
- onLoadStateChange: setPaypalLoadState
3750
+ onLoadStateChange: setPaypalLoadState,
3751
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
2912
3752
  }
2913
3753
  ) }),
2914
3754
  shouldRenderWallets ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -2917,17 +3757,48 @@ function SplitCardFormInner({
2917
3757
  sessionId,
2918
3758
  email: resolvedAccount.email,
2919
3759
  billingApiUrl: resolvedBillingApiUrl,
2920
- showApplePay,
2921
- showGooglePay,
3760
+ expressMethods: walletExpressMethods,
3761
+ onTokenizedBody: dispatchTokenizedBody,
3762
+ onErrorChange: updateError,
3763
+ onButtonClick,
3764
+ onDecline,
3765
+ runBeforeButtonClick,
3766
+ onLoadStateChange: setWalletLoadState,
3767
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
3768
+ }
3769
+ ) }) : shouldShowWallets ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("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,
3770
+ shouldRenderPaymentElement && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3771
+ StripePaymentElementInner,
3772
+ {
3773
+ sessionId,
3774
+ email: resolvedAccount.email,
3775
+ billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
3776
+ billingApiUrl: resolvedBillingApiUrl,
3777
+ paymentElementMethods: paymentElementMethodsForCurrency,
3778
+ stripeInstance,
3779
+ paymentElementBaseOptions,
2922
3780
  onTokenizedBody: dispatchTokenizedBody,
2923
3781
  onErrorChange: updateError,
2924
3782
  onButtonClick,
2925
3783
  onDecline,
2926
3784
  runBeforeButtonClick,
2927
- onLoadStateChange: setWalletLoadState
3785
+ onLoadStateChange: setPaymentElementLoadState,
3786
+ isProcessing: isSubmitting,
3787
+ submitButtonColor: resolvedPrimaryColor,
3788
+ submitButtonBorderRadius: resolvedBorderRadius,
3789
+ submitButtonStyle: bStyles.submitButton,
3790
+ onExpandApm: expandToApm,
3791
+ expandedApmMethod,
3792
+ themeId: theme,
3793
+ buttonAppearance: {
3794
+ backgroundColor: bStyles.cardButton?.backgroundColor,
3795
+ borderColor: bStyles.cardInputBorder,
3796
+ textColor: bStyles.cardButton?.color,
3797
+ borderRadius: bStyles.cardButton?.borderRadius
3798
+ }
2928
3799
  }
2929
- ) }) : shouldShowWallets ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
2930
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3800
+ ),
3801
+ showStripe && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2931
3802
  "button",
2932
3803
  {
2933
3804
  type: "button",
@@ -2942,10 +3813,21 @@ function SplitCardFormInner({
2942
3813
  style: {
2943
3814
  width: "100%",
2944
3815
  ...cardButtonSizing,
2945
- backgroundColor: "white",
2946
- color: "#262833",
2947
- border: "1px solid #d1d5db",
2948
- borderRadius: "8px",
3816
+ // `bStyles.cardButton` first — supplies the bundle's
3817
+ // padding / typography / border-radius — then the
3818
+ // primary surface overrides on top, so the card button
3819
+ // ends up wearing the *submit* button's colours rather
3820
+ // than the bundle's neutral white-tile `cardButton`
3821
+ // colours. Without this order flip, every non-classic
3822
+ // bundle's `cardButton.backgroundColor` was winning
3823
+ // over the primary fill we just derived above it.
3824
+ ...bStyles.cardButton,
3825
+ ...derivePrimaryTileStyle({
3826
+ themeBundle,
3827
+ resolvedPrimaryColor,
3828
+ resolvedBorderRadius,
3829
+ submitButtonStyle: bStyles.submitButton
3830
+ }),
2949
3831
  fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
2950
3832
  fontWeight: 600,
2951
3833
  cursor: isSubmitting ? "not-allowed" : "pointer",
@@ -2954,10 +3836,8 @@ function SplitCardFormInner({
2954
3836
  justifyContent: "center",
2955
3837
  gap: "0.625rem",
2956
3838
  transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
2957
- boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
2958
3839
  position: "relative",
2959
- opacity: isSubmitting ? 0.6 : 1,
2960
- ...bStyles.cardButton
3840
+ opacity: isSubmitting ? 0.6 : 1
2961
3841
  },
2962
3842
  onMouseDown: (e) => {
2963
3843
  e.currentTarget.style.transform = "scale(0.985)";
@@ -2988,44 +3868,209 @@ function SplitCardFormInner({
2988
3868
  ]
2989
3869
  }
2990
3870
  ),
2991
- isCardView && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
3871
+ showStripe && isCardView && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2992
3872
  gridArea: "1 / 1",
2993
3873
  ...cardAnim ? { animation: cardAnim } : {},
2994
3874
  ...viewState === "collapsing" ? { pointerEvents: "none" } : {}
2995
- }, children: cardFormBlock })
3875
+ }, children: cardFormBlock }),
3876
+ showStripe && isApmView && expandedApmMethod && apmInlineOptions && stripeInstance && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
3877
+ gridArea: "1 / 1",
3878
+ ...apmAnim ? { animation: apmAnim } : {},
3879
+ ...viewState === "apm-collapsing" ? { pointerEvents: "none" } : {}
3880
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
3881
+ backgroundColor: cardBg,
3882
+ borderRadius: containerRadius,
3883
+ ...containerOverrides,
3884
+ padding: containerPadding
3885
+ }, children: [
3886
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
3887
+ display: "flex",
3888
+ alignItems: "center",
3889
+ padding: "0.75rem 0 0.625rem"
3890
+ }, children: [
3891
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3892
+ "button",
3893
+ {
3894
+ type: "button",
3895
+ "data-testid": "flopay-apm-back-button",
3896
+ onClick: collapseFromApm,
3897
+ style: {
3898
+ display: "inline-flex",
3899
+ alignItems: "center",
3900
+ gap: hideBackButtonLabel ? 0 : "0.5rem",
3901
+ background: "none",
3902
+ border: "none",
3903
+ cursor: "pointer",
3904
+ color: "#4b5563",
3905
+ fontSize: bStyles.backButtonFontSize ?? "0.85rem",
3906
+ fontWeight: 500,
3907
+ padding: 0,
3908
+ transition: "color 0.15s",
3909
+ flexShrink: 0,
3910
+ ...bStyles.backButton
3911
+ },
3912
+ "aria-label": "Back to payment methods",
3913
+ children: [
3914
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: {
3915
+ display: "inline-flex",
3916
+ alignItems: "center",
3917
+ justifyContent: "center",
3918
+ width: 28,
3919
+ height: 28,
3920
+ borderRadius: "50%",
3921
+ backgroundColor: "#f3f4f6",
3922
+ transition: "background-color 0.15s",
3923
+ ...bStyles.backButtonIcon
3924
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M15 18l-6-6 6-6" }) }) }),
3925
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BackButtonContentSlot, { content: cardBackButtonContent })
3926
+ ]
3927
+ }
3928
+ ),
3929
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
3930
+ flex: 1,
3931
+ textAlign: "center",
3932
+ fontWeight: 600,
3933
+ fontSize: bStyles.titleFontSize ?? "1.05rem",
3934
+ color: resolvedTitleColor,
3935
+ paddingRight: 80,
3936
+ ...bStyles.title
3937
+ }, children: `Pay with ${(0, import_shared5.getStripeMethodDisplayName)(expandedApmMethod)}` })
3938
+ ] }),
3939
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3940
+ import_react_stripe_js.Elements,
3941
+ {
3942
+ stripe: stripeInstance,
3943
+ options: apmInlineOptions,
3944
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3945
+ StripeMethodInlineForm,
3946
+ {
3947
+ method: expandedApmMethod,
3948
+ sessionId,
3949
+ email: resolvedAccount.email,
3950
+ billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
3951
+ billingApiUrl: resolvedBillingApiUrl,
3952
+ onTokenizedBody: dispatchTokenizedBody,
3953
+ onErrorChange: updateError,
3954
+ onDecline,
3955
+ onCancel: collapseFromApm,
3956
+ runBeforeButtonClick,
3957
+ onButtonClick,
3958
+ isProcessing: isSubmitting,
3959
+ submitButtonColor: resolvedPrimaryColor,
3960
+ submitButtonBorderRadius: resolvedBorderRadius,
3961
+ submitButtonStyle: bStyles.submitButton
3962
+ }
3963
+ )
3964
+ },
3965
+ expandedApmMethod
3966
+ )
3967
+ ] }) })
2996
3968
  ] })
2997
3969
  ] });
2998
3970
  }
3971
+ if (isApmView && expandedApmMethod && apmInlineOptions && stripeInstance && showStripe) {
3972
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
3973
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FloPayKeyframes, {}),
3974
+ overlayStatus && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
3975
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
3976
+ ...apmAnim ? { animation: apmAnim } : {},
3977
+ ...viewState === "apm-collapsing" ? { pointerEvents: "none" } : {}
3978
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
3979
+ backgroundColor: cardBg,
3980
+ borderRadius: containerRadius,
3981
+ ...containerOverrides,
3982
+ padding: containerPadding
3983
+ }, children: [
3984
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
3985
+ display: "flex",
3986
+ alignItems: "center",
3987
+ padding: "0.75rem 0 0.625rem"
3988
+ }, children: [
3989
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3990
+ "button",
3991
+ {
3992
+ type: "button",
3993
+ "data-testid": "flopay-apm-back-button-default",
3994
+ onClick: collapseFromApm,
3995
+ style: {
3996
+ display: "inline-flex",
3997
+ alignItems: "center",
3998
+ gap: hideBackButtonLabel ? 0 : "0.5rem",
3999
+ background: "none",
4000
+ border: "none",
4001
+ cursor: "pointer",
4002
+ color: "#4b5563",
4003
+ fontSize: bStyles.backButtonFontSize ?? "0.85rem",
4004
+ fontWeight: 500,
4005
+ padding: 0,
4006
+ transition: "color 0.15s",
4007
+ flexShrink: 0,
4008
+ ...bStyles.backButton
4009
+ },
4010
+ "aria-label": "Back to payment methods",
4011
+ children: [
4012
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: {
4013
+ display: "inline-flex",
4014
+ alignItems: "center",
4015
+ justifyContent: "center",
4016
+ width: 28,
4017
+ height: 28,
4018
+ borderRadius: "50%",
4019
+ backgroundColor: "#f3f4f6",
4020
+ transition: "background-color 0.15s",
4021
+ ...bStyles.backButtonIcon
4022
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M15 18l-6-6 6-6" }) }) }),
4023
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(BackButtonContentSlot, { content: cardBackButtonContent })
4024
+ ]
4025
+ }
4026
+ ),
4027
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
4028
+ flex: 1,
4029
+ textAlign: "center",
4030
+ fontWeight: 600,
4031
+ fontSize: bStyles.titleFontSize ?? "1.05rem",
4032
+ color: resolvedTitleColor,
4033
+ paddingRight: 80,
4034
+ ...bStyles.title
4035
+ }, children: `Pay with ${(0, import_shared5.getStripeMethodDisplayName)(expandedApmMethod)}` })
4036
+ ] }),
4037
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4038
+ import_react_stripe_js.Elements,
4039
+ {
4040
+ stripe: stripeInstance,
4041
+ options: apmInlineOptions,
4042
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4043
+ StripeMethodInlineForm,
4044
+ {
4045
+ method: expandedApmMethod,
4046
+ sessionId,
4047
+ email: resolvedAccount.email,
4048
+ billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
4049
+ billingApiUrl: resolvedBillingApiUrl,
4050
+ onTokenizedBody: dispatchTokenizedBody,
4051
+ onErrorChange: updateError,
4052
+ onDecline,
4053
+ onCancel: collapseFromApm,
4054
+ runBeforeButtonClick,
4055
+ onButtonClick,
4056
+ isProcessing: isSubmitting,
4057
+ submitButtonColor: resolvedPrimaryColor,
4058
+ submitButtonBorderRadius: resolvedBorderRadius,
4059
+ submitButtonStyle: bStyles.submitButton
4060
+ }
4061
+ )
4062
+ },
4063
+ expandedApmMethod
4064
+ )
4065
+ ] }) })
4066
+ ] });
4067
+ }
2999
4068
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
3000
4069
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FloPayKeyframes, {}),
3001
4070
  overlayStatus && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
3002
4071
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
3003
- debug && showPayPal && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3004
- "pre",
3005
- {
3006
- "data-testid": "flopay-direct-paypal-gate-debug-default",
3007
- style: {
3008
- margin: 0,
3009
- padding: "6px 8px",
3010
- background: "#eef2ff",
3011
- border: "1px solid #c7d2fe",
3012
- borderRadius: 6,
3013
- color: "#111827",
3014
- font: "11px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace",
3015
- whiteSpace: "pre-wrap",
3016
- wordBreak: "break-word"
3017
- },
3018
- children: [
3019
- "FloPay/DirectPayPal-debug (parent gate)",
3020
- ` showPayPal=${showPayPal}`,
3021
- ` directPaypalConfigured=${directPaypalConfigured}`,
3022
- ` inAppBrowserDetected=${String(inAppBrowserDetected)}`,
3023
- ` shouldRenderDirectPayPal=${shouldRenderDirectPayPal}`,
3024
- ` shouldRenderStripePayPal=${shouldRenderStripePayPal}`,
3025
- ` hasPaypalStripeInstance=${!!paypalStripeInstance}`
3026
- ].join("\n")
3027
- }
3028
- ),
4072
+ renderDirectPaypalGateDebug("flopay-direct-paypal-gate-debug-default"),
4073
+ renderStripeGateDebug("flopay-stripe-gate-debug-default"),
3029
4074
  shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3030
4075
  paypalDirectRetry && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3031
4076
  "div",
@@ -3080,7 +4125,8 @@ function SplitCardFormInner({
3080
4125
  onButtonClick,
3081
4126
  onDecline,
3082
4127
  runBeforeButtonClick,
3083
- onLoadStateChange: setPaypalLoadState
4128
+ onLoadStateChange: setPaypalLoadState,
4129
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
3084
4130
  }
3085
4131
  ) }),
3086
4132
  shouldRenderWallets && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -3089,18 +4135,49 @@ function SplitCardFormInner({
3089
4135
  sessionId,
3090
4136
  email: resolvedAccount.email,
3091
4137
  billingApiUrl: resolvedBillingApiUrl,
3092
- showApplePay,
3093
- showGooglePay,
4138
+ expressMethods: walletExpressMethods,
4139
+ onTokenizedBody: dispatchTokenizedBody,
4140
+ onErrorChange: updateError,
4141
+ onButtonClick,
4142
+ onDecline,
4143
+ runBeforeButtonClick,
4144
+ onLoadStateChange: setWalletLoadState,
4145
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
4146
+ }
4147
+ ) }),
4148
+ shouldRenderPaymentElement && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4149
+ StripePaymentElementInner,
4150
+ {
4151
+ sessionId,
4152
+ email: resolvedAccount.email,
4153
+ billingName: [resolvedAccount.firstName, resolvedAccount.lastName].filter(Boolean).join(" ").trim() || void 0,
4154
+ billingApiUrl: resolvedBillingApiUrl,
4155
+ paymentElementMethods: paymentElementMethodsForCurrency,
4156
+ stripeInstance,
4157
+ paymentElementBaseOptions,
3094
4158
  onTokenizedBody: dispatchTokenizedBody,
3095
4159
  onErrorChange: updateError,
3096
4160
  onButtonClick,
3097
4161
  onDecline,
3098
4162
  runBeforeButtonClick,
3099
- onLoadStateChange: setWalletLoadState
4163
+ onLoadStateChange: setPaymentElementLoadState,
4164
+ isProcessing: isSubmitting,
4165
+ submitButtonColor: resolvedPrimaryColor,
4166
+ submitButtonBorderRadius: resolvedBorderRadius,
4167
+ submitButtonStyle: bStyles.submitButton,
4168
+ themeId: theme,
4169
+ onExpandApm: expandToApm,
4170
+ expandedApmMethod,
4171
+ buttonAppearance: {
4172
+ backgroundColor: bStyles.cardButton?.backgroundColor,
4173
+ borderColor: bStyles.cardInputBorder,
4174
+ textColor: bStyles.cardButton?.color,
4175
+ borderRadius: bStyles.cardButton?.borderRadius
4176
+ }
3100
4177
  }
3101
- ) })
4178
+ )
3102
4179
  ] }),
3103
- (shouldDisplayWalletRow || shouldDisplayPayPalRow) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
4180
+ showStripe && (shouldDisplayWalletRow || shouldDisplayPayPalRow || shouldDisplayPaymentElementRow) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
3104
4181
  display: "flex",
3105
4182
  alignItems: "center",
3106
4183
  gap: "0.75rem",
@@ -3112,7 +4189,7 @@ function SplitCardFormInner({
3112
4189
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "or pay with card" }),
3113
4190
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1, height: 1, backgroundColor: "#ddd" } })
3114
4191
  ] }),
3115
- cardFormBlock
4192
+ showStripe && cardFormBlock
3116
4193
  ] });
3117
4194
  }
3118
4195
 
@@ -3645,6 +4722,7 @@ function hasInlineSessionPatchData(patch) {
3645
4722
  }
3646
4723
  function FloPayCheckout({
3647
4724
  sessionId: sessionIdProp,
4725
+ nonce: nonceProp,
3648
4726
  createSession: createSessionParams,
3649
4727
  billingApiUrl,
3650
4728
  appearance: appearanceOverride,
@@ -3658,6 +4736,7 @@ function FloPayCheckout({
3658
4736
  onCountryChange,
3659
4737
  onZipChange,
3660
4738
  showPayPal = true,
4739
+ showStripe = true,
3661
4740
  showApplePay = true,
3662
4741
  showGooglePay = true,
3663
4742
  debug = false,
@@ -4004,6 +5083,14 @@ function FloPayCheckout({
4004
5083
  },
4005
5084
  successUrl: params?.successUrl,
4006
5085
  cancelUrl: params?.cancelUrl,
5086
+ // Unified products[] is the post-#760 wire shape. Must be included or
5087
+ // carts that only set `products` (no legacy items/subscriptions) all
5088
+ // hash to the same key — the bootstrap effect's `createSessionHash`
5089
+ // dep then never changes when products change, the session-id cache
5090
+ // hits the wrong session, and the backend's enabledPaymentMethods
5091
+ // result for the *previous* cart sticks until a fresh page-load with
5092
+ // the session cache cleared.
5093
+ p: params?.products?.map((x) => `${x.type ?? ""}:${x.code ?? x.providerItemId ?? x.providerPlanId ?? ""}:${x.totalAmount ?? ""}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
4007
5094
  i: params?.items?.map((x) => `${x.code ?? x.providerItemId ?? ""}:${x.totalAmount ?? ""}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
4008
5095
  s: params?.subscriptions?.map((x) => `${x.code ?? x.providerPlanId ?? ""}:${x.totalAmount ?? ""}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
4009
5096
  couponCodes: params?.couponCodes,
@@ -4230,7 +5317,7 @@ function FloPayCheckout({
4230
5317
  async function init() {
4231
5318
  try {
4232
5319
  const api = new import_js4.PaymentAPI(resolvedBillingUrl);
4233
- const result = await api.getUnifiedCheckoutSession(activeSessionId);
5320
+ const result = await api.getUnifiedCheckoutSession(activeSessionId, nonceProp);
4234
5321
  if (cancelled) return;
4235
5322
  setUnified(result);
4236
5323
  const sess = result.data.session ?? null;
@@ -4317,6 +5404,7 @@ function FloPayCheckout({
4317
5404
  createSessionHash,
4318
5405
  effectiveCreateSessionMode,
4319
5406
  initSessionDependency,
5407
+ nonceProp,
4320
5408
  runSavedPaymentFlow
4321
5409
  ]);
4322
5410
  const handleConfirmCheckout = (0, import_react9.useCallback)(async () => {
@@ -4390,17 +5478,18 @@ function FloPayCheckout({
4390
5478
  ] });
4391
5479
  }
4392
5480
  if (layout === "buttons") {
5481
+ const bundleRadius = themeBundle?.buttonsLayout?.cardButton?.borderRadius ?? themeBundle?.appearance.variables?.borderRadius ?? 8;
4393
5482
  const skeletonBar = (h) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
4394
5483
  height: h,
4395
- borderRadius: 8,
5484
+ borderRadius: bundleRadius,
4396
5485
  background: "#e5e7eb",
4397
5486
  animation: "flopay-loading-pulse 1.5s ease-in-out infinite"
4398
5487
  } });
4399
5488
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
4400
5489
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
4401
5490
  showPayPal && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4402
- (showApplePay || showGooglePay) && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4403
- skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
5491
+ showStripe && (showApplePay || showGooglePay) && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
5492
+ showStripe && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4404
5493
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `@keyframes flopay-loading-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
4405
5494
  ] }),
4406
5495
  modeOverlay
@@ -4445,6 +5534,7 @@ function FloPayCheckout({
4445
5534
  {
4446
5535
  onButtonClick,
4447
5536
  showPayPal,
5537
+ showStripe,
4448
5538
  showApplePay,
4449
5539
  showGooglePay,
4450
5540
  theme,
@@ -4589,6 +5679,8 @@ function FloPayCheckout({
4589
5679
  onErrorChange: setModeError,
4590
5680
  onFullNameChange,
4591
5681
  showPayPal,
5682
+ showStripe,
5683
+ enabledPaymentMethods: unified?.data.stripe?.enabledPaymentMethods,
4592
5684
  showApplePay,
4593
5685
  showGooglePay,
4594
5686
  layout,
@@ -4653,6 +5745,7 @@ function InterimButtonsView({
4653
5745
  cardOpen,
4654
5746
  errorMessage,
4655
5747
  showPayPal,
5748
+ showStripe = true,
4656
5749
  showApplePay,
4657
5750
  showGooglePay,
4658
5751
  theme,
@@ -4684,9 +5777,10 @@ function InterimButtonsView({
4684
5777
  title: { ...base.title, ...stylesOverride.title }
4685
5778
  };
4686
5779
  }, [themeBundle, buttonsTheme, stylesOverride]);
5780
+ const skeletonRadius = themeBundle?.buttonsLayout?.cardButton?.borderRadius ?? themeBundle?.appearance.variables?.borderRadius ?? 8;
4687
5781
  const skeleton = (h) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
4688
5782
  height: h,
4689
- borderRadius: 8,
5783
+ borderRadius: skeletonRadius,
4690
5784
  background: "#e5e7eb",
4691
5785
  animation: "flopay-interim-pulse 1.5s ease-in-out infinite"
4692
5786
  } });
@@ -4757,8 +5851,28 @@ function InterimButtonsView({
4757
5851
  ] }),
4758
5852
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTopLeftRadius: 8, borderTopRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { width: "60%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
4759
5853
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex" }, children: [
4760
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTop: "none", borderRight: "none", borderBottomLeftRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { width: "50%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
4761
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { flex: 1, backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTop: "none", borderBottomRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { width: "40%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) })
5854
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
5855
+ flex: 1,
5856
+ backgroundColor: inputBg,
5857
+ borderTop: "none",
5858
+ borderRight: "none",
5859
+ borderBottom: `1px solid ${inputBorder}`,
5860
+ borderLeft: `1px solid ${inputBorder}`,
5861
+ borderBottomLeftRadius: 8,
5862
+ padding: 12,
5863
+ height: 45
5864
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { width: "50%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
5865
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
5866
+ flex: 1,
5867
+ backgroundColor: inputBg,
5868
+ borderTop: "none",
5869
+ borderRight: `1px solid ${inputBorder}`,
5870
+ borderBottom: `1px solid ${inputBorder}`,
5871
+ borderLeft: `1px solid ${inputBorder}`,
5872
+ borderBottomRightRadius: 8,
5873
+ padding: 12,
5874
+ height: 45
5875
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { width: "40%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) })
4762
5876
  ] }),
4763
5877
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderRadius: 8, marginTop: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { width: "45%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
4764
5878
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
@@ -4782,8 +5896,8 @@ function InterimButtonsView({
4782
5896
  }
4783
5897
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
4784
5898
  showPayPal && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4785
- (showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4786
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5899
+ showStripe && (showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
5900
+ showStripe && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4787
5901
  "button",
4788
5902
  {
4789
5903
  type: "button",
@@ -4852,7 +5966,7 @@ var import_js5 = require("@flopay/js");
4852
5966
  var import_shared9 = require("@flopay/shared");
4853
5967
  var import_react10 = require("react");
4854
5968
  var import_jsx_runtime8 = require("react/jsx-runtime");
4855
- var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
5969
+ var WALLET_RESUME_KEY = "flopay_wallet_resume";
4856
5970
  var CheckoutForm = (0, import_react10.forwardRef)(
4857
5971
  function CheckoutForm2(props, ref) {
4858
5972
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckoutFormInner, { ...props, innerRef: ref });
@@ -4978,7 +6092,7 @@ function CheckoutFormInner({
4978
6092
  onDecline?.(buildDeclineEvent("paypal", message));
4979
6093
  return;
4980
6094
  }
4981
- localStorage.setItem(WALLET_RESUME_KEY2, JSON.stringify({
6095
+ localStorage.setItem(WALLET_RESUME_KEY, JSON.stringify({
4982
6096
  sessionId,
4983
6097
  paymentIntentId: "",
4984
6098
  tokenType: "card",
@@ -5054,12 +6168,12 @@ function CheckoutFormInner({
5054
6168
  }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
5055
6169
  (0, import_react10.useEffect)(() => {
5056
6170
  if (typeof window === "undefined") return;
5057
- const stored = localStorage.getItem(WALLET_RESUME_KEY2);
6171
+ const stored = localStorage.getItem(WALLET_RESUME_KEY);
5058
6172
  if (!stored) return;
5059
6173
  try {
5060
6174
  const payload = JSON.parse(stored);
5061
6175
  if (payload.sessionId === sessionId) {
5062
- localStorage.removeItem(WALLET_RESUME_KEY2);
6176
+ localStorage.removeItem(WALLET_RESUME_KEY);
5063
6177
  dispatchTokenizedBody({
5064
6178
  id: payload.tokenId,
5065
6179
  type: payload.tokenType,
@@ -5067,7 +6181,7 @@ function CheckoutFormInner({
5067
6181
  });
5068
6182
  }
5069
6183
  } catch {
5070
- localStorage.removeItem(WALLET_RESUME_KEY2);
6184
+ localStorage.removeItem(WALLET_RESUME_KEY);
5071
6185
  }
5072
6186
  }, [sessionId, dispatchTokenizedBody]);
5073
6187
  const handleSubmit = (0, import_react10.useCallback)(
@@ -5412,10 +6526,12 @@ function resolveCreateSessionDraft(props) {
5412
6526
  if (!props.clientId || !props.account || !props.successUrl || !props.cancelUrl) {
5413
6527
  return null;
5414
6528
  }
6529
+ const hasProducts = props.products != null;
5415
6530
  return {
5416
6531
  clientId: props.clientId,
5417
- items: props.items,
5418
- subscriptions: props.subscriptions,
6532
+ products: props.products,
6533
+ items: hasProducts ? void 0 : props.items,
6534
+ subscriptions: hasProducts ? void 0 : props.subscriptions,
5419
6535
  account: props.account,
5420
6536
  successUrl: props.successUrl,
5421
6537
  cancelUrl: props.cancelUrl,
@@ -5433,6 +6549,7 @@ function FloPayAutomaticPaymentButton({
5433
6549
  paymentMethodId: _deprecatedPaymentMethodId,
5434
6550
  checkoutMethod: _deprecatedCheckoutMethod,
5435
6551
  clientId,
6552
+ products,
5436
6553
  items,
5437
6554
  subscriptions,
5438
6555
  account,
@@ -5463,6 +6580,7 @@ function FloPayAutomaticPaymentButton({
5463
6580
  () => resolveCreateSessionDraft({
5464
6581
  createSession,
5465
6582
  clientId,
6583
+ products,
5466
6584
  items,
5467
6585
  subscriptions,
5468
6586
  account,
@@ -5478,6 +6596,7 @@ function FloPayAutomaticPaymentButton({
5478
6596
  clientId,
5479
6597
  couponCodes,
5480
6598
  createSession,
6599
+ products,
5481
6600
  items,
5482
6601
  subscriptions,
5483
6602
  successUrl,
@@ -5766,10 +6885,20 @@ function FloPayAutomaticPaymentButton({
5766
6885
  style: {
5767
6886
  width: "100%",
5768
6887
  ...cardButtonSizing,
5769
- backgroundColor: "white",
5770
- color: "#262833",
5771
- border: "1px solid #d1d5db",
5772
- borderRadius: "8px",
6888
+ // `bStyles.cardButton` first so the bundle's padding / typography
6889
+ // / radius / shadow come through, then `derivePrimaryTileStyle`
6890
+ // on top so the auto-pay button carries the *submit* button's
6891
+ // background and text colours — making it visually identical to
6892
+ // the "Confirm Payment" / "Pay with X" actions it stands in for.
6893
+ // Inline `style` consumer override stays at the end so explicit
6894
+ // per-button overrides still win.
6895
+ ...bStyles.cardButton,
6896
+ ...derivePrimaryTileStyle({
6897
+ themeBundle,
6898
+ resolvedPrimaryColor: themeBundle?.appearance.variables?.colorPrimary ?? "#4A49FF",
6899
+ resolvedBorderRadius: themeBundle?.appearance.variables?.borderRadius ?? "8px",
6900
+ submitButtonStyle: bStyles.submitButton
6901
+ }),
5773
6902
  fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
5774
6903
  fontWeight: 600,
5775
6904
  cursor: disabled || isProcessing ? "not-allowed" : "pointer",
@@ -5777,11 +6906,9 @@ function FloPayAutomaticPaymentButton({
5777
6906
  alignItems: "center",
5778
6907
  justifyContent: "center",
5779
6908
  gap: "0.625rem",
5780
- boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
5781
6909
  transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
5782
6910
  position: "relative",
5783
6911
  opacity: disabled || isProcessing ? 0.6 : 1,
5784
- ...bStyles.cardButton,
5785
6912
  ...style
5786
6913
  },
5787
6914
  onMouseDown: (e) => {