@flopay/react 1.1.5 → 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,33 @@ 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
+ }
1308
+ var BUTTONS_PANEL_HIDDEN_STYLE = {
1309
+ visibility: "hidden",
1310
+ position: "absolute",
1311
+ pointerEvents: "none",
1312
+ width: "100%",
1313
+ height: 0,
1314
+ overflow: "hidden"
1315
+ };
1287
1316
  function getButtonMethodLabel(method) {
1288
1317
  switch (method) {
1289
1318
  case "paypal":
@@ -1321,6 +1350,7 @@ function resolveExpressCheckoutLoadState(event, methods) {
1321
1350
  function ExpressCheckoutReadySwap({
1322
1351
  state,
1323
1352
  placeholderTestId,
1353
+ borderRadius = 8,
1324
1354
  children
1325
1355
  }) {
1326
1356
  if (state === "unavailable" || state === "load_error") return null;
@@ -1334,7 +1364,7 @@ function ExpressCheckoutReadySwap({
1334
1364
  position: "absolute",
1335
1365
  inset: 0,
1336
1366
  height: 44,
1337
- borderRadius: 8,
1367
+ borderRadius,
1338
1368
  background: "#e5e7eb",
1339
1369
  animation: state === "ready" ? void 0 : "flopay-pulse 1.5s ease-in-out infinite",
1340
1370
  opacity: state === "ready" ? 0 : 1,
@@ -1377,7 +1407,8 @@ function PayPalButtonInner({
1377
1407
  onButtonClick,
1378
1408
  onDecline,
1379
1409
  runBeforeButtonClick,
1380
- onLoadStateChange
1410
+ onLoadStateChange,
1411
+ placeholderBorderRadius
1381
1412
  }) {
1382
1413
  const stripe = (0, import_react_stripe_js.useStripe)();
1383
1414
  const elements = (0, import_react_stripe_js.useElements)();
@@ -1547,31 +1578,39 @@ function PayPalButtonInner({
1547
1578
  }
1548
1579
  }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]);
1549
1580
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1550
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-paypal-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1551
- import_react_stripe_js.ExpressCheckoutElement,
1581
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1582
+ ExpressCheckoutReadySwap,
1552
1583
  {
1553
- onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["paypal"])),
1554
- onLoadError: () => setLoadState("load_error"),
1555
- onClick: handlePayPalClick,
1556
- onConfirm: handlePayPalConfirm,
1557
- onCancel: () => {
1558
- beforeClickRef.current = null;
1559
- onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
1560
- },
1561
- options: {
1562
- buttonType: { paypal: "paypal" },
1563
- billingAddressRequired: false,
1564
- phoneNumberRequired: false,
1565
- shippingAddressRequired: false,
1566
- paymentMethods: {
1567
- applePay: "never",
1568
- googlePay: "never",
1569
- paypal: "auto",
1570
- 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
+ }
1571
1610
  }
1572
- }
1611
+ )
1573
1612
  }
1574
- ) }),
1613
+ ),
1575
1614
  submitting && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProcessingOverlay, { status: "processing" })
1576
1615
  ] });
1577
1616
  }
@@ -1579,14 +1618,14 @@ function WalletButtonInner({
1579
1618
  sessionId,
1580
1619
  email,
1581
1620
  billingApiUrl,
1582
- showApplePay = true,
1583
- showGooglePay = true,
1621
+ expressMethods,
1584
1622
  onTokenizedBody,
1585
1623
  onErrorChange,
1586
1624
  onButtonClick,
1587
1625
  onDecline,
1588
1626
  runBeforeButtonClick,
1589
- onLoadStateChange
1627
+ onLoadStateChange,
1628
+ placeholderBorderRadius
1590
1629
  }) {
1591
1630
  const stripe = (0, import_react_stripe_js.useStripe)();
1592
1631
  const elements = (0, import_react_stripe_js.useElements)();
@@ -1604,10 +1643,9 @@ function WalletButtonInner({
1604
1643
  const walletType = event.expressPaymentType;
1605
1644
  let prepared = beforeClickRef.current;
1606
1645
  beforeClickRef.current = null;
1646
+ const buttonMethod = walletType === "apple_pay" ? "apple_pay" : "google_pay";
1607
1647
  if (!prepared && runBeforeButtonClick) {
1608
- const beforeClick = await runBeforeButtonClick(
1609
- walletType === "apple_pay" ? "apple_pay" : "google_pay"
1610
- );
1648
+ const beforeClick = await runBeforeButtonClick(buttonMethod);
1611
1649
  if (!beforeClick.proceed) {
1612
1650
  event.paymentFailed({ reason: "fail", message: "Wallet checkout was cancelled." });
1613
1651
  return;
@@ -1617,7 +1655,7 @@ function WalletButtonInner({
1617
1655
  sessionId: beforeClick.sessionId
1618
1656
  };
1619
1657
  }
1620
- const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
1658
+ const method = buttonMethod;
1621
1659
  const effectiveSessionId = prepared?.sessionId ?? sessionId;
1622
1660
  const effectiveEmail = prepared?.accountPatch?.email ?? email;
1623
1661
  try {
@@ -1687,52 +1725,654 @@ function WalletButtonInner({
1687
1725
  },
1688
1726
  [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]
1689
1727
  );
1690
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1691
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-wallet-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1692
- import_react_stripe_js.ExpressCheckoutElement,
1693
- {
1694
- onReady: (event) => {
1695
- setLoadState(resolveExpressCheckoutLoadState(event, ["applePay", "googlePay"]));
1696
- },
1697
- onLoadError: (e) => {
1698
- setLoadState("load_error");
1699
- },
1700
- onClick: async (event) => {
1701
- lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
1702
- const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick(lastWalletMethodRef.current) : { proceed: true };
1703
- if (!beforeClick.proceed) {
1704
- beforeClickRef.current = null;
1705
- event.reject();
1706
- return;
1707
- }
1708
- beforeClickRef.current = {
1709
- accountPatch: beforeClick.accountPatch,
1710
- sessionId: beforeClick.sessionId
1711
- };
1712
- onButtonClick?.(lastWalletMethodRef.current);
1713
- event.resolve();
1714
- },
1715
- onConfirm: handleWalletConfirm,
1716
- onCancel: () => {
1717
- beforeClickRef.current = null;
1718
- onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
1719
- },
1720
- options: {
1721
- buttonType: { applePay: "plain", googlePay: "plain" },
1722
- paymentMethods: {
1723
- applePay: showApplePay ? "always" : "never",
1724
- googlePay: showGooglePay ? "always" : "never",
1725
- paypal: "never",
1726
- link: "never",
1727
- amazonPay: "never",
1728
- klarna: "never"
1729
- },
1730
- layout: { maxColumns: 1, overflow: "never" }
1731
- }
1732
- }
1733
- ) }),
1734
- submitting && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProcessingOverlay, { status: "processing" })
1735
- ] });
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
+ );
1746
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1747
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1748
+ ExpressCheckoutReadySwap,
1749
+ {
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
+ }
1787
+ }
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
2012
+ },
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);
2089
+ },
2090
+ options: {
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
+ }
2098
+ }
2099
+ }
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
+ )
2128
+ ] });
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
+ );
1736
2376
  }
