@flopay/react 0.4.7 → 0.4.9

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
@@ -75,13 +75,22 @@ function FloPayProvider({
75
75
  clientSecret: options?.clientSecret,
76
76
  amount: options?.amount,
77
77
  currency: options?.currency,
78
- paymentMethodCreation: options?.paymentMethodCreation
78
+ paymentMethodCreation: options?.paymentMethodCreation,
79
+ setupFutureUsage: options?.setupFutureUsage
79
80
  });
80
81
  setElements(els);
81
82
  return () => {
82
83
  els.destroy();
83
84
  };
84
- }, [flopay, options?.appearance, options?.clientSecret, options?.amount, options?.currency]);
85
+ }, [
86
+ flopay,
87
+ options?.appearance,
88
+ options?.clientSecret,
89
+ options?.amount,
90
+ options?.currency,
91
+ options?.paymentMethodCreation,
92
+ options?.setupFutureUsage
93
+ ]);
85
94
  const resolvedBillingApiUrl = resolveBillingApiUrl(options?.billingApiUrl);
86
95
  const value = useMemo(
87
96
  () => ({ flopay, paypalFlopay, elements, billingApiUrl: resolvedBillingApiUrl }),
@@ -504,6 +513,15 @@ function buildDeclineEvent(method, input, overrides) {
504
513
  ...declineCode ? { declineCode } : {}
505
514
  };
506
515
  }
