@flopay/react 0.3.20 → 0.4.1

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/README.md CHANGED
@@ -299,9 +299,13 @@ The `SplitCardForm` layout:
299
299
 
300
300
  ### PayPal Handling
301
301
 
302
- PayPal requires its own Stripe Elements instance because it cannot share an Elements group that uses `paymentMethodCreation: 'manual'`. The SDK handles this in two ways:
302
+ PayPal requires its own Stripe Elements instance because it cannot share an Elements group that uses `paymentMethodCreation: 'manual'`. In addition, the billing API may return a dedicated Stripe account for PayPal via `gatewayData.paypalPublishableKey` — when set, every PayPal Stripe operation uses that key. When the field is `null` or missing, PayPal is disabled (the button is not rendered); there is no fallback to the primary `publishableKey`.
303
303
 
304
- - **`SplitCardForm`**: Automatically manages two Elements instances internally. Card fields use the provider's elements with `paymentMethodCreation: 'manual'`. PayPal renders inside a separate `<Elements>` wrapper with `captureMethod: 'manual'` and no `paymentMethodCreation`.
304
+ The SDK handles this in two ways:
305
+
306
+ - **`SplitCardForm`**: Automatically manages two Elements instances internally. Card/wallet fields use the primary Stripe account with `paymentMethodCreation: 'manual'`. PayPal renders inside a separate `<Elements>` wrapper backed by the dedicated PayPal FloPay instance (exposed via the `usePayPalFloPay()` hook).
307
+
308
+ - **`FloPayProvider`** (manual composition): accepts an optional `paypalFlopay` prop. Load a second `FloPay` instance with the PayPal publishable key yourself, then pass it in alongside the primary instance. `usePayPalFloPay()` exposes it to descendants.
305
309
 
306
310
  - **`PayPalButton`** (standalone): Must be rendered inside its own `FloPayProvider`:
307
311
 
package/dist/index.cjs CHANGED
@@ -43,7 +43,8 @@ __export(index_exports, {
43
43
  SplitCardForm: () => SplitCardForm,
44
44
  useCheckout: () => useCheckout,
45
45
  useElements: () => useElements,
46
- useFloPay: () => useFloPay
46
+ useFloPay: () => useFloPay,
47
+ usePayPalFloPay: () => usePayPalFloPay
47
48
  });
48
49
  module.exports = __toCommonJS(index_exports);
49
50
 
@@ -55,6 +56,7 @@ var import_shared = require("@flopay/shared");
55
56
  var import_react = require("react");
56
57
  var FloPayContext = (0, import_react.createContext)({
57
58
  flopay: null,
59
+ paypalFlopay: null,
58
60
  elements: null,
59
61
  billingApiUrl: ""
60
62
  });