1737
2377
  function SplitCardFormInner({
1738
2378
  sessionId,
@@ -1756,6 +2396,8 @@ function SplitCardFormInner({
1756
2396
  onFirstNameChange,
1757
2397
  onLastNameChange,
1758
2398
  showPayPal = true,
2399
+ showStripe = true,
2400
+ enabledPaymentMethods,
1759
2401
  showApplePay = true,
1760
2402
  showGooglePay = true,
1761
2403
  layout = "default",
@@ -1815,6 +2457,7 @@ function SplitCardFormInner({
1815
2457
  const avsConfig = (0, import_react8.useMemo)(() => (0, import_shared5.resolveAVSConfig)(enableAVSProp), [enableAVSProp]);
1816
2458
  const enableAVS = avsConfig !== null;
1817
2459
  const [viewState, setViewState] = (0, import_react8.useState)(initialCardOpen ? "card" : "buttons");
2460
+ const [expandedApmMethod, setExpandedApmMethod] = (0, import_react8.useState)(null);
1818
2461
  const showCardForm = viewState === "expanding" || viewState === "card";
1819
2462
  const TRANSITION_MS = 280;
1820
2463
  const expandToCard = (0, import_react8.useCallback)(() => {
@@ -1825,6 +2468,18 @@ function SplitCardFormInner({
1825
2468
  setViewState("collapsing");
1826
2469
  setTimeout(() => setViewState("buttons"), TRANSITION_MS);
1827
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
+ }, []);
1828
2483
  (0, import_react8.useEffect)(() => {
1829
2484
  if (layout === "buttons" && initialCardOpen) {
1830
2485
  setViewState("card");
@@ -1878,20 +2533,43 @@ function SplitCardFormInner({
1878
2533
  return paypalFlopay.getRawProvider();
1879
2534
  }, [paypalFlopay]);
1880
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]);
1881
2557
  const walletOptions = (0, import_react8.useMemo)(() => ({
1882
2558
  mode: "payment",
1883
2559
  amount: amountInCents,
1884
2560
  currency: currency.toLowerCase(),
1885
2561
  paymentMethodCreation: "manual",
1886
- captureMethod: "manual"
1887
- }), [amountInCents, currency]);
2562
+ captureMethod: "manual",
2563
+ ...stripeAppearanceProp
2564
+ }), [amountInCents, currency, stripeAppearanceProp]);
1888
2565
  const paypalOptions = (0, import_react8.useMemo)(() => ({
1889
2566
  mode: "payment",
1890
2567
  amount: amountInCents,
1891
2568
  currency: currency.toLowerCase(),
1892
2569
  captureMethod: "manual",
1893
- setupFutureUsage: "off_session"
1894
- }), [amountInCents, currency]);
2570
+ setupFutureUsage: "off_session",
2571
+ ...stripeAppearanceProp
2572
+ }), [amountInCents, currency, stripeAppearanceProp]);
1895
2573
  const updateError = (0, import_react8.useCallback)(
1896
2574
  (err) => {
1897
2575
  setError(err);
@@ -1905,22 +2583,98 @@ function SplitCardFormInner({
1905
2583
  },
1906
2584
  [onDecline]
1907
2585
  );
1908
- 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]);
1909
2627
  const [paypalLoadState, setPaypalLoadState] = (0, import_react8.useState)("loading");
1910
2628
  const [walletLoadState, setWalletLoadState] = (0, import_react8.useState)("loading");
2629
+ const [paymentElementLoadState, setPaymentElementLoadState] = (0, import_react8.useState)("loading");
1911
2630
  const [directPaypalReady, setDirectPaypalReady] = (0, import_react8.useState)(false);
1912
2631
  const [inAppBrowserDetected, setInAppBrowserDetected] = (0, import_react8.useState)();
1913
2632
  (0, import_react8.useEffect)(() => {
1914
2633
  setInAppBrowserDetected(isInAppBrowser());
1915
2634
  }, []);
1916
- const directPaypalConfigured = !!directPaypal?.clientId;
1917
- const shouldShowPayPal = showPayPal && (directPaypalConfigured || inAppBrowserDetected === false);
1918
- const shouldShowWallets = showWallets;
2635
+ const shouldShowPayPal = showPayPal && (directPaypalConfigured || (hasEnabledMethodsPayload ? paypalEnabled : inAppBrowserDetected === false));
2636
+ const shouldShowWallets = showStripe && showWallets;
1919
2637
  const shouldRenderDirectPayPal = shouldShowPayPal && directPaypalConfigured;
1920
2638
  const shouldRenderStripePayPal = shouldShowPayPal && !directPaypalConfigured && !!paypalStripeInstance;
1921
2639
  const shouldRenderWallets = shouldShowWallets && !!stripeInstance;
2640
+ const shouldRenderPaymentElement = showStripe && hasEnabledMethods && paymentElementMethodsForCurrency.length > 0 && !!stripeInstance;
1922
2641
  const shouldDisplayPayPalRow = shouldRenderDirectPayPal ? directPaypalReady : shouldRenderStripePayPal && isExpressCheckoutRowVisible(paypalLoadState);
1923
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]);
1924
2678
  const handleNameChange = (0, import_react8.useCallback)((value) => {
1925
2679
  setFullName(value);
1926
2680
  onFullNameChange?.(value);
@@ -2221,24 +2975,75 @@ function SplitCardFormInner({
2221
2975
  }
2222
2976
  }
2223
2977
  }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
2978
+ const stripeResumeAttemptedRef = (0, import_react8.useRef)(false);
2224
2979
  (0, import_react8.useEffect)(() => {
2225
- if (typeof window === "undefined") return;
2226
- const stored = localStorage.getItem(WALLET_RESUME_KEY);
2227
- if (!stored) return;
2228
- try {
2229
- const payload = JSON.parse(stored);
2230
- if (payload.sessionId === sessionId) {
2231
- localStorage.removeItem(WALLET_RESUME_KEY);
2232
- dispatchTokenizedBody({
2233
- id: payload.tokenId,
2234
- type: payload.tokenType,
2235
- threeDSecureActionResultTokenId: payload.paymentIntentId
2236
- });
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;
2237
2994
  }
2238
- } catch {
2239
- localStorage.removeItem(WALLET_RESUME_KEY);
2240
- }
2241
- }, [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]);
2242
3047
  const handleSubmit = (0, import_react8.useCallback)(
2243
3048
  async (e) => {
2244
3049
  e.preventDefault();
@@ -2521,17 +3326,23 @@ function SplitCardFormInner({
2521
3326
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2522
3327
  flex: 1,
2523
3328
  backgroundColor: cardInputBg,
2524
- 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.
2525
3332
  borderTop: "none",
2526
3333
  borderRight: "none",
3334
+ borderBottom: `1px solid ${resolvedBorder}`,
3335
+ borderLeft: `1px solid ${resolvedBorder}`,
2527
3336
  borderBottomLeftRadius: "8px",
2528
3337
  padding: "10px"
2529
3338
  }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardExpiryElement, { options: stripeElementStyle }) }),
2530
3339
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2531
3340
  flex: 1,
2532
3341
  backgroundColor: cardInputBg,
2533
- border: `1px solid ${resolvedBorder}`,
2534
3342
  borderTop: "none",
3343
+ borderRight: `1px solid ${resolvedBorder}`,
3344
+ borderBottom: `1px solid ${resolvedBorder}`,
3345
+ borderLeft: `1px solid ${resolvedBorder}`,
2535
3346
  borderBottomRightRadius: "8px",
2536
3347
  padding: "10px"
2537
3348
  }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardCvcElement, { options: stripeElementStyle }) })