516
+ function mapPayPalIntentStatusToPaymentResult(status) {
517
+ if (status === "succeeded") {
518
+ return "succeeded";
519
+ }
520
+ if (status === "processing" || status === "requires_capture") {
521
+ return "processing";
522
+ }
523
+ return "failed";
524
+ }
507
525
  function resolveTokenizedPaymentMethodId(tokenizedBody) {
508
526
  const candidates = [
509
527
  tokenizedBody?.id,
@@ -731,7 +749,10 @@ function PayPalButtonInner({
731
749
  const createPM = stripe.createPaymentMethod;
732
750
  const { error: pmError, paymentMethod } = await createPM({ type: "paypal" });
733
751
  if (pmError) {
734
- console.warn("[FloPay] Could not create PayPal PM upfront:", pmError.message);
752
+ const message = pmError.message ?? "PayPal payment failed.";
753
+ onErrorChange?.(message);
754
+ event.paymentFailed({ reason: "fail", message });
755
+ return;
735
756
  }
736
757
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
737
758
  method: "POST",
@@ -740,7 +761,9 @@ function PayPalButtonInner({
740
761
  sessionId: effectiveSessionId,
741
762
  email: effectiveEmail,
742
763
  paymentMethodType: paymentMethod?.id ?? "paypal",
743
- isPaypal: "true"
764
+ isPaypal: "true",
765
+ setupFutureUsage: "off_session",
766
+ setup_future_usage: "off_session"
744
767
  })
745
768
  });
746
769
  if (!intentResponse.ok) throw new Error("Failed to create payment intent");
@@ -776,6 +799,7 @@ function PayPalButtonInner({
776
799
  accountPatch: prepared?.accountPatch,
777
800
  sessionId: effectiveSessionId
778
801
  });
802
+ return;
779
803
  } catch (err) {
780
804
  onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
781
805
  } finally {
@@ -796,6 +820,9 @@ function PayPalButtonInner({
796
820
  },
797
821
  options: {
798
822
  buttonType: { paypal: "paypal" },
823
+ billingAddressRequired: false,
824
+ phoneNumberRequired: false,
825
+ shippingAddressRequired: false,
799
826
  paymentMethods: {
800
827
  applePay: "never",
801
828
  googlePay: "never",
@@ -1075,7 +1102,8 @@ function SplitCardFormInner({
1075
1102
  mode: "payment",
1076
1103
  amount: amountInCents,
1077
1104
  currency: currency.toLowerCase(),
1078
- captureMethod: "manual"
1105
+ captureMethod: "manual",
1106
+ setupFutureUsage: "off_session"
1079
1107
  }), [amountInCents, currency]);
1080
1108
  const updateError = useCallback(
1081
1109
  (err) => {
@@ -1160,6 +1188,8 @@ function SplitCardFormInner({
1160
1188
  updateError(null);
1161
1189
  const effectiveSessionId = overrides?.sessionId ?? sessionId;
1162
1190
  const effectiveAccount = mergeAccountPatch(resolvedAccount, overrides?.accountPatch);
1191
+ const resolvedCompletionPaymentMethodId = overrides?.completionPaymentMethodId ?? resolveTokenizedPaymentMethodId(tokenizedBody);
1192
+ const requestTokenizedBody = tokenizedBody.originalPaymentMethodId ? { ...tokenizedBody, originalPaymentMethodId: void 0 } : tokenizedBody;
1163
1193
  try {
1164
1194
  const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
1165
1195
  method: "POST",
@@ -1169,7 +1199,7 @@ function SplitCardFormInner({
1169
1199
  },
1170
1200
  body: JSON.stringify({
1171
1201
  sessionId: effectiveSessionId,
1172
- tokenizedData: tokenizedBody,
1202
+ tokenizedData: requestTokenizedBody,
1173
1203
  accountData: {
1174
1204
  userId: effectiveAccount.userId ?? "",
1175
1205
  email: effectiveAccount.email ?? "",
@@ -1186,7 +1216,8 @@ function SplitCardFormInner({
1186
1216
  onComplete?.({
1187
1217
  status: "succeeded",
1188
1218
  paymentIntentId: tokenizedBody.threeDSecureActionResultTokenId,
1189
- paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
1219
+ paymentMethodId: resolvedCompletionPaymentMethodId,
1220
+ checkoutMethod: tokenizedBody.isPaypal ? "paypal" : "card"
1190
1221
  });
1191
1222
  return;
1192
1223
  }
@@ -1200,9 +1231,22 @@ function SplitCardFormInner({
1200
1231
  }
1201
1232
  setIs3DSActive(true);
1202
1233
  try {
1234
+ const retryBillingAddress = {
1235
+ ...(enableAVS ? selectedCountryRef.current : effectiveAccount.country) ? {
1236
+ country: enableAVS ? selectedCountryRef.current : effectiveAccount.country
1237
+ } : {},
1238
+ ...(enableAVS ? zipCodeRef.current.trim() : effectiveAccount.zip) || "" ? {
1239
+ postal_code: enableAVS ? zipCodeRef.current.trim() : effectiveAccount.zip
1240
+ } : {}
1241
+ };
1203
1242
  const result = await flopay.confirmPayment({
1204
1243
  clientSecret: secret,
1205
- returnUrl: window.location.href
1244
+ returnUrl: window.location.href,
1245
+ billingDetails: {
1246
+ ...effectiveAccount.email ? { email: effectiveAccount.email } : {},
1247
+ ...fullName.trim() ? { name: fullName.trim() } : {},
1248
+ ...Object.keys(retryBillingAddress).length > 0 ? { address: retryBillingAddress } : {}
1249
+ }
1206
1250
  });
1207
1251
  if (result.error) {
1208
1252
  setOverlayStatus("error");
@@ -1216,8 +1260,9 @@ function SplitCardFormInner({
1216
1260
  await processPaymentInternal({
1217
1261
  id: result.paymentIntentId,
1218
1262
  type: "card",
1219
- threeDSecureActionResultTokenId: result.paymentIntentId,
1220
- originalPaymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
1263
+ threeDSecureActionResultTokenId: result.paymentIntentId
1264
+ }, {
1265
+ completionPaymentMethodId: resolvedCompletionPaymentMethodId
1221
1266
  });
1222
1267
  }
1223
1268
  } finally {
@@ -2042,7 +2087,8 @@ async function processSavedPaymentForMode({
2042
2087
  result: {
2043
2088
  status: "succeeded",
2044
2089
  paymentIntentId: tokenizedData?.threeDSecureActionResultTokenId,
2045
- paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedData)
2090
+ paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedData),
2091
+ checkoutMethod: tokenizedData ? tokenizedData.isPaypal ? "paypal" : "card" : void 0
2046
2092
  }
2047
2093
  };
2048
2094
  }
@@ -2161,8 +2207,7 @@ async function processSavedPaymentWithIntent({
2161
2207
  tokenizedData: {
2162
2208
  id: paymentMethodId,
2163
2209
  type: "card",
2164
- threeDSecureActionResultTokenId: confirmResult.paymentIntentId,
2165
- originalPaymentMethodId: paymentMethodId
2210
+ threeDSecureActionResultTokenId: confirmResult.paymentIntentId
2166
2211
  },
2167
2212
  returnUrl
2168
2213
  });
@@ -2178,7 +2223,8 @@ async function processSavedPaymentWithIntent({
2178
2223
  return {
2179
2224
  ...finalResult,
2180
2225
  paymentIntentId: finalResult.paymentIntentId ?? confirmResult.paymentIntentId,
2181
- paymentMethodId: finalResult.paymentMethodId ?? paymentMethodId
2226
+ paymentMethodId: finalResult.paymentMethodId ?? paymentMethodId,
2227
+ checkoutMethod: finalResult.checkoutMethod ?? "card"
2182
2228
  };
2183
2229
  }
2184
2230
  async function handleSavedPaymentRedirectResult(redirectResult, {
@@ -2272,8 +2318,7 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
2272
2318
  tokenizedData: {
2273
2319
  id: paymentIntent.id,
2274
2320
  type: "card",
2275
- threeDSecureActionResultTokenId: paymentIntent.id,
2276
- originalPaymentMethodId: savedPaymentMethodId
2321
+ threeDSecureActionResultTokenId: paymentIntent.id
2277
2322
  },
2278
2323
  returnUrl
2279
2324
  });
@@ -2328,7 +2373,10 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
2328
2373
  { checkoutMethod: "paypal" }
2329
2374
  );
2330
2375
  }
2331
- return { status: "succeeded" };
2376
+ return {
2377
+ status: "succeeded",
2378
+ checkoutMethod: "paypal"
2379
+ };
2332
2380
  }
2333
2381
  throw new FloPayError3("Unsupported payment redirect state.", "api_error");
2334
2382
  }
@@ -2341,19 +2389,16 @@ function normalizeSavedPaymentError(err) {
2341
2389
  "api_error"
2342
2390
  );
2343
2391
  }
2344
- function resolveSavedPaymentPublishableKeys(unified, fallbackPublishableKey) {
2392
+ function resolveSavedPaymentPublishableKeys(unified) {
2345
2393
  let publishableKey;
2346
2394
  let paypalPublishableKey;
2347
2395
  if (unified.provider === "stripe") {
2348
2396
  publishableKey = unified.data.stripe?.publishableKey;
2349
2397
  paypalPublishableKey = unified.data.stripe?.paypalPublishableKey ?? void 0;
2350
2398
  }
2351
- if (!publishableKey) {
2352
- publishableKey = fallbackPublishableKey;
2353
- }
2354
2399
  if (!publishableKey) {
2355
2400
  throw new FloPayError3(
2356
- "No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
2401
+ "No publishable key found in the checkout session. Ensure the session includes gatewayData.publishableKey.",
2357
2402
  "validation_error"
2358
2403
  );
2359
2404
  }
@@ -2391,9 +2436,60 @@ async function loadSavedPaymentProviders({
2391
2436
  // src/flopay-checkout.tsx
2392
2437
  import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2393
2438
  var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2 = 44;
2439
+ var PAYPAL_RESUME_STORAGE_KEY = "flopay_checkout_saved_payment_resume";
2394
2440
  function sleep(ms) {
2395
2441
  return new Promise((resolve) => setTimeout(resolve, ms));
2396
2442
  }
2443
+ function canUseStorage() {
2444
+ return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
2445
+ }
2446
+ function readPayPalResumeState() {
2447
+ if (!canUseStorage()) return null;
2448
+ try {
2449
+ const raw = window.sessionStorage.getItem(PAYPAL_RESUME_STORAGE_KEY);
2450
+ if (!raw) return null;
2451
+ return JSON.parse(raw);
2452
+ } catch {
2453
+ return null;
2454
+ }
2455
+ }
2456
+ function persistPayPalResumeState(state) {
2457
+ if (!canUseStorage()) return;
2458
+ try {
2459
+ window.sessionStorage.setItem(PAYPAL_RESUME_STORAGE_KEY, JSON.stringify(state));
2460
+ } catch (error) {
2461
+ console.warn("[FloPayCheckout] Failed to persist PayPal resume state.", error);
2462
+ }
2463
+ }
2464
+ function clearPayPalResumeState() {
2465
+ if (!canUseStorage()) return;
2466
+ try {
2467
+ window.sessionStorage.removeItem(PAYPAL_RESUME_STORAGE_KEY);
2468
+ } catch (error) {
2469
+ console.warn("[FloPayCheckout] Failed to clear PayPal resume state.", error);
2470
+ }
2471
+ }
2472
+ function clearPayPalRedirectParams() {
2473
+ if (typeof window === "undefined") return;
2474
+ const url = new URL(window.location.href);
2475
+ const keys = ["payment_intent", "payment_intent_client_secret", "redirect_status"];
2476
+ let changed = false;
2477
+ for (const key of keys) {
2478
+ if (url.searchParams.has(key)) {
2479
+ url.searchParams.delete(key);
2480
+ changed = true;
2481
+ }
2482
+ }
2483
+ if (changed) {
2484
+ window.history.replaceState(window.history.state, "", url.toString());
2485
+ }
2486
+ }
2487
+ function replaceCheckoutModeQueryParam(mode) {
2488
+ if (typeof window === "undefined") return;
2489
+ const url = new URL(window.location.href);
2490
+ url.searchParams.set("mode", mode);
2491
+ window.history.replaceState(window.history.state, "", url.toString());
2492
+ }
2397
2493
  function hasInlineSessionPatchData(patch) {
2398
2494
  if (!patch) return false;
2399
2495
  return Boolean(
@@ -2406,7 +2502,6 @@ function FloPayCheckout({
2406
2502
  billingApiUrl,
2407
2503
  appearance,
2408
2504
  locale,
2409
- fallbackPublishableKey,
2410
2505
  loading: loadingNode,
2411
2506
  error: errorNode,
2412
2507
  onComplete,
@@ -2461,6 +2556,8 @@ function FloPayCheckout({
2461
2556
  const [createSessionPatchBaseHash, setCreateSessionPatchBaseHash] = useState3("");
2462
2557
  const [cardBootstrapPending, setCardBootstrapPending] = useState3(false);
2463
2558
  const autoCheckoutAttempted = useRef3(false);
2559
+ const paypalResumeAttempted = useRef3(false);
2560
+ const savedPaymentKeysRef = useRef3(null);
2464
2561
  const onCompleteRef = useRef3(onComplete);
2465
2562
  onCompleteRef.current = onComplete;
2466
2563
  const onErrorRef = useRef3(onError);
@@ -2520,6 +2617,13 @@ function FloPayCheckout({
2520
2617
  const redirectResult = getRedirectResultFromCheckoutProcessError(options?.initialAutoProcessingError);
2521
2618
  let paymentResult;
2522
2619
  if (redirectResult) {
2620
+ if (redirectResult.type === "paypal_redirect_required" && savedPaymentKeysRef.current) {
2621
+ persistPayPalResumeState({
2622
+ sessionId: activeSessionId2,
2623
+ publishableKey: savedPaymentKeysRef.current.publishableKey,
2624
+ paypalPublishableKey: savedPaymentKeysRef.current.paypalPublishableKey
2625
+ });
2626
+ }
2523
2627
  paymentResult = await handleSavedPaymentRedirectResult(redirectResult, {
2524
2628
  flopay: flopayRef.current,
2525
2629
  paypalFlopay: paypalFlopayRef.current,
@@ -2528,6 +2632,9 @@ function FloPayCheckout({
2528
2632
  sessionId: activeSessionId2,
2529
2633
  session: sess
2530
2634
  });
2635
+ if (redirectResult.type === "paypal_redirect_required") {
2636
+ clearPayPalResumeState();
2637
+ }
2531
2638
  } else if (options?.initialAutoProcessingError) {
2532
2639
  throw checkoutProcessErrorToFloPayError(
2533
2640
  options.initialAutoProcessingError,
@@ -2539,6 +2646,7 @@ function FloPayCheckout({
2539
2646
  } else if (options?.fromCreateSession) {
2540
2647
  if (options?.fallbackToFull) {
2541
2648
  setCurrentMode("full");
2649
+ replaceCheckoutModeQueryParam("full");
2542
2650
  }
2543
2651
  return false;
2544
2652
  } else {
@@ -2547,14 +2655,28 @@ function FloPayCheckout({
2547
2655
  sessionId: activeSessionId2,
2548
2656
  session: sess
2549
2657
  });
2550
- paymentResult = result.type === "success" ? result.result : await handleSavedPaymentRedirectResult(result, {
2551
- flopay: flopayRef.current,
2552
- paypalFlopay: paypalFlopayRef.current,
2553
- attempt3DS: options?.attempt3DS,
2554
- billingApiUrl: resolvedBillingUrl,
2555
- sessionId: activeSessionId2,
2556
- session: sess
2557
- });
2658
+ if (result.type === "success") {
2659
+ paymentResult = result.result;
2660
+ } else {
2661
+ if (result.type === "paypal_redirect_required" && savedPaymentKeysRef.current) {
2662
+ persistPayPalResumeState({
2663
+ sessionId: activeSessionId2,
2664
+ publishableKey: savedPaymentKeysRef.current.publishableKey,
2665
+ paypalPublishableKey: savedPaymentKeysRef.current.paypalPublishableKey
2666
+ });
2667
+ }
2668
+ paymentResult = await handleSavedPaymentRedirectResult(result, {
2669
+ flopay: flopayRef.current,
2670
+ paypalFlopay: paypalFlopayRef.current,
2671
+ attempt3DS: options?.attempt3DS,
2672
+ billingApiUrl: resolvedBillingUrl,
2673
+ sessionId: activeSessionId2,
2674
+ session: sess
2675
+ });
2676
+ if (result.type === "paypal_redirect_required") {
2677
+ clearPayPalResumeState();
2678
+ }
2679
+ }
2558
2680
  }
2559
2681
  setModeOverlayStatus("success");
2560
2682
  await sleep(PROCESSING_OVERLAY_SUCCESS_DELAY_MS);
@@ -2567,6 +2689,7 @@ function FloPayCheckout({
2567
2689
  setModeOverlayError(floPayErr.message);
2568
2690
  if (options?.fallbackToFull) {
2569
2691
  setCurrentMode("full");
2692
+ replaceCheckoutModeQueryParam("full");
2570
2693
  }
2571
2694
  onErrorRef.current?.(floPayErr);
2572
2695
  emitDecline(method, floPayErr, {
@@ -2590,6 +2713,96 @@ function FloPayCheckout({
2590
2713
  resolvedBillingUrl
2591
2714
  ]
2592
2715
  );
2716
+ useEffect4(() => {
2717
+ if (typeof window === "undefined" || paypalResumeAttempted.current) {
2718
+ return;
2719
+ }
2720
+ const params = new URLSearchParams(window.location.search);
2721
+ const clientSecret = params.get("payment_intent_client_secret");
2722
+ if (!clientSecret) {
2723
+ return;
2724
+ }
2725
+ const resumeState = readPayPalResumeState();
2726
+ if (!resumeState) {
2727
+ return;
2728
+ }
2729
+ paypalResumeAttempted.current = true;
2730
+ void (async () => {
2731
+ setModeError(null);
2732
+ setModeOverlayError(null);
2733
+ setModeOverlayStatus("processing");
2734
+ setConfirmProcessing(true);
2735
+ try {
2736
+ if (params.get("redirect_status") === "failed") {
2737
+ throw Object.assign(
2738
+ new FloPayError4("PayPal payment was declined. Please try again.", "api_error"),
2739
+ { checkoutMethod: "paypal" }
2740
+ );
2741
+ }
2742
+ const {
2743
+ flopay: resumeFlopay,
2744
+ paypalFlopay: resumePaypalFlopay
2745
+ } = await loadSavedPaymentProviders({
2746
+ publishableKey: resumeState.publishableKey,
2747
+ paypalPublishableKey: resumeState.paypalPublishableKey,
2748
+ billingApiUrl: resolvedBillingUrl,
2749
+ locale
2750
+ });
2751
+ const paypalStripe = (resumePaypalFlopay ?? resumeFlopay).getRawProvider();
2752
+ if (!paypalStripe) {
2753
+ throw Object.assign(
2754
+ new FloPayError4("PayPal is not available.", "api_error"),
2755
+ { checkoutMethod: "paypal" }
2756
+ );
2757
+ }
2758
+ const { paymentIntent, error } = await paypalStripe.retrievePaymentIntent(clientSecret);
2759
+ if (error) {
2760
+ throw Object.assign(
2761
+ new FloPayError4(
2762
+ error.message ?? "Failed to retrieve PayPal payment status.",
2763
+ "api_error",
2764
+ { code: error.code }
2765
+ ),
2766
+ { checkoutMethod: "paypal" }
2767
+ );
2768
+ }
2769
+ const resultStatus = mapPayPalIntentStatusToPaymentResult(paymentIntent?.status);
2770
+ if (!paymentIntent || resultStatus === "failed") {
2771
+ throw Object.assign(
2772
+ new FloPayError4("PayPal payment was not completed. Please try again.", "api_error"),
2773
+ { checkoutMethod: "paypal" }
2774
+ );
2775
+ }
2776
+ const paymentMethodId = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
2777
+ setModeOverlayStatus("success");
2778
+ await sleep(PROCESSING_OVERLAY_SUCCESS_DELAY_MS);
2779
+ onCompleteRef.current?.({
2780
+ status: resultStatus,
2781
+ paymentIntentId: paymentIntent.id,
2782
+ paymentMethodId,
2783
+ checkoutMethod: "paypal"
2784
+ });
2785
+ } catch (err) {
2786
+ const floPayErr = normalizeSavedPaymentError(err);
2787
+ const method = floPayErr.checkoutMethod ?? "paypal";
2788
+ setModeError(floPayErr.message);
2789
+ setModeOverlayError(floPayErr.message);
2790
+ onErrorRef.current?.(floPayErr);
2791
+ emitDecline(method, floPayErr, {
2792
+ code: floPayErr.code,
2793
+ declineCode: floPayErr.declineCode
2794
+ });
2795
+ setModeOverlayStatus("error");
2796
+ await sleep(PROCESSING_OVERLAY_ERROR_DELAY_MS);
2797
+ } finally {
2798
+ clearPayPalResumeState();
2799
+ clearPayPalRedirectParams();
2800
+ setModeOverlayStatus(null);
2801
+ setModeOverlayError(null);
2802
+ setConfirmProcessing(false);
2803
+ }
2804
+ })();
2805
+ }, [emitDecline, locale, normalizeSavedPaymentError, resolvedBillingUrl]);
2593
2806
  const inflightRef = useRef3(/* @__PURE__ */ new Map());
2594
2807
  const initializedHashRef = useRef3(null);
2595
2808
  function hashCreateParams(params) {
@@ -2688,7 +2901,7 @@ function FloPayCheckout({
2688
2901
  const {
2689
2902
  publishableKey,
2690
2903
  paypalPublishableKey
2691
- } = resolveSavedPaymentPublishableKeys(realResult, fallbackPublishableKey);
2904
+ } = resolveSavedPaymentPublishableKeys(realResult);
2692
2905
  const {
2693
2906
  flopay: instance,
2694
2907
  paypalFlopay: paypalInstance
@@ -2711,7 +2924,7 @@ function FloPayCheckout({
2711
2924
  }
2712
2925
  return resolved;
2713
2926
  },
2714
- [fallbackPublishableKey, locale, resolvedBillingUrl]
2927
+ [locale, resolvedBillingUrl]
2715
2928
  );
2716
2929
  const handleInlineSessionPatch = useCallback2(async (patch) => {
2717
2930
  if (!hasInlineSessionPatchData(patch) || cardBootstrapPending) {
@@ -2759,6 +2972,7 @@ function FloPayCheckout({
2759
2972
  try {
2760
2973
  const resolved = await bootstrapInlineSession();
2761
2974
  const resolvedSession = resolved.result.data.session ?? null;
2975
+ savedPaymentKeysRef.current = resolveSavedPaymentPublishableKeys(resolved.result);
2762
2976
  if (!cancelled && shouldAutoProcessInlineSession && resolvedSession) {
2763
2977
  autoCheckoutAttempted.current = true;
2764
2978
  await runSavedPaymentFlow(resolvedSession, {
@@ -2857,7 +3071,11 @@ function FloPayCheckout({
2857
3071
  const {
2858
3072
  publishableKey,
2859
3073
  paypalPublishableKey
2860
- } = resolveSavedPaymentPublishableKeys(result, fallbackPublishableKey);
3074
+ } = resolveSavedPaymentPublishableKeys(result);
3075
+ savedPaymentKeysRef.current = {
3076
+ publishableKey,
3077
+ paypalPublishableKey
3078
+ };
2861
3079
  const {
2862
3080
  flopay: instance,
2863
3081
  paypalFlopay: paypalInstance
@@ -3410,9 +3628,11 @@ function CheckoutFormInner({
3410
3628
  [onDecline]
3411
3629
  );
3412
3630
  const processPaymentInternal = useCallback3(
3413
- async (tokenizedBody) => {
3631
+ async (tokenizedBody, completionPaymentMethodId) => {
3414
3632
  setProcessing(true);
3415
3633
  updateError(null);
3634
+ const resolvedCompletionPaymentMethodId = completionPaymentMethodId ?? resolveTokenizedPaymentMethodId(tokenizedBody);
3635
+ const requestTokenizedBody = tokenizedBody.originalPaymentMethodId ? { ...tokenizedBody, originalPaymentMethodId: void 0 } : tokenizedBody;
3416
3636
  try {
3417
3637
  const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
3418
3638
  method: "POST",
@@ -3422,7 +3642,7 @@ function CheckoutFormInner({
3422
3642
  },
3423
3643
  body: JSON.stringify({
3424
3644
  sessionId,
3425
- tokenizedData: tokenizedBody,
3645
+ tokenizedData: requestTokenizedBody,
3426
3646
  accountData: {
3427
3647
  userId: userId ?? "",
3428
3648
  email: email ?? "",
@@ -3436,7 +3656,8 @@ function CheckoutFormInner({
3436
3656
  onComplete?.({
3437
3657
  status: "succeeded",
3438
3658
  paymentIntentId: tokenizedBody.threeDSecureActionResultTokenId,
3439
- paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
3659
+ paymentMethodId: resolvedCompletionPaymentMethodId,
3660
+ checkoutMethod: tokenizedBody.isPaypal ? "paypal" : "card"
3440
3661
  });
3441
3662
  return;
3442
3663
  }
@@ -3449,9 +3670,14 @@ function CheckoutFormInner({
3449
3670
  }
3450
3671
  setIs3DSActive(true);
3451
3672
  try {
3673
+ const retryFullName = `${firstName ?? ""} ${lastName ?? ""}`.trim();
3452
3674
  const result = await flopay.confirmPayment({
3453
3675
  clientSecret: secret,
3454
- returnUrl: window.location.href
3676
+ returnUrl: window.location.href,
3677
+ billingDetails: {
3678
+ ...email ? { email } : {},
3679
+ ...retryFullName ? { name: retryFullName } : {}
3680
+ }
3455
3681
  });
3456
3682
  if (result.error) {
3457
3683
  updateError(result.error.message);
@@ -3463,9 +3689,8 @@ function CheckoutFormInner({
3463
3689
  await processPaymentInternal({
3464
3690
  id: result.paymentIntentId,
3465
3691
  type: "card",
3466
- threeDSecureActionResultTokenId: result.paymentIntentId,
3467
- originalPaymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
3468
- });
3692
+ threeDSecureActionResultTokenId: result.paymentIntentId
3693
+ }, resolvedCompletionPaymentMethodId);
3469
3694
  }
3470
3695
  } finally {
3471
3696
  setIs3DSActive(false);
@@ -3792,33 +4017,23 @@ function PayPalButton({
3792
4017
  if (!sessionId || !email) {
3793
4018
  throw new FloPayError6("Missing sessionId or email for PayPal payment", "validation_error");
3794
4019
  }
3795
- const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
3796
- method: "POST",
3797
- headers: { "Content-Type": "application/json" },
3798
- body: JSON.stringify({
3799
- sessionId,
3800
- email,
3801
- paymentMethodType: "paypal",
3802
- isPaypal: "true"
3803
- })
3804
- });
3805
- if (!intentResponse.ok) throw new FloPayError6("Failed to create payment intent", "api_error");
3806
- const intentJson = await intentResponse.json();
3807
- const intentClientSecret = intentJson.data?.id;
3808
- if (!intentClientSecret) throw new FloPayError6("No client_secret in response", "api_error");
3809
- const result = await flopay.confirmPayment({
3810
- clientSecret: intentClientSecret,
4020
+ const result = await flopay.confirmPayPalPayment({
4021
+ billingApiUrl: baseUrl,
4022
+ sessionId,
4023
+ email,
3811
4024
  returnUrl: window.location.href
3812
4025
  });
3813
- if (result.status === "succeeded" || result.status === "processing") {
4026
+ if (result.error) {
4027
+ onErrorChange?.(result.error.message);
4028
+ return;
4029
+ }
4030
+ if (result.paymentIntentId && (result.status === "succeeded" || result.status === "processing" || result.status === "requires_capture")) {
3814
4031
  dispatchTokenizedBody({
3815
- id: result.paymentIntentId,
4032
+ id: result.paymentMethodId ?? result.paymentIntentId,
3816
4033
  type: "card",
3817
4034
  threeDSecureActionResultTokenId: result.paymentIntentId,
3818
4035
  isPaypal: true
3819
4036
  });
3820
- } else if (result.error) {
3821
- onErrorChange?.(result.error.message);
3822
4037
  }
3823
4038
  } catch (err) {
3824
4039
  onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
@@ -3878,7 +4093,7 @@ import { PaymentAPI as PaymentAPI3 } from "@flopay/js";
3878
4093
  import { FloPayError as FloPayError7, resolveBillingApiUrl as resolveBillingApiUrl4, resolveButtonsLayoutTheme as resolveButtonsLayoutTheme3 } from "@flopay/shared";
3879
4094
  import { Fragment as Fragment6, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
3880
4095
  var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT3 = 44;
3881
- var PAYPAL_RESUME_STORAGE_KEY = "flopay_automatic_payment_button_resume";
4096
+ var PAYPAL_RESUME_STORAGE_KEY2 = "flopay_automatic_payment_button_resume";
3882
4097
  function sleep2(ms) {
3883
4098
  return new Promise((resolve) => setTimeout(resolve, ms));
3884
4099
  }
@@ -3891,42 +4106,44 @@ function coerceError(err, fallbackMessage) {
3891
4106
  "api_error"
3892
4107
  );
3893
4108
  }
3894
- function canUseStorage() {
4109
+ function canUseStorage2() {
3895
4110
  return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
3896
4111
  }
3897
4112
  function shouldShowFallbackCheckout(error, sessionId) {
3898
4113
  if (!sessionId) return false;
3899
4114
  if (error.type === "validation_error") return false;
3900
- if (error.checkoutMethod && error.checkoutMethod !== "card") return false;
4115
+ if (error.checkoutMethod && error.checkoutMethod !== "card" && error.checkoutMethod !== "paypal") {
4116
+ return false;
4117
+ }
3901
4118
  return true;
3902
4119
  }
3903
- function readPayPalResumeState() {
3904
- if (!canUseStorage()) return null;
4120
+ function readPayPalResumeState2() {
4121
+ if (!canUseStorage2()) return null;
3905
4122
  try {
3906
- const raw = window.sessionStorage.getItem(PAYPAL_RESUME_STORAGE_KEY);
4123
+ const raw = window.sessionStorage.getItem(PAYPAL_RESUME_STORAGE_KEY2);
3907
4124
  if (!raw) return null;
3908
4125
  return JSON.parse(raw);
3909
4126
  } catch {
3910
4127
  return null;
3911
4128
  }
3912
4129
  }
3913
- function persistPayPalResumeState(state) {
3914
- if (!canUseStorage()) return;
4130
+ function persistPayPalResumeState2(state) {
4131
+ if (!canUseStorage2()) return;
3915
4132
  try {
3916
- window.sessionStorage.setItem(PAYPAL_RESUME_STORAGE_KEY, JSON.stringify(state));
4133
+ window.sessionStorage.setItem(PAYPAL_RESUME_STORAGE_KEY2, JSON.stringify(state));
3917
4134
  } catch (error) {
3918
4135
  console.warn("[FloPayAutomaticPaymentButton] Failed to persist PayPal resume state.", error);
3919
4136
  }
3920
4137
  }
3921
- function clearPayPalResumeState() {
3922
- if (!canUseStorage()) return;
4138
+ function clearPayPalResumeState2() {
4139
+ if (!canUseStorage2()) return;
3923
4140
  try {
3924
- window.sessionStorage.removeItem(PAYPAL_RESUME_STORAGE_KEY);
4141
+ window.sessionStorage.removeItem(PAYPAL_RESUME_STORAGE_KEY2);
3925
4142
  } catch (error) {
3926
4143
  console.warn("[FloPayAutomaticPaymentButton] Failed to clear PayPal resume state.", error);
3927
4144
  }
3928
4145
  }
3929
- function clearPayPalRedirectParams() {
4146
+ function clearPayPalRedirectParams2() {
3930
4147
  if (typeof window === "undefined") return;
3931
4148
  const url = new URL(window.location.href);
3932
4149
  const keys = ["payment_intent", "payment_intent_client_secret", "redirect_status"];
@@ -3968,6 +4185,7 @@ function FloPayAutomaticPaymentButton({
3968
4185
  sessionId,
3969
4186
  createSession,
3970
4187
  paymentMethodId,
4188
+ checkoutMethod,
3971
4189
  clientId,
3972
4190
  items,
3973
4191
  subscriptions,
@@ -3979,7 +4197,6 @@ function FloPayAutomaticPaymentButton({
3979
4197
  utmMetadata,
3980
4198
  billingApiUrl,
3981
4199
  locale,
3982
- fallbackPublishableKey,
3983
4200
  buttonsTheme,
3984
4201
  buttonsStyles: stylesOverride,
3985
4202
  onSuccess,
@@ -4028,9 +4245,10 @@ function FloPayAutomaticPaymentButton({
4028
4245
  const automaticPaymentToken = useMemo4(
4029
4246
  () => paymentMethodId ? {
4030
4247
  id: paymentMethodId,
4031
- type: "card"
4248
+ type: "card",
4249
+ ...checkoutMethod === "paypal" ? { isPaypal: true } : {}
4032
4250
  } : void 0,
4033
- [paymentMethodId]
4251
+ [checkoutMethod, paymentMethodId]
4034
4252
  );
4035
4253
  const isMountedRef = useRef5(true);
4036
4254
  const resumeAttemptedRef = useRef5(false);
@@ -4112,13 +4330,14 @@ function FloPayAutomaticPaymentButton({
4112
4330
  let paymentResult = null;
4113
4331
  const redirectResult = getRedirectResultFromCheckoutProcessError(apiResult.autoProcessingError);
4114
4332
  const shouldTreatCreateSessionFlowAsServerAutoAttempt = options?.fromCreateSession && (apiResult.autoProcessingAttempted === true || !!apiResult.autoProcessingError || !!redirectResult);
4333
+ const shouldRetryPayPalClientSide = checkoutMethod === "paypal" && automaticPaymentToken?.isPaypal === true && options?.fromCreateSession === true;
4115
4334
  if (redirectResult) {
4116
4335
  const {
4117
4336
  publishableKey,
4118
4337
  paypalPublishableKey
4119
- } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4338
+ } = resolveSavedPaymentPublishableKeys(apiResult);
4120
4339
  if (redirectResult.type === "paypal_redirect_required") {
4121
- persistPayPalResumeState({
4340
+ persistPayPalResumeState2({
4122
4341
  sessionId: session.id || resolvedSessionId,
4123
4342
  publishableKey,
4124
4343
  paypalPublishableKey
@@ -4142,9 +4361,9 @@ function FloPayAutomaticPaymentButton({
4142
4361
  session
4143
4362
  });
4144
4363
  if (redirectResult.type === "paypal_redirect_required") {
4145
- clearPayPalResumeState();
4364
+ clearPayPalResumeState2();
4146
4365
  }
4147
- } else if (apiResult.autoProcessingError) {
4366
+ } else if (apiResult.autoProcessingError && !shouldRetryPayPalClientSide) {
4148
4367
  throw checkoutProcessErrorToFloPayError(
4149
4368
  apiResult.autoProcessingError,
4150
4369
  "Automatic payment failed. Please try again.",
@@ -4152,7 +4371,7 @@ function FloPayAutomaticPaymentButton({
4152
4371
  checkoutMethod: apiResult.autoProcessingError.checkoutMethod
4153
4372
  }
4154
4373
  );
4155
- } else if (shouldTreatCreateSessionFlowAsServerAutoAttempt) {
4374
+ } else if (shouldTreatCreateSessionFlowAsServerAutoAttempt && !shouldRetryPayPalClientSide) {
4156
4375
  throw checkoutProcessErrorToFloPayError(
4157
4376
  {
4158
4377
  type: "unknown",
@@ -4172,9 +4391,9 @@ function FloPayAutomaticPaymentButton({
4172
4391
  const {
4173
4392
  publishableKey,
4174
4393
  paypalPublishableKey
4175
- } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4394
+ } = resolveSavedPaymentPublishableKeys(apiResult);
4176
4395
  if (result.type === "paypal_redirect_required") {
4177
- persistPayPalResumeState({
4396
+ persistPayPalResumeState2({
4178
4397
  sessionId: session.id || resolvedSessionId,
4179
4398
  publishableKey,
4180
4399
  paypalPublishableKey
@@ -4198,17 +4417,19 @@ function FloPayAutomaticPaymentButton({
4198
4417
  session
4199
4418
  });
4200
4419
  if (result.type === "paypal_redirect_required") {
4201
- clearPayPalResumeState();
4420
+ clearPayPalResumeState2();
4202
4421
  }
4203
4422
  }
4204
4423
  }
4205
4424
  await showSuccess({
4206
4425
  result: paymentResult ? {
4207
4426
  ...paymentResult,
4208
- paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId
4427
+ paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId,
4428
+ checkoutMethod: paymentResult.checkoutMethod ?? checkoutMethod
4209
4429
  } : {
4210
4430
  status: "succeeded",
4211
- paymentMethodId: paymentMethodId ?? void 0
4431
+ paymentMethodId: paymentMethodId ?? void 0,
4432
+ checkoutMethod
4212
4433
  },
4213
4434
  session,
4214
4435
  sessionId: session.id || resolvedSessionId,
@@ -4223,7 +4444,7 @@ function FloPayAutomaticPaymentButton({
4223
4444
  const {
4224
4445
  publishableKey,
4225
4446
  paypalPublishableKey
4226
- } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4447
+ } = resolveSavedPaymentPublishableKeys(apiResult);
4227
4448
  const {
4228
4449
  flopay
4229
4450
  } = await loadSavedPaymentProviders({
@@ -4242,7 +4463,8 @@ function FloPayAutomaticPaymentButton({
4242
4463
  await showSuccess({
4243
4464
  result: {
4244
4465
  ...paymentResult,
4245
- paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId
4466
+ paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId,
4467
+ checkoutMethod: paymentResult.checkoutMethod ?? checkoutMethod
4246
4468
  },
4247
4469
  session,
4248
4470
  sessionId: fallbackSessionId,
@@ -4265,7 +4487,7 @@ function FloPayAutomaticPaymentButton({
4265
4487
  }
4266
4488
  }
4267
4489
  }, [
4268
- fallbackPublishableKey,
4490
+ checkoutMethod,
4269
4491
  locale,
4270
4492
  automaticPaymentToken,
4271
4493
  paymentMethodId,
@@ -4377,7 +4599,7 @@ function FloPayAutomaticPaymentButton({
4377
4599
  if (!clientSecret) {
4378
4600
  return;
4379
4601
  }
4380
- const resumeState = readPayPalResumeState();
4602
+ const resumeState = readPayPalResumeState2();
4381
4603
  if (!resumeState) {
4382
4604
  return;
4383
4605
  }
@@ -4420,9 +4642,16 @@ function FloPayAutomaticPaymentButton({
4420
4642
  { checkoutMethod: "paypal" }
4421
4643
  );
4422
4644
  }
4423
- if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded" || paymentIntent.status === "processing")) {
4645
+ const resultStatus = mapPayPalIntentStatusToPaymentResult(paymentIntent?.status);
4646
+ if (paymentIntent && resultStatus !== "failed") {
4647
+ const paymentMethodId2 = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
4424
4648
  await showSuccess({
4425
- result: { status: "succeeded", paymentIntentId: paymentIntent.id },
4649
+ result: {
4650
+ status: resultStatus,
4651
+ paymentIntentId: paymentIntent.id,
4652
+ paymentMethodId: paymentMethodId2,
4653
+ checkoutMethod: "paypal"
4654
+ },
4426
4655
  session: null,
4427
4656
  sessionId: resumeState.sessionId,
4428
4657
  autoCompleted: false
@@ -4440,8 +4669,8 @@ function FloPayAutomaticPaymentButton({
4440
4669
  method: floPayErr.checkoutMethod ?? "paypal"
4441
4670
  });
4442
4671
  } finally {
4443
- clearPayPalResumeState();
4444
- clearPayPalRedirectParams();
4672
+ clearPayPalResumeState2();
4673
+ clearPayPalRedirectParams2();
4445
4674
  if (isMountedRef.current) {
4446
4675
  setOverlayStatus(null);
4447
4676
  setOverlayError(null);
@@ -4555,7 +4784,6 @@ function FloPayAutomaticPaymentButton({
4555
4784
  sessionId: fallbackSession.sessionId,
4556
4785
  checkoutMode: "full",
4557
4786
  billingApiUrl: resolvedBillingUrl,
4558
- fallbackPublishableKey,
4559
4787
  initialErrorMessage: fallbackSession.errorMessage,
4560
4788
  cardTitleContent: null,
4561
4789
  showSecurityFooter: false,