@flopay/react 0.3.20 → 0.4.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/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",
@@ -1866,6 +1900,8 @@ function FloPayCheckout({
1866
1900
  const [unified, setUnified] = (0, import_react7.useState)(null);
1867
1901
  const [flopay, setFloPay] = (0, import_react7.useState)(null);
1868
1902
  const flopayRef = (0, import_react7.useRef)(null);
1903
+ const [paypalFlopay, setPaypalFloPay] = (0, import_react7.useState)(null);
1904
+ const paypalFlopayRef = (0, import_react7.useRef)(null);
1869
1905
  const [session, setSession] = (0, import_react7.useState)(null);
1870
1906
  const [resolvedSessionId, setResolvedSessionId] = (0, import_react7.useState)(sessionIdProp ?? "");
1871
1907
  const [isLoading, setIsLoading] = (0, import_react7.useState)(true);
@@ -1983,13 +2019,18 @@ function FloPayCheckout({
1983
2019
  return false;
1984
2020
  }
1985
2021
  if (redirectResult.type === "paypal_redirect_required") {
2022
+ const paypalStripe = paypalFlopayRef.current?.getRawProvider();
2023
+ if (!paypalStripe) {
2024
+ setModeError("PayPal is not available.");
2025
+ return false;
2026
+ }
1986
2027
  const confirmParams = {
1987
2028
  return_url: window.location.href
1988
2029
  };
1989
2030
  if (redirectResult.paymentMethodId) {
1990
2031
  confirmParams["payment_method"] = redirectResult.paymentMethodId;
1991
2032
  }
1992
- const { error } = await stripe.confirmPayment({
2033
+ const { error } = await paypalStripe.confirmPayment({
1993
2034
  clientSecret: redirectResult.threeDSecureToken,
1994
2035
  confirmParams,
1995
2036
  redirect: "if_required"
@@ -2098,8 +2139,10 @@ function FloPayCheckout({
2098
2139
  setResolvedSessionId(sid);
2099
2140
  }
2100
2141
  let publishableKey;
2142
+ let paypalPublishableKey;
2101
2143
  if (realResult.provider === "stripe") {
2102
2144
  publishableKey = realResult.data.stripe?.publishableKey;
2145
+ paypalPublishableKey = realResult.data.stripe?.paypalPublishableKey;
2103
2146
  }
2104
2147
  if (!publishableKey) publishableKey = fallbackPublishableKey;
2105
2148
  if (!publishableKey) {
@@ -2108,12 +2151,30 @@ function FloPayCheckout({
2108
2151
  "validation_error"
2109
2152
  );
2110
2153
  }
2111
- const instance = await (0, import_js.loadFloPay)(publishableKey, {
2112
- billingApiUrl: resolvedBillingUrl,
2113
- locale
2114
- });
2154
+ const needsSeparatePaypal = Boolean(paypalPublishableKey) && paypalPublishableKey !== publishableKey;
2155
+ const [instance, paypalInstanceOrError] = await Promise.all([
2156
+ (0, import_js.loadFloPay)(publishableKey, {
2157
+ billingApiUrl: resolvedBillingUrl,
2158
+ locale
2159
+ }),
2160
+ needsSeparatePaypal ? (0, import_js.loadFloPay)(paypalPublishableKey, {
2161
+ billingApiUrl: resolvedBillingUrl,
2162
+ locale
2163
+ }).catch((err) => {
2164
+ console.warn("[FloPay] Failed to load PayPal Stripe instance:", err);
2165
+ return null;
2166
+ }) : Promise.resolve(null)
2167
+ ]);
2115
2168
  flopayRef.current = instance;
2116
2169
  setFloPay(instance);
2170
+ if (paypalPublishableKey) {
2171
+ const paypalInstance = needsSeparatePaypal ? paypalInstanceOrError : instance;
2172
+ paypalFlopayRef.current = paypalInstance;
2173
+ setPaypalFloPay(paypalInstance);
2174
+ } else {
2175
+ paypalFlopayRef.current = null;
2176
+ setPaypalFloPay(null);
2177
+ }
2117
2178
  setIsLoading(false);
2118
2179
  } catch (err) {
2119
2180
  if (initializedHashRef.current === cacheKey) {
@@ -2226,8 +2287,10 @@ function FloPayCheckout({
2226
2287
  }
2227
2288
  async function initStripe(result) {
2228
2289
  let publishableKey;
2290
+ let paypalPublishableKey;
2229
2291
  if (result.provider === "stripe") {
2230
2292
  publishableKey = result.data.stripe?.publishableKey;
2293
+ paypalPublishableKey = result.data.stripe?.paypalPublishableKey;
2231
2294
  }
2232
2295
  if (!publishableKey) publishableKey = fallbackPublishableKey;
2233
2296
  if (!publishableKey) {
@@ -2236,12 +2299,30 @@ function FloPayCheckout({
2236
2299
  "validation_error"
2237
2300
  );
2238
2301
  }
2239
- const instance = await (0, import_js.loadFloPay)(publishableKey, {
2240
- billingApiUrl: resolvedBillingUrl,
2241
- locale
2242
- });
2302
+ const needsSeparatePaypal = Boolean(paypalPublishableKey) && paypalPublishableKey !== publishableKey;
2303
+ const [instance, paypalInstanceOrError] = await Promise.all([
2304
+ (0, import_js.loadFloPay)(publishableKey, {
2305
+ billingApiUrl: resolvedBillingUrl,
2306
+ locale
2307
+ }),
2308
+ needsSeparatePaypal ? (0, import_js.loadFloPay)(paypalPublishableKey, {
2309
+ billingApiUrl: resolvedBillingUrl,
2310
+ locale
2311
+ }).catch((err) => {
2312
+ console.warn("[FloPay] Failed to load PayPal Stripe instance:", err);
2313
+ return null;
2314
+ }) : Promise.resolve(null)
2315
+ ]);
2243
2316
  flopayRef.current = instance;
2244
2317
  setFloPay(instance);
2318
+ if (paypalPublishableKey) {
2319
+ const paypalInstance = needsSeparatePaypal ? paypalInstanceOrError : instance;
2320
+ paypalFlopayRef.current = paypalInstance;
2321
+ setPaypalFloPay(paypalInstance);
2322
+ } else {
2323
+ paypalFlopayRef.current = null;
2324
+ setPaypalFloPay(null);
2325
+ }
2245
2326
  }
2246
2327
  init();
2247
2328
  return () => {
@@ -2376,7 +2457,7 @@ function FloPayCheckout({
2376
2457
  }
2377
2458
  if (!flopay || !providerOptions) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, {});
2378
2459
  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: [
2460
+ 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
2461
  modeError && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2381
2462
  "div",
2382
2463
  {
@@ -3208,6 +3289,7 @@ function PayPalButton({
3208
3289
  SplitCardForm,
3209
3290
  useCheckout,
3210
3291
  useElements,
3211
- useFloPay
3292
+ useFloPay,
3293
+ usePayPalFloPay
3212
3294
  });
3213
3295
  //# sourceMappingURL=index.cjs.map