@@ -2623,7 +3434,10 @@ function SplitCardFormInner({
2623
3434
  (0, import_shared5.isAVSFieldVisible)(avsConfig.city, cc) && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2624
3435
  flex: 1,
2625
3436
  backgroundColor: cardInputBg,
2626
- border: `1px solid ${resolvedBorder}`,
3437
+ borderTop: `1px solid ${resolvedBorder}`,
3438
+ borderRight: `1px solid ${resolvedBorder}`,
3439
+ borderBottom: `1px solid ${resolvedBorder}`,
3440
+ borderLeft: `1px solid ${resolvedBorder}`,
2627
3441
  padding: "10px",
2628
3442
  borderTopLeftRadius: "8px",
2629
3443
  borderBottomLeftRadius: "8px",
@@ -2700,7 +3514,10 @@ function SplitCardFormInner({
2700
3514
  (0, import_shared5.isAVSFieldVisible)(avsConfig.country, cc) && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2701
3515
  flex: avsLayoutProp === "row" ? 1 : void 0,
2702
3516
  backgroundColor: cardInputBg,
2703
- border: `1px solid ${resolvedBorder}`,
3517
+ borderTop: `1px solid ${resolvedBorder}`,
3518
+ borderRight: `1px solid ${resolvedBorder}`,
3519
+ borderBottom: `1px solid ${resolvedBorder}`,
3520
+ borderLeft: `1px solid ${resolvedBorder}`,
2704
3521
  padding: "10px",
2705
3522
  ...avsLayoutProp === "row" && (0, import_shared5.isAVSFieldVisible)(avsConfig.postal_code, cc) ? { borderRadius: "0", borderTopLeftRadius: "8px", borderBottomLeftRadius: "8px", borderRight: "none" } : { borderRadius: "8px" },
2706
3523
  ...isButtons && bStyles.countrySelect ? bStyles.countrySelect : {}
@@ -2795,221 +3612,465 @@ function SplitCardFormInner({
2795
3612
  }
2796
3613
  )
2797
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;
2798
3669
  if (layout === "buttons") {
2799
- const isButtonsView = viewState === "buttons" || viewState === "expanding";
3670
+ const isButtonsView = viewState === "buttons" || viewState === "expanding" || viewState === "apm-expanding";
2800
3671
  const isCardView = viewState === "expanding" || viewState === "card" || viewState === "collapsing";
2801
3672
  const cardButtonSizing = cardButtonContent === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
2802
- 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;
2803
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;
2804
3675
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
2805
3676
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FloPayKeyframes, {}),
2806
3677
  overlayStatus && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
2807
3678
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "grid" }, children: [
2808
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
3679
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3680
+ "div",
3681
+ {
3682
+ "data-testid": "flopay-buttons-panel",
3683
+ "aria-hidden": !isButtonsView && !buttonsAnim ? true : void 0,
3684
+ style: {
3685
+ gridArea: "1 / 1",
3686
+ display: "flex",
3687
+ flexDirection: "column",
3688
+ gap: "0.5rem",
3689
+ // When card form is showing (and not transitioning), hide but keep mounted.
3690
+ ...!isButtonsView && !buttonsAnim ? BUTTONS_PANEL_HIDDEN_STYLE : {},
3691
+ ...buttonsAnim ? { animation: buttonsAnim, pointerEvents: "none" } : {}
3692
+ },
3693
+ children: [
3694
+ renderDirectPaypalGateDebug("flopay-direct-paypal-gate-debug"),
3695
+ renderStripeGateDebug("flopay-stripe-gate-debug"),
3696
+ shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3697
+ paypalDirectRetry && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3698
+ "div",
3699
+ {
3700
+ "data-testid": "flopay-paypal-direct-retry-notice",
3701
+ style: {
3702
+ padding: "8px 10px",
3703
+ background: "#fef3c7",
3704
+ border: "1px solid #fcd34d",
3705
+ borderRadius: 6,
3706
+ color: "#78350f",
3707
+ fontSize: 13,
3708
+ lineHeight: 1.4
3709
+ },
3710
+ children: "Please confirm your PayPal payment to complete checkout."
3711
+ }
3712
+ ),
3713
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3714
+ DirectPayPalButton,
3715
+ {
3716
+ sessionId,
3717
+ billingApiUrl: resolvedBillingApiUrl,
3718
+ email: resolvedAccount.email,
3719
+ clientId: directPaypal.clientId,
3720
+ environment: directPaypal.environment,
3721
+ currency: currency.toUpperCase(),
3722
+ isSubscription,
3723
+ onTokenizedBody: dispatchTokenizedBody,
3724
+ onComplete,
3725
+ onErrorChange: updateError,
3726
+ onDecline,
3727
+ onButtonClick,
3728
+ runBeforeButtonClick,
3729
+ isProcessing: isSubmitting,
3730
+ onLoadStateChange: setDirectPaypalReady,
3731
+ session: session ?? null,
3732
+ existingOrderId: paypalDirectRetry?.orderId,
3733
+ debug
3734
+ },
3735
+ paypalDirectRetry?.orderId ?? "fresh"
3736
+ )
3737
+ ] }),
3738
+ shouldRenderStripePayPal && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_stripe_js.Elements, { stripe: paypalStripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3739
+ PayPalButtonInner,
3740
+ {
3741
+ sessionId,
3742
+ email: resolvedAccount.email,
3743
+ billingApiUrl: resolvedBillingApiUrl,
3744
+ onTokenizedBody: dispatchTokenizedBody,
3745
+ onErrorChange: updateError,
3746
+ isProcessing: isSubmitting,
3747
+ onButtonClick,
3748
+ onDecline,
3749
+ runBeforeButtonClick,
3750
+ onLoadStateChange: setPaypalLoadState,
3751
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
3752
+ }
3753
+ ) }),
3754
+ shouldRenderWallets ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3755
+ WalletButtonInner,
3756
+ {
3757
+ sessionId,
3758
+ email: resolvedAccount.email,
3759
+ billingApiUrl: resolvedBillingApiUrl,
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,
3780
+ onTokenizedBody: dispatchTokenizedBody,
3781
+ onErrorChange: updateError,
3782
+ onButtonClick,
3783
+ onDecline,
3784
+ runBeforeButtonClick,
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
+ }
3799
+ }
3800
+ ),
3801
+ showStripe && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3802
+ "button",
3803
+ {
3804
+ type: "button",
3805
+ onClick: async () => {
3806
+ if (isSubmitting) return;
3807
+ const beforeClick = await runBeforeButtonClick("card");
3808
+ if (!beforeClick.proceed) return;
3809
+ onButtonClick?.("card");
3810
+ expandToCard();
3811
+ },
3812
+ disabled: isSubmitting,
3813
+ style: {
3814
+ width: "100%",
3815
+ ...cardButtonSizing,
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
+ }),
3831
+ fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
3832
+ fontWeight: 600,
3833
+ cursor: isSubmitting ? "not-allowed" : "pointer",
3834
+ display: "flex",
3835
+ alignItems: "center",
3836
+ justifyContent: "center",
3837
+ gap: "0.625rem",
3838
+ transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
3839
+ position: "relative",
3840
+ opacity: isSubmitting ? 0.6 : 1
3841
+ },
3842
+ onMouseDown: (e) => {
3843
+ e.currentTarget.style.transform = "scale(0.985)";
3844
+ },
3845
+ onMouseUp: (e) => {
3846
+ e.currentTarget.style.transform = "scale(1)";
3847
+ },
3848
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardButtonContentSlot, { content: cardButtonContent })
3849
+ }
3850
+ ),
3851
+ displayError && viewState === "buttons" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { role: "alert", "data-testid": "flopay-error", style: {
3852
+ margin: "0.25rem 0",
3853
+ padding: "0.625rem 0.875rem",
3854
+ background: "#FEF2F2",
3855
+ border: "1px solid #FECACA",
3856
+ borderRadius: "8px",
3857
+ color: "#991B1B",
3858
+ fontSize: "0.85rem",
3859
+ fontWeight: 600,
3860
+ display: "flex",
3861
+ alignItems: "center",
3862
+ gap: "0.5rem",
3863
+ ...bStyles.errorBanner ? bStyles.errorBanner : {}
3864
+ }, children: [
3865
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 9v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", stroke: "#DC2626", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }),
3866
+ displayError
3867
+ ] })
3868
+ ]
3869
+ }
3870
+ ),
3871
+ showStripe && isCardView && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2809
3872
  gridArea: "1 / 1",
