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