@@ -68,12 +70,16 @@ var CheckoutContext = (0, import_react.createContext)({
68
70
  var import_jsx_runtime = require("react/jsx-runtime");
69
71
  function FloPayProvider({
70
72
  flopay: floPayProp,
73
+ paypalFlopay: paypalFloPayProp,
71
74
  options,
72
75
  children
73
76
  }) {
74
77
  const [flopay, setFloPay] = (0, import_react2.useState)(
75
78
  floPayProp instanceof Promise ? null : floPayProp
76
79
  );
80
+ const [paypalFlopay, setPaypalFloPay] = (0, import_react2.useState)(
81
+ paypalFloPayProp instanceof Promise || !paypalFloPayProp ? null : paypalFloPayProp
82
+ );
77
83
  const [elements, setElements] = (0, import_react2.useState)(null);
78
84
  (0, import_react2.useEffect)(() => {
79
85
  let cancelled = false;
@@ -90,6 +96,25 @@ function FloPayProvider({
90
96
  cancelled = true;
91
97
  };
92
98
  }, [floPayProp]);
99
+ (0, import_react2.useEffect)(() => {
100
+ let cancelled = false;
101
+ if (!paypalFloPayProp) {
102
+ setPaypalFloPay(null);
103
+ return;
104
+ }
105
+ if (paypalFloPayProp instanceof Promise) {
106
+ paypalFloPayProp.then((instance) => {
107
+ if (!cancelled) {
108
+ setPaypalFloPay(instance);
109
+ }
110
+ });
111
+ } else {
112
+ setPaypalFloPay(paypalFloPayProp);
113
+ }
114
+ return () => {
115
+ cancelled = true;
116
+ };
117
+ }, [paypalFloPayProp]);
93
118
  (0, import_react2.useEffect)(() => {
94
119
  if (!flopay) {
95
120
  setElements(null);
@@ -109,8 +134,8 @@ function FloPayProvider({
109
134
  }, [flopay, options?.appearance, options?.clientSecret, options?.amount, options?.currency]);
110
135
  const resolvedBillingApiUrl = (0, import_shared.resolveBillingApiUrl)(options?.billingApiUrl);
111
136
  const value = (0, import_react2.useMemo)(
112
- () => ({ flopay, elements, billingApiUrl: resolvedBillingApiUrl }),
113
- [flopay, elements, resolvedBillingApiUrl]
137
+ () => ({ flopay, paypalFlopay, elements, billingApiUrl: resolvedBillingApiUrl }),
138
+ [flopay, paypalFlopay, elements, resolvedBillingApiUrl]
114
139
  );
115
140
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FloPayContext.Provider, { value, children });
116
141
  }
@@ -259,6 +284,10 @@ function useFloPay() {
259
284
  const ctx = (0, import_react5.useContext)(FloPayContext);
260
285
  return ctx.flopay;
261
286
  }
287
+ function usePayPalFloPay() {
288
+ const ctx = (0, import_react5.useContext)(FloPayContext);
289
+ return ctx.paypalFlopay ?? null;
290
+ }
262
291
  function useElements() {
263
292
  const ctx = (0, import_react5.useContext)(FloPayContext);
264
293
  return ctx.elements;
@@ -947,6 +976,7 @@ function SplitCardFormInner({
947
976
  innerRef
948
977
  }) {
949
978
  const flopay = useFloPay();
979
+ const paypalFlopay = usePayPalFloPay();
950
980
  const elements = useElements();
951
981
  const checkout = (0, import_react6.useContext)(CheckoutContext);
952
982
  const contextBillingUrl = useBillingApiUrl();
@@ -1010,6 +1040,10 @@ function SplitCardFormInner({
1010
1040
  if (!flopay) return null;
1011
1041
  return flopay.getRawProvider();
1012
1042
  }, [flopay]);
1043
+ const paypalStripeInstance = (0, import_react6.useMemo)(() => {
1044
+ if (!paypalFlopay) return null;
1045
+ return paypalFlopay.getRawProvider();
1046
+ }, [paypalFlopay]);
1013
1047
  const amountInCents = totalAmount || 100;
1014
1048
  const walletOptions = (0, import_react6.useMemo)(() => ({
1015
1049
  mode: "payment",
@@ -1179,9 +1213,9 @@ function SplitCardFormInner({
1179
1213
  return;
1180
1214
  }
1181
1215
  try {
1182
- const stripeInstance2 = flopay.getRawProvider();
1183
- if (!stripeInstance2) {
1184
- updateError("Payment provider not available.");
1216
+ const paypalStripe = paypalFlopay?.getRawProvider();
1217
+ if (!paypalStripe) {
1218
+ updateError("PayPal is not available.");
1185
1219
  return;
1186
1220
  }
1187
1221
  const confirmParams = {
@@ -1190,7 +1224,7 @@ function SplitCardFormInner({
1190
1224
  if (savedPmId) {
1191
1225
  confirmParams["payment_method"] = savedPmId;
1192
1226
  }
1193
- const { error: confirmError } = await stripeInstance2.confirmPayment({
1227
+ const { error: confirmError } = await paypalStripe.confirmPayment({
1194
1228
  clientSecret: secret,
1195
1229
  confirmParams,
1196
1230
  redirect: "if_required"
@@ -1227,7 +1261,7 @@ function SplitCardFormInner({
1227
1261
  processingRef.current = false;
1228
1262
  }
1229
1263
  },
1230
- [baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, onComplete, onError, updateError, emitDecline]
1264
+ [baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, paypalFlopay, onComplete, onError, updateError, emitDecline]
1231
1265
  );
1232
1266
  const dispatchTokenizedBody = (0, import_react6.useCallback)(
1233
1267
  (tokenizedBody, overrides) => {
@@ -1677,7 +1711,7 @@ function SplitCardFormInner({
1677
1711
  ...!isButtonsView && !buttonsAnim ? { visibility: "hidden", position: "absolute", pointerEvents: "none", width: "100%" } : {},
1678
1712
  ...buttonsAnim ? { animation: buttonsAnim, pointerEvents: "none" } : {}
1679
1713
  }, children: [
1680
- showPayPal && stripeInstance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1714
+ showPayPal && paypalStripeInstance && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: paypalStripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1681
1715
  PayPalButtonInner,
1682
1716
  {
1683
1717
  sessionId,
@@ -1690,7 +1724,7 @@ function SplitCardFormInner({
1690
1724
  onDecline,
1691
1725
  runBeforeButtonClick
1692
1726
  }
1693
- ) }) : showPayPal ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1727
+ ) }),
1694
1728
  showWallets && stripeInstance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1695
1729
  WalletButtonInner,
1696
1730
  {
@@ -1789,7 +1823,7 @@ function SplitCardFormInner({
1789
1823
  onDecline
1790
1824
  }
1791
1825
  ) }),
1792
- showPayPal && stripeInstance && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1826
+ showPayPal && paypalStripeInstance && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: paypalStripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1793
1827
  PayPalButtonInner,
1794
1828
  {
1795
1829
  sessionId,
@@ -1801,7 +1835,7 @@ function SplitCardFormInner({
1801
1835
  onDecline
1802
1836
  }
1803
1837
  ) }),
1804
- (showWallets && stripeInstance || showPayPal && stripeInstance) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
1838
+ (showWallets && stripeInstance || showPayPal && paypalStripeInstance) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
1805
1839
  display: "flex",
1806
1840
  alignItems: "center",
1807
1841
  gap: "0.75rem",
@@ -1863,9 +1897,13 @@ function FloPayCheckout({
1863
1897
  onSessionCompleted
1864
1898
  }) {
1865
1899
  const resolvedBillingUrl = (0, import_shared6.resolveBillingApiUrl)(billingApiUrl);
1900
+ const checkoutType = createSessionParams ? "embedded_checkout" : "standard_checkout";
1901
+ const checkoutLayout = children ? "custom_layout" : layout === "buttons" ? "buttons_layout" : "default_layout";
1866
1902
  const [unified, setUnified] = (0, import_react7.useState)(null);
1867
1903
  const [flopay, setFloPay] = (0, import_react7.useState)(null);
1868
1904
  const flopayRef = (0, import_react7.useRef)(null);
1905
+ const [paypalFlopay, setPaypalFloPay] = (0, import_react7.useState)(null);
1906
+ const paypalFlopayRef = (0, import_react7.useRef)(null);
1869
1907
  const [session, setSession] = (0, import_react7.useState)(null);
1870
1908
  const [resolvedSessionId, setResolvedSessionId] = (0, import_react7.useState)(sessionIdProp ?? "");
1871
1909
  const [isLoading, setIsLoading] = (0, import_react7.useState)(true);
@@ -1885,6 +1923,14 @@ function FloPayCheckout({
1885
1923
  onDeclineRef.current = onDecline;
1886
1924
  const onSessionCompletedRef = (0, import_react7.useRef)(onSessionCompleted);
1887
1925
  onSessionCompletedRef.current = onSessionCompleted;
1926
+ (0, import_react7.useEffect)(() => {
1927
+ console.info("[FloPay] Checkout initialized", {
1928
+ sdk_version: import_shared6.SDK_VERSION,
1929
+ checkout_type: checkoutType,
1930
+ checkout_layout: checkoutLayout,
1931
+ billing_api_url: resolvedBillingUrl
1932
+ });
1933
+ }, [checkoutLayout, checkoutType, resolvedBillingUrl]);
1888
1934
  const baseCreateSessionHash = (0, import_react7.useMemo)(
1889
1935
  () => createSessionParams ? hashCreateParams(createSessionParams) : "",
1890
1936
  [createSessionParams]
@@ -1983,13 +2029,18 @@ function FloPayCheckout({
1983
2029
  return false;
1984
2030
  }
1985
2031
  if (redirectResult.type === "paypal_redirect_required") {
2032
+ const paypalStripe = paypalFlopayRef.current?.getRawProvider();
2033
+ if (!paypalStripe) {
2034
+ setModeError("PayPal is not available.");
2035
+ return false;
2036
+ }
1986
2037
  const confirmParams = {
1987
2038
  return_url: window.location.href
1988
2039
  };
1989
2040
  if (redirectResult.paymentMethodId) {
1990
2041
  confirmParams["payment_method"] = redirectResult.paymentMethodId;
1991
2042
  }
1992
- const { error } = await stripe.confirmPayment({
2043
+ const { error } = await paypalStripe.confirmPayment({
1993
2044
  clientSecret: redirectResult.threeDSecureToken,
1994
2045
  confirmParams,
1995
2046
  redirect: "if_required"
@@ -2098,8 +2149,10 @@ function FloPayCheckout({
2098
2149
  setResolvedSessionId(sid);
2099
2150
  }
2100
2151
  let publishableKey;
2152
+ let paypalPublishableKey;
2101
2153
  if (realResult.provider === "stripe") {
2102
2154
  publishableKey = realResult.data.stripe?.publishableKey;
2155
+ paypalPublishableKey = realResult.data.stripe?.paypalPublishableKey;
2103
2156
  }
2104
2157
  if (!publishableKey) publishableKey = fallbackPublishableKey;
2105
2158
  if (!publishableKey) {
@@ -2108,12 +2161,30 @@ function FloPayCheckout({
2108
2161
  "validation_error"
2109
2162
  );
2110
2163
  }
2111
- const instance = await (0, import_js.loadFloPay)(publishableKey, {
2112
- billingApiUrl: resolvedBillingUrl,
2113
- locale
2114
- });
2164
+ const needsSeparatePaypal = Boolean(paypalPublishableKey) && paypalPublishableKey !== publishableKey;
2165
+ const [instance, paypalInstanceOrError] = await Promise.all([
2166
+ (0, import_js.loadFloPay)(publishableKey, {
2167
+ billingApiUrl: resolvedBillingUrl,
2168
+ locale
2169
+ }),
2170
+ needsSeparatePaypal ? (0, import_js.loadFloPay)(paypalPublishableKey, {
2171
+ billingApiUrl: resolvedBillingUrl,
2172
+ locale
2173
+ }).catch((err) => {
2174
+ console.warn("[FloPay] Failed to load PayPal Stripe instance:", err);
2175
+ return null;
2176
+ }) : Promise.resolve(null)
2177
+ ]);
2115
2178
  flopayRef.current = instance;
2116
2179
  setFloPay(instance);
2180
+ if (paypalPublishableKey) {
2181
+ const paypalInstance = needsSeparatePaypal ? paypalInstanceOrError : instance;
2182
+ paypalFlopayRef.current = paypalInstance;
2183
+ setPaypalFloPay(paypalInstance);
2184
+ } else {
2185
+ paypalFlopayRef.current = null;
2186
+ setPaypalFloPay(null);
2187
+ }
2117
2188
  setIsLoading(false);
2118
2189
  } catch (err) {
2119
2190
  if (initializedHashRef.current === cacheKey) {
@@ -2226,8 +2297,10 @@ function FloPayCheckout({
2226
2297
  }
2227
2298
  async function initStripe(result) {
2228
2299
  let publishableKey;
2300
+ let paypalPublishableKey;
2229
2301
  if (result.provider === "stripe") {
2230
2302
  publishableKey = result.data.stripe?.publishableKey;
2303
+ paypalPublishableKey = result.data.stripe?.paypalPublishableKey;
2231
2304
  }
2232
2305
  if (!publishableKey) publishableKey = fallbackPublishableKey;
2233
2306
  if (!publishableKey) {
@@ -2236,12 +2309,30 @@ function FloPayCheckout({
2236
2309
  "validation_error"
2237
2310
  );
2238
2311
  }
2239
- const instance = await (0, import_js.loadFloPay)(publishableKey, {
2240
- billingApiUrl: resolvedBillingUrl,
2241
- locale
2242
- });
2312
+ const needsSeparatePaypal = Boolean(paypalPublishableKey) && paypalPublishableKey !== publishableKey;
2313
+ const [instance, paypalInstanceOrError] = await Promise.all([
2314
+ (0, import_js.loadFloPay)(publishableKey, {
2315
+ billingApiUrl: resolvedBillingUrl,
2316
+ locale
2317
+ }),
2318
+ needsSeparatePaypal ? (0, import_js.loadFloPay)(paypalPublishableKey, {
2319
+ billingApiUrl: resolvedBillingUrl,
2320
+ locale
2321
+ }).catch((err) => {
2322
+ console.warn("[FloPay] Failed to load PayPal Stripe instance:", err);
2323
+ return null;
2324
+ }) : Promise.resolve(null)
2325
+ ]);
2243
2326
  flopayRef.current = instance;
2244
2327
  setFloPay(instance);
2328
+ if (paypalPublishableKey) {
2329
+ const paypalInstance = needsSeparatePaypal ? paypalInstanceOrError : instance;
2330
+ paypalFlopayRef.current = paypalInstance;
2331
+ setPaypalFloPay(paypalInstance);
2332
+ } else {
2333
+ paypalFlopayRef.current = null;
2334
+ setPaypalFloPay(null);
2335
+ }
2245
2336
  }
2246
2337
  init();
2247
2338
  return () => {
@@ -2376,7 +2467,7 @@ function FloPayCheckout({
2376
2467
  }
2377
2468
  if (!flopay || !providerOptions) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, {});
2378
2469
  if (currentMode === "confirm") {
2379
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FloPayProvider, { flopay, options: providerOptions, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className, children: [
2470
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(FloPayProvider, { flopay, paypalFlopay, options: providerOptions, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className, children: [
2380
2471
  modeError && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2381
2472
  "div",
2382
2473
  {
@@ -3208,6 +3299,7 @@ function PayPalButton({
3208
3299
  SplitCardForm,
3209
3300
  useCheckout,
3210
3301
  useElements,
3211
- useFloPay
3302
+ useFloPay,
3303
+ usePayPalFloPay
3212
3304
  });
3213
3305
  //# sourceMappingURL=index.cjs.map