2810
- display: "flex",
2811
- flexDirection: "column",
2812
- gap: "0.5rem",
2813
- // When card form is showing (and not transitioning), hide but keep mounted
2814
- ...!isButtonsView && !buttonsAnim ? { visibility: "hidden", position: "absolute", pointerEvents: "none", width: "100%" } : {},
2815
- ...buttonsAnim ? { animation: buttonsAnim, pointerEvents: "none" } : {}
3873
+ ...cardAnim ? { animation: cardAnim } : {},
3874
+ ...viewState === "collapsing" ? { pointerEvents: "none" } : {}
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
2816
3885
  }, children: [
2817
- debug && showPayPal && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2818
- "pre",
2819
- {
2820
- "data-testid": "flopay-direct-paypal-gate-debug",
2821
- style: {
2822
- margin: 0,
2823
- padding: "6px 8px",
2824
- background: "#eef2ff",
2825
- border: "1px solid #c7d2fe",
2826
- borderRadius: 6,
2827
- color: "#111827",
2828
- font: "11px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace",
2829
- whiteSpace: "pre-wrap",
2830
- wordBreak: "break-word"
2831
- },
2832
- children: [
2833
- "FloPay/DirectPayPal-debug (parent gate)",
2834
- ` showPayPal=${showPayPal}`,
2835
- ` directPaypalConfigured=${directPaypalConfigured}`,
2836
- ` inAppBrowserDetected=${String(inAppBrowserDetected)}`,
2837
- ` shouldRenderDirectPayPal=${shouldRenderDirectPayPal}`,
2838
- ` shouldRenderStripePayPal=${shouldRenderStripePayPal}`,
2839
- ` hasPaypalStripeInstance=${!!paypalStripeInstance}`
2840
- ].join("\n")
2841
- }
2842
- ),
2843
- shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2844
- paypalDirectRetry && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2845
- "div",
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",
2846
3893
  {
2847
- "data-testid": "flopay-paypal-direct-retry-notice",
3894
+ type: "button",
3895
+ "data-testid": "flopay-apm-back-button",
3896
+ onClick: collapseFromApm,
2848
3897
  style: {
2849
- padding: "8px 10px",
2850
- background: "#fef3c7",
2851
- border: "1px solid #fcd34d",
2852
- borderRadius: 6,
2853
- color: "#78350f",
2854
- fontSize: 13,
2855
- lineHeight: 1.4
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
2856
3911
  },
2857
- children: "Please confirm your PayPal payment to complete checkout."
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
+ ]
2858
3927
  }
