@flopay/react 0.3.14 → 0.3.15

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
@@ -206,7 +206,7 @@ import {
206
206
  useStripe as useStripeRaw
207
207
  } from "@stripe/react-stripe-js";
208
208
  import { resolveButtonsLayoutTheme, getPostalCodeLabel, COUNTRY_OPTIONS } from "@flopay/shared";
209
- import { forwardRef, useCallback, useEffect as useEffect3, useImperativeHandle, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
209
+ import { forwardRef, useCallback, useContext as useContext3, useEffect as useEffect3, useImperativeHandle, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
210
210
 
211
211
  // src/hooks.ts
212
212
  import { useContext as useContext2 } from "react";
@@ -307,15 +307,6 @@ function buildSyntheticSession(params, checkoutModeOverride) {
307
307
  }))
308
308
  };
309
309
  }
310
- function ensureInlineSessionReady(params) {
311
- if (!params.account.email?.trim()) {
312
- throw new FloPayError(
313
- "Email is required before continuing with card checkout.",
314
- "validation_error",
315
- { param: "createSession.account.email" }
316
- );
317
- }
318
- }
319
310
  function mergeAccountPatch(base, patch) {
320
311
  if (!patch) return base;
321
312
  return {
@@ -777,6 +768,7 @@ function SplitCardFormInner({
777
768
  }) {
778
769
  const flopay = useFloPay();
779
770
  const elements = useElements();
771
+ const checkout = useContext3(CheckoutContext);
780
772
  const contextBillingUrl = useBillingApiUrl();
781
773
  const [processing, setProcessing] = useState2(false);
782
774
  const [error, setError] = useState2(null);
@@ -822,7 +814,8 @@ function SplitCardFormInner({
822
814
  title: { ...base.title, ...buttonsStylesOverride.title }
823
815
  };
824
816
  }, [buttonsTheme, buttonsStylesOverride]);
825
- const isSubmitting = externalProcessing ?? processing;
817
+ const isInlineSessionPatchProcessing = checkout.inlineSessionPatchProcessing ?? false;
818
+ const isSubmitting = (externalProcessing ?? processing) || isInlineSessionPatchProcessing;
826
819
  const isSelfContained = !onTokenizedBody;
827
820
  const baseUrl = resolvedBillingApiUrl.replace(/\/+$/, "");
828
821
  const resolvedAccount = useMemo2(() => mergeAccountPatch({
@@ -881,8 +874,11 @@ function SplitCardFormInner({
881
874
  if (result === false) {
882
875
  return false;
883
876
  }
884
- if (result?.account) {
885
- setAccountPatch((prev) => ({ ...prev ?? {}, ...result.account }));
877
+ if (result && typeof result === "object") {
878
+ await checkout.applyInlineSessionPatch?.(result);
879
+ if (result.account) {
880
+ setAccountPatch((prev) => ({ ...prev ?? {}, ...result.account }));
881
+ }
886
882
  }
887
883
  return true;
888
884
  } catch (err) {
@@ -894,7 +890,7 @@ function SplitCardFormInner({
894
890
  onError?.(floPayErr);
895
891
  return false;
896
892
  }
897
- }, [onBeforeButtonClick, sessionId, updateError, onError]);
893
+ }, [checkout.applyInlineSessionPatch, onBeforeButtonClick, sessionId, updateError, onError]);
898
894
  const processPaymentInternal = useCallback(
899
895
  async (tokenizedBody) => {
900
896
  if (processingRef.current) return;
@@ -1503,11 +1499,13 @@ function SplitCardFormInner({
1503
1499
  {
1504
1500
  type: "button",
1505
1501
  onClick: async () => {
1502
+ if (isSubmitting) return;
1506
1503
  const shouldContinue = await runBeforeCardButtonClick();
1507
1504
  if (!shouldContinue) return;
1508
1505
  onButtonClick?.("card");
1509
1506
  expandToCard();
1510
1507
  },
1508
+ disabled: isSubmitting,
1511
1509
  style: {
1512
1510
  width: "100%",
1513
1511
  padding: "0.9rem 1rem",
@@ -1517,7 +1515,7 @@ function SplitCardFormInner({
1517
1515
  borderRadius: "8px",
1518
1516
  fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
1519
1517
  fontWeight: 600,
1520
- cursor: "pointer",
1518
+ cursor: isSubmitting ? "not-allowed" : "pointer",
1521
1519
  display: "flex",
1522
1520
  alignItems: "center",
1523
1521
  justifyContent: "center",
@@ -1525,6 +1523,7 @@ function SplitCardFormInner({
1525
1523
  transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
1526
1524
  boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
1527
1525
  position: "relative",
1526
+ opacity: isSubmitting ? 0.6 : 1,
1528
1527
  ...bStyles.cardButton
1529
1528
  },
1530
1529
  onMouseDown: (e) => {
@@ -1608,6 +1607,12 @@ function SplitCardFormInner({
1608
1607
 
1609
1608
  // src/flopay-checkout.tsx
1610
1609
  import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1610
+ function hasInlineSessionPatchData(patch) {
1611
+ if (!patch) return false;
1612
+ return Boolean(
1613
+ patch.account && Object.keys(patch.account).length > 0 || patch.couponCodes !== void 0 || patch.tagsData !== void 0 || patch.utmMetadata !== void 0
1614
+ );
1615
+ }
1611
1616
  function FloPayCheckout({
1612
1617
  sessionId: sessionIdProp,
1613
1618
  createSession: createSessionParams,
@@ -1655,7 +1660,6 @@ function FloPayCheckout({
1655
1660
  const [createSessionPatch, setCreateSessionPatch] = useState3(void 0);
1656
1661
  const [createSessionPatchBaseHash, setCreateSessionPatchBaseHash] = useState3("");
1657
1662
  const [cardBootstrapPending, setCardBootstrapPending] = useState3(false);
1658
- const [deferredCardOpen, setDeferredCardOpen] = useState3(false);
1659
1663
  const autoCheckoutAttempted = useRef3(false);
1660
1664
  const onCompleteRef = useRef3(onComplete);
1661
1665
  onCompleteRef.current = onComplete;
@@ -1790,9 +1794,6 @@ function FloPayCheckout({
1790
1794
  );
1791
1795
  const inflightRef = useRef3(/* @__PURE__ */ new Map());
1792
1796
  const initializedHashRef = useRef3(null);
1793
- const deferInlineSessionUntilCardClick = Boolean(
1794
- effectiveCreateSession && !children && layout === "buttons" && onBeforeButtonClick && (effectiveCreateSession.checkoutMode ?? checkoutModeProp ?? "full") === "full"
1795
- );
1796
1797
  function hashCreateParams(params) {
1797
1798
  const key = JSON.stringify({
1798
1799
  c: params?.clientId,
@@ -1822,7 +1823,6 @@ function FloPayCheckout({
1822
1823
  setResolvedSessionId(sessionIdProp ?? "");
1823
1824
  }, [sessionIdProp]);
1824
1825
  async function resolveInlineSession(params, cacheKey) {
1825
- ensureInlineSessionReady(params);
1826
1826
  const api = new PaymentAPI(resolvedBillingUrl);
1827
1827
  let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
1828
1828
  let realResult = null;
@@ -1867,104 +1867,64 @@ function FloPayCheckout({
1867
1867
  } finally {
1868
1868
  inflightRef.current.delete(cacheKey);
1869
1869
  }
1870
- if (patch) {
1871
- setCreateSessionPatchBaseHash(baseCreateSessionHash);
1872
- setCreateSessionPatch((prev) => mergeInlineSessionPatches(prev, patch));
1873
- }
1874
- const { sid, result: realResult } = resolved;
1875
- setUnified(realResult);
1876
- if (realResult.data.session) {
1877
- setSession(realResult.data.session);
1878
- }
1879
- if (sid) {
1880
- setResolvedSessionId(sid);
1881
- }
1882
- let publishableKey;
1883
- if (realResult.provider === "stripe") {
1884
- publishableKey = realResult.data.stripe?.publishableKey;
1885
- }
1886
- if (!publishableKey) publishableKey = fallbackPublishableKey;
1887
- if (!publishableKey) {
1888
- throw new FloPayError3(
1889
- "No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
1890
- "validation_error"
1891
- );
1892
- }
1893
- const instance = await loadFloPay(publishableKey, {
1894
- billingApiUrl: resolvedBillingUrl,
1895
- locale
1896
- });
1897
- flopayRef.current = instance;
1898
- setFloPay(instance);
1899
1870
  initializedHashRef.current = cacheKey;
1900
- setIsLoading(false);
1871
+ try {
1872
+ if (patch) {
1873
+ setCreateSessionPatchBaseHash(baseCreateSessionHash);
1874
+ setCreateSessionPatch((prev) => mergeInlineSessionPatches(prev, patch));
1875
+ }
1876
+ const { sid, result: realResult } = resolved;
1877
+ setUnified(realResult);
1878
+ if (realResult.data.session) {
1879
+ setSession(realResult.data.session);
1880
+ }
1881
+ if (sid) {
1882
+ setResolvedSessionId(sid);
1883
+ }
1884
+ let publishableKey;
1885
+ if (realResult.provider === "stripe") {
1886
+ publishableKey = realResult.data.stripe?.publishableKey;
1887
+ }
1888
+ if (!publishableKey) publishableKey = fallbackPublishableKey;
1889
+ if (!publishableKey) {
1890
+ throw new FloPayError3(
1891
+ "No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
1892
+ "validation_error"
1893
+ );
1894
+ }
1895
+ const instance = await loadFloPay(publishableKey, {
1896
+ billingApiUrl: resolvedBillingUrl,
1897
+ locale
1898
+ });
1899
+ flopayRef.current = instance;
1900
+ setFloPay(instance);
1901
+ setIsLoading(false);
1902
+ } catch (err) {
1903
+ if (initializedHashRef.current === cacheKey) {
1904
+ initializedHashRef.current = null;
1905
+ }
1906
+ throw err;
1907
+ }
1901
1908
  return resolved;
1902
1909
  },
1903
1910
  [fallbackPublishableKey, locale, resolvedBillingUrl]
1904
1911
  );
1905
- const handleDeferredCardButtonClick = useCallback2(async () => {
1906
- if (cardBootstrapPending) return;
1907
- setLoadError(null);
1912
+ const handleInlineSessionPatch = useCallback2(async (patch) => {
1913
+ if (!hasInlineSessionPatchData(patch) || cardBootstrapPending) return;
1908
1914
  setCardBootstrapPending(true);
1909
1915
  try {
1910
- const beforeClickResult = await onBeforeButtonClick?.({
1911
- method: "card",
1912
- createSession: effectiveCreateSession
1913
- });
1914
- if (beforeClickResult === false) {
1915
- setDeferredCardOpen(false);
1916
- return;
1917
- }
1918
- const patch = beforeClickResult && typeof beforeClickResult === "object" ? beforeClickResult : void 0;
1919
- const baseParams = createSessionParamsRef.current;
1920
- if (!baseParams) {
1921
- throw new FloPayError3(
1922
- "createSession is required to bootstrap checkout.",
1923
- "validation_error"
1924
- );
1925
- }
1926
- ensureInlineSessionReady(mergeInlineSessionPatch(baseParams, patch));
1927
- setDeferredCardOpen(true);
1928
- onButtonClick?.("card");
1929
1916
  await bootstrapInlineSession(patch);
1930
- } catch (err) {
1931
- setDeferredCardOpen(false);
1932
- const floPayErr = err instanceof FloPayError3 ? err : new FloPayError3(
1933
- err instanceof Error ? err.message : "Failed to start card checkout.",
1934
- "api_error"
1935
- );
1936
- setLoadError(floPayErr);
1937
- onErrorRef.current?.(floPayErr);
1938
1917
  } finally {
1939
1918
  setCardBootstrapPending(false);
1940
1919
  }
1941
1920
  }, [
1942
1921
  bootstrapInlineSession,
1943
- baseCreateSessionHash,
1944
- cardBootstrapPending,
1945
- effectiveCreateSession,
1946
- onButtonClick,
1947
- onBeforeButtonClick
1922
+ cardBootstrapPending
1948
1923
  ]);
1949
1924
  useEffect4(() => {
1950
1925
  let cancelled = false;
1951
1926
  setLoadError(null);
1952
1927
  if (createSessionHash) {
1953
- if (deferInlineSessionUntilCardClick) {
1954
- if (initializedHashRef.current !== createSessionHash) {
1955
- setSession(null);
1956
- setUnified(null);
1957
- setFloPay(null);
1958
- setResolvedSessionId("");
1959
- setDeferredCardOpen(false);
1960
- flopayRef.current = null;
1961
- }
1962
- setCurrentMode(createSessionParamsRef.current?.checkoutMode ?? checkoutModeProp ?? "full");
1963
- setIsLoading(false);
1964
- return () => {
1965
- cancelled = true;
1966
- };
1967
- }
1968
1928
  if (initializedHashRef.current === createSessionHash) return;
1969
1929
  const params = createSessionParamsRef.current;
1970
1930
  setSession(buildSyntheticSession(params, checkoutModeProp));
@@ -2060,7 +2020,7 @@ function FloPayCheckout({
2060
2020
  return () => {
2061
2021
  cancelled = true;
2062
2022
  };
2063
- }, [resolvedSessionId, createSessionHash, checkoutModeProp, deferInlineSessionUntilCardClick, bootstrapInlineSession]);
2023
+ }, [resolvedSessionId, createSessionHash, checkoutModeProp, bootstrapInlineSession]);
2064
2024
  const handleConfirmCheckout = useCallback2(async () => {
2065
2025
  if (confirmProcessing || !session) return;
2066
2026
  setConfirmProcessing(true);
@@ -2103,17 +2063,29 @@ function FloPayCheckout({
2103
2063
  }
2104
2064
  return opts;
2105
2065
  }, [unified, session, appearance, resolvedBillingUrl]);
2066
+ const shouldHandleInlineSessionPatch = Boolean(
2067
+ createSessionParams && !children && layout === "buttons" && onBeforeButtonClick && (effectiveCreateSession?.checkoutMode ?? checkoutModeProp ?? "full") === "full"
2068
+ );
2106
2069
  const checkoutValue = useMemo3(
2107
2070
  () => ({
2108
2071
  session,
2109
2072
  loading: isLoading,
2110
2073
  error: loadError,
2111
- checkoutMode: currentMode
2074
+ checkoutMode: currentMode,
2075
+ applyInlineSessionPatch: shouldHandleInlineSessionPatch ? handleInlineSessionPatch : void 0,
2076
+ inlineSessionPatchProcessing: cardBootstrapPending
2112
2077
  }),
2113
- [session, isLoading, loadError, currentMode]
2078
+ [
2079
+ session,
2080
+ isLoading,
2081
+ loadError,
2082
+ currentMode,
2083
+ shouldHandleInlineSessionPatch,
2084
+ handleInlineSessionPatch,
2085
+ cardBootstrapPending
2086
+ ]
2114
2087
  );
2115
2088
  const shouldShowInterimButtons = Boolean(createSessionParams) && layout === "buttons" && (!flopay || !providerOptions);
2116
- const shouldKeepDeferredInterimVisible = shouldShowInterimButtons && deferInlineSessionUntilCardClick;
2117
2089
  if (isLoading) {
2118
2090
  if (loadingNode) return /* @__PURE__ */ jsx5(Fragment3, { children: loadingNode });
2119
2091
  if (layout === "buttons") {
@@ -2142,7 +2114,7 @@ function FloPayCheckout({
2142
2114
  /* @__PURE__ */ jsx5("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
2143
2115
  ] });
2144
2116
  }
2145
- if (loadError && !shouldKeepDeferredInterimVisible) {
2117
+ if (loadError) {
2146
2118
  if (errorNode) return /* @__PURE__ */ jsx5(Fragment3, { children: errorNode(loadError) });
2147
2119
  return /* @__PURE__ */ jsx5(
2148
2120
  "div",
@@ -2162,13 +2134,9 @@ function FloPayCheckout({
2162
2134
  InterimButtonsView,
2163
2135
  {
2164
2136
  onButtonClick,
2165
- onCardButtonClick: deferInlineSessionUntilCardClick ? handleDeferredCardButtonClick : void 0,
2166
- cardLoading: cardBootstrapPending,
2167
- cardOpen: deferInlineSessionUntilCardClick ? deferredCardOpen : void 0,
2168
- errorMessage: shouldKeepDeferredInterimVisible ? loadError?.message ?? null : null,
2169
- showPayPal: deferInlineSessionUntilCardClick ? false : showPayPal,
2170
- showApplePay: deferInlineSessionUntilCardClick ? false : showApplePay,
2171
- showGooglePay: deferInlineSessionUntilCardClick ? false : showGooglePay,
2137
+ showPayPal,
2138
+ showApplePay,
2139
+ showGooglePay,
2172
2140
  buttonsTheme,
2173
2141
  buttonsStyles,
2174
2142
  cardButtonContent,
@@ -2269,7 +2237,6 @@ function FloPayCheckout({
2269
2237
  enableAVS,
2270
2238
  avsLayout,
2271
2239
  country: session?.customer?.country,
2272
- initialCardOpen: deferredCardOpen,
2273
2240
  submitLabel,
2274
2241
  className
2275
2242
  }