2859
3928
  ),
2860
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2861
- DirectPayPalButton,
2862
- {
2863
- sessionId,
2864
- billingApiUrl: resolvedBillingApiUrl,
2865
- email: resolvedAccount.email,
2866
- clientId: directPaypal.clientId,
2867
- environment: directPaypal.environment,
2868
- currency: currency.toUpperCase(),
2869
- isSubscription,
2870
- onTokenizedBody: dispatchTokenizedBody,
2871
- onComplete,
2872
- onErrorChange: updateError,
2873
- onDecline,
2874
- onButtonClick,
2875
- runBeforeButtonClick,
2876
- isProcessing: isSubmitting,
2877
- onLoadStateChange: setDirectPaypalReady,
2878
- session: session ?? null,
2879
- existingOrderId: paypalDirectRetry?.orderId,
2880
- debug
2881
- },
2882
- paypalDirectRetry?.orderId ?? "fresh"
2883
- )
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)}` })
2884
3938
  ] }),
2885
- shouldRenderStripePayPal && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_stripe_js.Elements, { stripe: paypalStripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2886
- PayPalButtonInner,
2887
- {
2888
- sessionId,
2889
- email: resolvedAccount.email,
2890
- billingApiUrl: resolvedBillingApiUrl,
2891
- onTokenizedBody: dispatchTokenizedBody,
2892
- onErrorChange: updateError,
2893
- isProcessing: isSubmitting,
2894
- onButtonClick,
2895
- onDecline,
2896
- runBeforeButtonClick,
2897
- onLoadStateChange: setPaypalLoadState
2898
- }
2899
- ) }),
2900
- shouldRenderWallets ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2901
- WalletButtonInner,
2902
- {
2903
- sessionId,
2904
- email: resolvedAccount.email,
2905
- billingApiUrl: resolvedBillingApiUrl,
2906
- showApplePay,
2907
- showGooglePay,
2908
- onTokenizedBody: dispatchTokenizedBody,
2909
- onErrorChange: updateError,
2910
- onButtonClick,
2911
- onDecline,
2912
- runBeforeButtonClick,
2913
- onLoadStateChange: setWalletLoadState
2914
- }
2915
- ) }) : 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,
2916
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
+ ] }) })
3968
+ ] })
3969
+ ] });
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)(
2917
3990
  "button",
2918
3991
  {
2919
3992
  type: "button",
2920
- onClick: async () => {
2921
- if (isSubmitting) return;
2922
- const beforeClick = await runBeforeButtonClick("card");
2923
- if (!beforeClick.proceed) return;
2924
- onButtonClick?.("card");
2925
- expandToCard();
2926
- },
2927
- disabled: isSubmitting,
3993
+ "data-testid": "flopay-apm-back-button-default",
3994
+ onClick: collapseFromApm,
2928
3995
  style: {
2929
- width: "100%",
2930
- ...cardButtonSizing,
2931
- backgroundColor: "white",
2932
- color: "#262833",
2933
- border: "1px solid #d1d5db",
2934
- borderRadius: "8px",
2935
- fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
2936
- fontWeight: 600,
2937
- cursor: isSubmitting ? "not-allowed" : "pointer",
2938
- display: "flex",
3996
+ display: "inline-flex",
2939
3997
  alignItems: "center",
2940
- justifyContent: "center",
2941
- gap: "0.625rem",
2942
- transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
2943
- boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
2944
- position: "relative",
2945
- opacity: isSubmitting ? 0.6 : 1,
2946
- ...bStyles.cardButton
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
2947
4009
  },
2948
- onMouseDown: (e) => {
2949
- e.currentTarget.style.transform = "scale(0.985)";
2950
- },
2951
- onMouseUp: (e) => {
2952
- e.currentTarget.style.transform = "scale(1)";
2953
- },
2954
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CardButtonContentSlot, { content: cardButtonContent })
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
+ ]
2955
4025
  }
2956
4026
  ),
2957
- displayError && viewState === "buttons" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { role: "alert", "data-testid": "flopay-error", style: {
2958
- margin: "0.25rem 0",
2959
- padding: "0.625rem 0.875rem",
2960
- background: "#FEF2F2",
2961
- border: "1px solid #FECACA",
2962
- borderRadius: "8px",
2963
- color: "#991B1B",
2964
- fontSize: "0.85rem",
4027
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
4028
+ flex: 1,
4029
+ textAlign: "center",
2965
4030
  fontWeight: 600,
2966
- display: "flex",
2967
- alignItems: "center",
2968
- gap: "0.5rem",
2969
- ...bStyles.errorBanner ? bStyles.errorBanner : {}
2970
- }, children: [
2971
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 9v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", stroke: "#DC2626", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }),
2972
- displayError
2973
- ] })
4031
+ fontSize: bStyles.titleFontSize ?? "1.05rem",
4032
+ color: resolvedTitleColor,
4033
+ paddingRight: 80,
4034
+ ...bStyles.title
4035
+ }, children: `Pay with ${(0, import_shared5.getStripeMethodDisplayName)(expandedApmMethod)}` })
2974
4036
  ] }),
2975
- isCardView && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: {
2976
- gridArea: "1 / 1",
2977
- ...cardAnim ? { animation: cardAnim } : {},
2978
- ...viewState === "collapsing" ? { pointerEvents: "none" } : {}
2979
- }, children: cardFormBlock })
2980
- ] })
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
+ ] }) })
2981
4066
  ] });
2982
4067
  }
2983
4068
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
2984
4069
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(FloPayKeyframes, {}),
2985
4070
  overlayStatus && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
2986
4071
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
2987
- debug && showPayPal && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
2988
- "pre",
2989
- {
2990
- "data-testid": "flopay-direct-paypal-gate-debug-default",
2991
- style: {
2992
- margin: 0,
2993
- padding: "6px 8px",
2994
- background: "#eef2ff",
2995
- border: "1px solid #c7d2fe",
2996
- borderRadius: 6,
2997
- color: "#111827",
2998
- font: "11px/1.35 ui-monospace, SFMono-Regular, Menlo, monospace",
2999
- whiteSpace: "pre-wrap",
3000
- wordBreak: "break-word"
3001
- },
3002
- children: [
3003
- "FloPay/DirectPayPal-debug (parent gate)",
3004
- ` showPayPal=${showPayPal}`,
3005
- ` directPaypalConfigured=${directPaypalConfigured}`,
3006
- ` inAppBrowserDetected=${String(inAppBrowserDetected)}`,
3007
- ` shouldRenderDirectPayPal=${shouldRenderDirectPayPal}`,
3008
- ` shouldRenderStripePayPal=${shouldRenderStripePayPal}`,
3009
- ` hasPaypalStripeInstance=${!!paypalStripeInstance}`
3010
- ].join("\n")
3011
- }
3012
- ),
4072
+ renderDirectPaypalGateDebug("flopay-direct-paypal-gate-debug-default"),
4073
+ renderStripeGateDebug("flopay-stripe-gate-debug-default"),
3013
4074
  shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3014
4075
  paypalDirectRetry && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
3015
4076
  "div",
@@ -3064,7 +4125,8 @@ function SplitCardFormInner({
3064
4125
  onButtonClick,
3065
4126
  onDecline,
3066
4127
  runBeforeButtonClick,
3067
- onLoadStateChange: setPaypalLoadState
4128
+ onLoadStateChange: setPaypalLoadState,
4129
+ placeholderBorderRadius: bStyles.cardButton?.borderRadius ?? resolvedBorderRadius
3068
4130
  }
3069
4131
  ) }),
3070
4132
  shouldRenderWallets && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
@@ -3073,18 +4135,49 @@ function SplitCardFormInner({
3073
4135
  sessionId,
3074
4136
  email: resolvedAccount.email,
3075
4137
  billingApiUrl: resolvedBillingApiUrl,
3076
- showApplePay,
3077
- 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,
3078
4158
  onTokenizedBody: dispatchTokenizedBody,
3079
4159
  onErrorChange: updateError,
3080
4160
  onButtonClick,
3081
4161
  onDecline,
3082
4162
  runBeforeButtonClick,
3083
- 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
+ }
3084
4177
  }
3085
- ) })
4178
+ )
3086
4179
  ] }),
3087
- (shouldDisplayWalletRow || shouldDisplayPayPalRow) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
4180
+ showStripe && (shouldDisplayWalletRow || shouldDisplayPayPalRow || shouldDisplayPaymentElementRow) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: {
3088
4181
  display: "flex",
3089
4182
  alignItems: "center",
3090
4183
  gap: "0.75rem",
@@ -3096,7 +4189,7 @@ function SplitCardFormInner({
3096
4189
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "or pay with card" }),
3097
4190
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { flex: 1, height: 1, backgroundColor: "#ddd" } })
3098
4191
  ] }),
3099
- cardFormBlock
4192
+ showStripe && cardFormBlock
3100
4193
  ] });
3101
4194
  }
3102
4195
 
@@ -3629,6 +4722,7 @@ function hasInlineSessionPatchData(patch) {
3629
4722
  }
3630
4723
  function FloPayCheckout({
3631
4724
  sessionId: sessionIdProp,
4725
+ nonce: nonceProp,
3632
4726
  createSession: createSessionParams,
3633
4727
  billingApiUrl,
3634
4728
  appearance: appearanceOverride,
@@ -3642,6 +4736,7 @@ function FloPayCheckout({
3642
4736
  onCountryChange,
3643
4737
  onZipChange,
3644
4738
  showPayPal = true,
4739
+ showStripe = true,
3645
4740
  showApplePay = true,
3646
4741
  showGooglePay = true,
3647
4742
  debug = false,
@@ -3988,6 +5083,14 @@ function FloPayCheckout({
3988
5083
  },
3989
5084
  successUrl: params?.successUrl,
3990
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(),
3991
5094
  i: params?.items?.map((x) => `${x.code ?? x.providerItemId ?? ""}:${x.totalAmount ?? ""}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
3992
5095
  s: params?.subscriptions?.map((x) => `${x.code ?? x.providerPlanId ?? ""}:${x.totalAmount ?? ""}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
3993
5096
  couponCodes: params?.couponCodes,
@@ -4214,7 +5317,7 @@ function FloPayCheckout({
4214
5317
  async function init() {
4215
5318
  try {
4216
5319
  const api = new import_js4.PaymentAPI(resolvedBillingUrl);
4217
- const result = await api.getUnifiedCheckoutSession(activeSessionId);
5320
+ const result = await api.getUnifiedCheckoutSession(activeSessionId, nonceProp);
4218
5321
  if (cancelled) return;
4219
5322
  setUnified(result);
4220
5323
  const sess = result.data.session ?? null;
@@ -4301,6 +5404,7 @@ function FloPayCheckout({
4301
5404
  createSessionHash,
4302
5405
  effectiveCreateSessionMode,
4303
5406
  initSessionDependency,
5407
+ nonceProp,
4304
5408
  runSavedPaymentFlow
4305
5409
  ]);
4306
5410
  const handleConfirmCheckout = (0, import_react9.useCallback)(async () => {
@@ -4374,17 +5478,18 @@ function FloPayCheckout({
4374
5478
  ] });
4375
5479
  }
4376
5480
  if (layout === "buttons") {
5481
+ const bundleRadius = themeBundle?.buttonsLayout?.cardButton?.borderRadius ?? themeBundle?.appearance.variables?.borderRadius ?? 8;
4377
5482
  const skeletonBar = (h) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
4378
5483
  height: h,
4379
- borderRadius: 8,
5484
+ borderRadius: bundleRadius,
4380
5485
  background: "#e5e7eb",
4381
5486
  animation: "flopay-loading-pulse 1.5s ease-in-out infinite"
4382
5487
  } });
4383
5488
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
4384
5489
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
4385
5490
  showPayPal && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4386
- (showApplePay || showGooglePay) && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4387
- skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
5491
+ showStripe && (showApplePay || showGooglePay) && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
5492
+ showStripe && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4388
5493
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("style", { children: `@keyframes flopay-loading-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
4389
5494
  ] }),
4390
5495
  modeOverlay
@@ -4429,6 +5534,7 @@ function FloPayCheckout({
4429
5534
  {
4430
5535
  onButtonClick,
4431
5536
  showPayPal,
5537
+ showStripe,
4432
5538
  showApplePay,
4433
5539
  showGooglePay,
4434
5540
  theme,
@@ -4573,6 +5679,8 @@ function FloPayCheckout({
4573
5679
  onErrorChange: setModeError,
4574
5680
  onFullNameChange,
4575
5681
  showPayPal,
5682
+ showStripe,
5683
+ enabledPaymentMethods: unified?.data.stripe?.enabledPaymentMethods,
4576
5684
  showApplePay,
4577
5685
  showGooglePay,
4578
5686
  layout,
@@ -4637,6 +5745,7 @@ function InterimButtonsView({
4637
5745
  cardOpen,
4638
5746
  errorMessage,
4639
5747
  showPayPal,
5748
+ showStripe = true,
4640
5749
  showApplePay,
4641
5750
  showGooglePay,
4642
5751
  theme,
@@ -4668,9 +5777,10 @@ function InterimButtonsView({
4668
5777
  title: { ...base.title, ...stylesOverride.title }
4669
5778
  };
4670
5779
  }, [themeBundle, buttonsTheme, stylesOverride]);
5780
+ const skeletonRadius = themeBundle?.buttonsLayout?.cardButton?.borderRadius ?? themeBundle?.appearance.variables?.borderRadius ?? 8;
4671
5781
  const skeleton = (h) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
4672
5782
  height: h,
4673
- borderRadius: 8,
5783
+ borderRadius: skeletonRadius,
4674
5784
  background: "#e5e7eb",
4675
5785
  animation: "flopay-interim-pulse 1.5s ease-in-out infinite"
4676
5786
  } });
@@ -4741,8 +5851,28 @@ function InterimButtonsView({
4741
5851
  ] }),
4742
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" } }) }),
4743
5853
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex" }, children: [
4744
- /* @__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" } }) }),
4745
- /* @__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" } }) })
4746
5876
  ] }),
4747
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" } }) }),
4748
5878
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: {
@@ -4766,8 +5896,8 @@ function InterimButtonsView({
4766
5896
  }
4767
5897
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
4768
5898
  showPayPal && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4769
- (showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
4770
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5899
+ showStripe && (showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
5900
+ showStripe && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4771
5901
  "button",
4772
5902
  {
4773
5903
  type: "button",
@@ -4836,7 +5966,7 @@ var import_js5 = require("@flopay/js");
4836
5966
  var import_shared9 = require("@flopay/shared");
4837
5967
  var import_react10 = require("react");
4838
5968
  var import_jsx_runtime8 = require("react/jsx-runtime");
4839
- var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
5969
+ var WALLET_RESUME_KEY = "flopay_wallet_resume";
4840
5970
  var CheckoutForm = (0, import_react10.forwardRef)(
4841
5971
  function CheckoutForm2(props, ref) {
4842
5972
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(CheckoutFormInner, { ...props, innerRef: ref });
@@ -4962,7 +6092,7 @@ function CheckoutFormInner({
4962
6092
  onDecline?.(buildDeclineEvent("paypal", message));
4963
6093
  return;
4964
6094
  }
4965
- localStorage.setItem(WALLET_RESUME_KEY2, JSON.stringify({
6095
+ localStorage.setItem(WALLET_RESUME_KEY, JSON.stringify({
4966
6096
  sessionId,
4967
6097
  paymentIntentId: "",
4968
6098
  tokenType: "card",
@@ -5038,12 +6168,12 @@ function CheckoutFormInner({
5038
6168
  }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
5039
6169
  (0, import_react10.useEffect)(() => {
5040
6170
  if (typeof window === "undefined") return;
5041
- const stored = localStorage.getItem(WALLET_RESUME_KEY2);
6171
+ const stored = localStorage.getItem(WALLET_RESUME_KEY);
5042
6172
  if (!stored) return;
5043
6173
  try {
5044
6174
  const payload = JSON.parse(stored);
5045
6175
  if (payload.sessionId === sessionId) {
5046
- localStorage.removeItem(WALLET_RESUME_KEY2);
6176
+ localStorage.removeItem(WALLET_RESUME_KEY);
5047
6177
  dispatchTokenizedBody({
5048
6178
  id: payload.tokenId,
5049
6179
  type: payload.tokenType,
@@ -5051,7 +6181,7 @@ function CheckoutFormInner({
5051
6181
  });
5052
6182
  }
5053
6183
  } catch {
5054
- localStorage.removeItem(WALLET_RESUME_KEY2);
6184
+ localStorage.removeItem(WALLET_RESUME_KEY);
5055
6185
  }
5056
6186
  }, [sessionId, dispatchTokenizedBody]);
5057
6187
  const handleSubmit = (0, import_react10.useCallback)(
@@ -5396,10 +6526,12 @@ function resolveCreateSessionDraft(props) {
5396
6526
  if (!props.clientId || !props.account || !props.successUrl || !props.cancelUrl) {
5397
6527
  return null;
5398
6528
  }
6529
+ const hasProducts = props.products != null;
5399
6530
  return {
5400
6531
  clientId: props.clientId,
5401
- items: props.items,
5402
- subscriptions: props.subscriptions,
6532
+ products: props.products,
6533
+ items: hasProducts ? void 0 : props.items,
6534
+ subscriptions: hasProducts ? void 0 : props.subscriptions,
5403
6535
  account: props.account,
5404
6536
  successUrl: props.successUrl,
5405
6537
  cancelUrl: props.cancelUrl,
@@ -5417,6 +6549,7 @@ function FloPayAutomaticPaymentButton({
5417
6549
  paymentMethodId: _deprecatedPaymentMethodId,
5418
6550
  checkoutMethod: _deprecatedCheckoutMethod,
5419
6551
  clientId,
6552
+ products,
5420
6553
  items,
5421
6554
  subscriptions,
5422
6555
  account,
@@ -5447,6 +6580,7 @@ function FloPayAutomaticPaymentButton({
5447
6580
  () => resolveCreateSessionDraft({
5448
6581
  createSession,
5449
6582
  clientId,
6583
+ products,
5450
6584
  items,
5451
6585
  subscriptions,
5452
6586
  account,
@@ -5462,6 +6596,7 @@ function FloPayAutomaticPaymentButton({
5462
6596
  clientId,
5463
6597
  couponCodes,
5464
6598
  createSession,
6599
+ products,
5465
6600
  items,
5466
6601
  subscriptions,
5467
6602
  successUrl,
@@ -5750,10 +6885,20 @@ function FloPayAutomaticPaymentButton({
5750
6885
  style: {
5751
6886
  width: "100%",
5752
6887
  ...cardButtonSizing,
5753
- backgroundColor: "white",
5754
- color: "#262833",
5755
- border: "1px solid #d1d5db",
5756
- 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
+ }),
5757
6902
  fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
5758
6903
  fontWeight: 600,
5759
6904
  cursor: disabled || isProcessing ? "not-allowed" : "pointer",
@@ -5761,11 +6906,9 @@ function FloPayAutomaticPaymentButton({
5761
6906
  alignItems: "center",
5762
6907
  justifyContent: "center",
5763
6908
  gap: "0.625rem",
5764
- boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
5765
6909
  transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
5766
6910
  position: "relative",
5767
6911
  opacity: disabled || isProcessing ? 0.6 : 1,
5768
- ...bStyles.cardButton,
5769
6912
  ...style
5770
6913
  },
5771
6914
  onMouseDown: (e) => {