@flopay/react 0.4.8 → 0.4.10

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
  }
@@ -1229,8 +1260,9 @@ function SplitCardFormInner({
1229
1260
  await processPaymentInternal({
1230
1261
  id: result.paymentIntentId,
1231
1262
  type: "card",
1232
- threeDSecureActionResultTokenId: result.paymentIntentId,
1233
- originalPaymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
1263
+ threeDSecureActionResultTokenId: result.paymentIntentId
1264
+ }, {
1265
+ completionPaymentMethodId: resolvedCompletionPaymentMethodId
1234
1266
  });
1235
1267
  }
1236
1268
  } finally {
@@ -2055,7 +2087,8 @@ async function processSavedPaymentForMode({
2055
2087
  result: {
2056
2088
  status: "succeeded",
2057
2089
  paymentIntentId: tokenizedData?.threeDSecureActionResultTokenId,
2058
- paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedData)
2090
+ paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedData),
2091
+ checkoutMethod: tokenizedData ? tokenizedData.isPaypal ? "paypal" : "card" : void 0
2059
2092
  }
2060
2093
  };
2061
2094
  }
@@ -2158,7 +2191,7 @@ async function processSavedPaymentWithIntent({
2158
2191
  if (!confirmResult.paymentIntentId || confirmResult.status !== "succeeded" && confirmResult.status !== "processing" && confirmResult.status !== "requires_capture") {
2159
2192
  throw Object.assign(
2160
2193
  new FloPayError3(
2161
- `Payment intent in unexpected status: ${confirmResult.status}`,
2194
+ `Unfortunately, your payment could not be processed. Please try again using a different payment method or contact your bank for assistance. If the issue persists, feel free to reach out to us for support.`,
2162
2195
  "api_error",
2163
2196
  {
2164
2197
  code: confirmResult.status === "requires_action" ? "authentication_required" : void 0
@@ -2174,8 +2207,7 @@ async function processSavedPaymentWithIntent({
2174
2207
  tokenizedData: {
2175
2208
  id: paymentMethodId,
2176
2209
  type: "card",
2177
- threeDSecureActionResultTokenId: confirmResult.paymentIntentId,
2178
- originalPaymentMethodId: paymentMethodId
2210
+ threeDSecureActionResultTokenId: confirmResult.paymentIntentId
2179
2211
  },
2180
2212
  returnUrl
2181
2213
  });
@@ -2191,7 +2223,8 @@ async function processSavedPaymentWithIntent({
2191
2223
  return {
2192
2224
  ...finalResult,
2193
2225
  paymentIntentId: finalResult.paymentIntentId ?? confirmResult.paymentIntentId,
2194
- paymentMethodId: finalResult.paymentMethodId ?? paymentMethodId
2226
+ paymentMethodId: finalResult.paymentMethodId ?? paymentMethodId,
2227
+ checkoutMethod: finalResult.checkoutMethod ?? "card"
2195
2228
  };
2196
2229
  }
2197
2230
  async function handleSavedPaymentRedirectResult(redirectResult, {
@@ -2285,8 +2318,7 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
2285
2318
  tokenizedData: {
2286
2319
  id: paymentIntent.id,
2287
2320
  type: "card",
2288
- threeDSecureActionResultTokenId: paymentIntent.id,
2289
- originalPaymentMethodId: savedPaymentMethodId
2321
+ threeDSecureActionResultTokenId: paymentIntent.id
2290
2322
  },
2291
2323
  returnUrl
2292
2324
  });
@@ -2341,7 +2373,10 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
2341
2373
  { checkoutMethod: "paypal" }
2342
2374
  );
2343
2375
  }
2344
- return { status: "succeeded" };
2376
+ return {
2377
+ status: "succeeded",
2378
+ checkoutMethod: "paypal"
2379
+ };
2345
2380
  }
2346
2381
  throw new FloPayError3("Unsupported payment redirect state.", "api_error");
2347
2382
  }
@@ -2354,19 +2389,16 @@ function normalizeSavedPaymentError(err) {
2354
2389
  "api_error"
2355
2390
  );
2356
2391
  }
2357
- function resolveSavedPaymentPublishableKeys(unified, fallbackPublishableKey) {
2392
+ function resolveSavedPaymentPublishableKeys(unified) {
2358
2393
  let publishableKey;
2359
2394
  let paypalPublishableKey;
2360
2395
  if (unified.provider === "stripe") {
2361
2396
  publishableKey = unified.data.stripe?.publishableKey;
2362
2397
  paypalPublishableKey = unified.data.stripe?.paypalPublishableKey ?? void 0;
2363
2398
  }
2364
- if (!publishableKey) {
2365
- publishableKey = fallbackPublishableKey;
2366
- }
2367
2399
  if (!publishableKey) {
2368
2400
  throw new FloPayError3(
2369
- "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.",
2370
2402
  "validation_error"
2371
2403
  );
2372
2404
  }
@@ -2404,9 +2436,60 @@ async function loadSavedPaymentProviders({
2404
2436
  // src/flopay-checkout.tsx
2405
2437
  import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
2406
2438
  var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2 = 44;
2439
+ var PAYPAL_RESUME_STORAGE_KEY = "flopay_checkout_saved_payment_resume";
2407
2440
  function sleep(ms) {
2408
2441
  return new Promise((resolve) => setTimeout(resolve, ms));
2409
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
+ }
2410
2493
  function hasInlineSessionPatchData(patch) {
2411
2494
  if (!patch) return false;
2412
2495
  return Boolean(
@@ -2419,7 +2502,6 @@ function FloPayCheckout({
2419
2502
  billingApiUrl,
2420
2503
  appearance,
2421
2504
  locale,
2422
- fallbackPublishableKey,
2423
2505
  loading: loadingNode,
2424
2506
  error: errorNode,
2425
2507
  onComplete,
@@ -2474,6 +2556,8 @@ function FloPayCheckout({
2474
2556
  const [createSessionPatchBaseHash, setCreateSessionPatchBaseHash] = useState3("");
2475
2557
  const [cardBootstrapPending, setCardBootstrapPending] = useState3(false);
2476
2558
  const autoCheckoutAttempted = useRef3(false);
2559
+ const paypalResumeAttempted = useRef3(false);
2560
+ const savedPaymentKeysRef = useRef3(null);
2477
2561
  const onCompleteRef = useRef3(onComplete);
2478
2562
  onCompleteRef.current = onComplete;
2479
2563
  const onErrorRef = useRef3(onError);
@@ -2533,6 +2617,13 @@ function FloPayCheckout({
2533
2617
  const redirectResult = getRedirectResultFromCheckoutProcessError(options?.initialAutoProcessingError);
2534
2618
  let paymentResult;
2535
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
+ }
2536
2627
  paymentResult = await handleSavedPaymentRedirectResult(redirectResult, {
2537
2628
  flopay: flopayRef.current,
2538
2629
  paypalFlopay: paypalFlopayRef.current,
@@ -2541,6 +2632,9 @@ function FloPayCheckout({
2541
2632
  sessionId: activeSessionId2,
2542
2633
  session: sess
2543
2634
  });
2635
+ if (redirectResult.type === "paypal_redirect_required") {
2636
+ clearPayPalResumeState();
2637
+ }
2544
2638
  } else if (options?.initialAutoProcessingError) {
2545
2639
  throw checkoutProcessErrorToFloPayError(
2546
2640
  options.initialAutoProcessingError,
@@ -2552,6 +2646,7 @@ function FloPayCheckout({
2552
2646
  } else if (options?.fromCreateSession) {
2553
2647
  if (options?.fallbackToFull) {
2554
2648
  setCurrentMode("full");
2649
+ replaceCheckoutModeQueryParam("full");
2555
2650
  }
2556
2651
  return false;
2557
2652
  } else {
@@ -2560,14 +2655,28 @@ function FloPayCheckout({
2560
2655
  sessionId: activeSessionId2,
2561
2656
  session: sess
2562
2657
  });
2563
- paymentResult = result.type === "success" ? result.result : await handleSavedPaymentRedirectResult(result, {
2564
- flopay: flopayRef.current,
2565
- paypalFlopay: paypalFlopayRef.current,
2566
- attempt3DS: options?.attempt3DS,
2567
- billingApiUrl: resolvedBillingUrl,
2568
- sessionId: activeSessionId2,
2569
- session: sess
2570
- });
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
+ }
2571
2680
  }
2572
2681
  setModeOverlayStatus("success");
2573
2682
  await sleep(PROCESSING_OVERLAY_SUCCESS_DELAY_MS);
@@ -2580,6 +2689,7 @@ function FloPayCheckout({
2580
2689
  setModeOverlayError(floPayErr.message);
2581
2690
  if (options?.fallbackToFull) {
2582
2691
  setCurrentMode("full");
2692
+ replaceCheckoutModeQueryParam("full");
2583
2693
  }
2584
2694
  onErrorRef.current?.(floPayErr);
2585
2695
  emitDecline(method, floPayErr, {
@@ -2603,6 +2713,96 @@ function FloPayCheckout({
2603
2713
  resolvedBillingUrl
2604
2714
  ]
2605
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]);
2606
2806
  const inflightRef = useRef3(/* @__PURE__ */ new Map());
2607
2807
  const initializedHashRef = useRef3(null);
2608
2808
  function hashCreateParams(params) {
@@ -2701,7 +2901,7 @@ function FloPayCheckout({
2701
2901
  const {
2702
2902
  publishableKey,
2703
2903
  paypalPublishableKey
2704
- } = resolveSavedPaymentPublishableKeys(realResult, fallbackPublishableKey);
2904
+ } = resolveSavedPaymentPublishableKeys(realResult);
2705
2905
  const {
2706
2906
  flopay: instance,
2707
2907
  paypalFlopay: paypalInstance
@@ -2724,7 +2924,7 @@ function FloPayCheckout({
2724
2924
  }
2725
2925
  return resolved;
2726
2926
  },
2727
- [fallbackPublishableKey, locale, resolvedBillingUrl]
2927
+ [locale, resolvedBillingUrl]
2728
2928
  );
2729
2929
  const handleInlineSessionPatch = useCallback2(async (patch) => {
2730
2930
  if (!hasInlineSessionPatchData(patch) || cardBootstrapPending) {
@@ -2772,6 +2972,7 @@ function FloPayCheckout({
2772
2972
  try {
2773
2973
  const resolved = await bootstrapInlineSession();
2774
2974
  const resolvedSession = resolved.result.data.session ?? null;
2975
+ savedPaymentKeysRef.current = resolveSavedPaymentPublishableKeys(resolved.result);
2775
2976
  if (!cancelled && shouldAutoProcessInlineSession && resolvedSession) {
2776
2977
  autoCheckoutAttempted.current = true;
2777
2978
  await runSavedPaymentFlow(resolvedSession, {
@@ -2870,7 +3071,11 @@ function FloPayCheckout({
2870
3071
  const {
2871
3072
  publishableKey,
2872
3073
  paypalPublishableKey
2873
- } = resolveSavedPaymentPublishableKeys(result, fallbackPublishableKey);
3074
+ } = resolveSavedPaymentPublishableKeys(result);
3075
+ savedPaymentKeysRef.current = {
3076
+ publishableKey,
3077
+ paypalPublishableKey
3078
+ };
2874
3079
  const {
2875
3080
  flopay: instance,
2876
3081
  paypalFlopay: paypalInstance
@@ -3423,9 +3628,11 @@ function CheckoutFormInner({
3423
3628
  [onDecline]
3424
3629
  );
3425
3630
  const processPaymentInternal = useCallback3(
3426
- async (tokenizedBody) => {
3631
+ async (tokenizedBody, completionPaymentMethodId) => {
3427
3632
  setProcessing(true);
3428
3633
  updateError(null);
3634
+ const resolvedCompletionPaymentMethodId = completionPaymentMethodId ?? resolveTokenizedPaymentMethodId(tokenizedBody);
3635
+ const requestTokenizedBody = tokenizedBody.originalPaymentMethodId ? { ...tokenizedBody, originalPaymentMethodId: void 0 } : tokenizedBody;
3429
3636
  try {
3430
3637
  const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
3431
3638
  method: "POST",
@@ -3435,7 +3642,7 @@ function CheckoutFormInner({
3435
3642
  },
3436
3643
  body: JSON.stringify({
3437
3644
  sessionId,
3438
- tokenizedData: tokenizedBody,
3645
+ tokenizedData: requestTokenizedBody,
3439
3646
  accountData: {
3440
3647
  userId: userId ?? "",
3441
3648
  email: email ?? "",
@@ -3449,7 +3656,8 @@ function CheckoutFormInner({
3449
3656
  onComplete?.({
3450
3657
  status: "succeeded",
3451
3658
  paymentIntentId: tokenizedBody.threeDSecureActionResultTokenId,
3452
- paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
3659
+ paymentMethodId: resolvedCompletionPaymentMethodId,
3660
+ checkoutMethod: tokenizedBody.isPaypal ? "paypal" : "card"
3453
3661
  });
3454
3662
  return;
3455
3663
  }
@@ -3481,9 +3689,8 @@ function CheckoutFormInner({
3481
3689
  await processPaymentInternal({
3482
3690
  id: result.paymentIntentId,
3483
3691
  type: "card",
3484
- threeDSecureActionResultTokenId: result.paymentIntentId,
3485
- originalPaymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
3486
- });
3692
+ threeDSecureActionResultTokenId: result.paymentIntentId
3693
+ }, resolvedCompletionPaymentMethodId);
3487
3694
  }
3488
3695
  } finally {
3489
3696
  setIs3DSActive(false);
@@ -3810,33 +4017,23 @@ function PayPalButton({
3810
4017
  if (!sessionId || !email) {
3811
4018
  throw new FloPayError6("Missing sessionId or email for PayPal payment", "validation_error");
3812
4019
  }
3813
- const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
3814
- method: "POST",
3815
- headers: { "Content-Type": "application/json" },
3816
- body: JSON.stringify({
3817
- sessionId,
3818
- email,
3819
- paymentMethodType: "paypal",
3820
- isPaypal: "true"
3821
- })
3822
- });
3823
- if (!intentResponse.ok) throw new FloPayError6("Failed to create payment intent", "api_error");
3824
- const intentJson = await intentResponse.json();
3825
- const intentClientSecret = intentJson.data?.id;
3826
- if (!intentClientSecret) throw new FloPayError6("No client_secret in response", "api_error");
3827
- const result = await flopay.confirmPayment({
3828
- clientSecret: intentClientSecret,
4020
+ const result = await flopay.confirmPayPalPayment({
4021
+ billingApiUrl: baseUrl,
4022
+ sessionId,
4023
+ email,
3829
4024
  returnUrl: window.location.href
3830
4025
  });
3831
- 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")) {
3832
4031
  dispatchTokenizedBody({
3833
- id: result.paymentIntentId,
4032
+ id: result.paymentMethodId ?? result.paymentIntentId,
3834
4033
  type: "card",
3835
4034
  threeDSecureActionResultTokenId: result.paymentIntentId,
3836
4035
  isPaypal: true
3837
4036
  });
3838
- } else if (result.error) {
3839
- onErrorChange?.(result.error.message);
3840
4037
  }
3841
4038
  } catch (err) {
3842
4039
  onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
@@ -3896,7 +4093,7 @@ import { PaymentAPI as PaymentAPI3 } from "@flopay/js";
3896
4093
  import { FloPayError as FloPayError7, resolveBillingApiUrl as resolveBillingApiUrl4, resolveButtonsLayoutTheme as resolveButtonsLayoutTheme3 } from "@flopay/shared";
3897
4094
  import { Fragment as Fragment6, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
3898
4095
  var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT3 = 44;
3899
- var PAYPAL_RESUME_STORAGE_KEY = "flopay_automatic_payment_button_resume";
4096
+ var PAYPAL_RESUME_STORAGE_KEY2 = "flopay_automatic_payment_button_resume";
3900
4097
  function sleep2(ms) {
3901
4098
  return new Promise((resolve) => setTimeout(resolve, ms));
3902
4099
  }
@@ -3909,42 +4106,44 @@ function coerceError(err, fallbackMessage) {
3909
4106
  "api_error"
3910
4107
  );
3911
4108
  }
3912
- function canUseStorage() {
4109
+ function canUseStorage2() {
3913
4110
  return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
3914
4111
  }
3915
4112
  function shouldShowFallbackCheckout(error, sessionId) {
3916
4113
  if (!sessionId) return false;
3917
4114
  if (error.type === "validation_error") return false;
3918
- if (error.checkoutMethod && error.checkoutMethod !== "card") return false;
4115
+ if (error.checkoutMethod && error.checkoutMethod !== "card" && error.checkoutMethod !== "paypal") {
4116
+ return false;
4117
+ }
3919
4118
  return true;
3920
4119
  }
3921
- function readPayPalResumeState() {
3922
- if (!canUseStorage()) return null;
4120
+ function readPayPalResumeState2() {
4121
+ if (!canUseStorage2()) return null;
3923
4122
  try {
3924
- const raw = window.sessionStorage.getItem(PAYPAL_RESUME_STORAGE_KEY);
4123
+ const raw = window.sessionStorage.getItem(PAYPAL_RESUME_STORAGE_KEY2);
3925
4124
  if (!raw) return null;
3926
4125
  return JSON.parse(raw);
3927
4126
  } catch {
3928
4127
  return null;
3929
4128
  }
3930
4129
  }
3931
- function persistPayPalResumeState(state) {
3932
- if (!canUseStorage()) return;
4130
+ function persistPayPalResumeState2(state) {
4131
+ if (!canUseStorage2()) return;
3933
4132
  try {
3934
- window.sessionStorage.setItem(PAYPAL_RESUME_STORAGE_KEY, JSON.stringify(state));
4133
+ window.sessionStorage.setItem(PAYPAL_RESUME_STORAGE_KEY2, JSON.stringify(state));
3935
4134
  } catch (error) {
3936
4135
  console.warn("[FloPayAutomaticPaymentButton] Failed to persist PayPal resume state.", error);
3937
4136
  }
3938
4137
  }
3939
- function clearPayPalResumeState() {
3940
- if (!canUseStorage()) return;
4138
+ function clearPayPalResumeState2() {
4139
+ if (!canUseStorage2()) return;
3941
4140
  try {
3942
- window.sessionStorage.removeItem(PAYPAL_RESUME_STORAGE_KEY);
4141
+ window.sessionStorage.removeItem(PAYPAL_RESUME_STORAGE_KEY2);
3943
4142
  } catch (error) {
3944
4143
  console.warn("[FloPayAutomaticPaymentButton] Failed to clear PayPal resume state.", error);
3945
4144
  }
3946
4145
  }
3947
- function clearPayPalRedirectParams() {
4146
+ function clearPayPalRedirectParams2() {
3948
4147
  if (typeof window === "undefined") return;
3949
4148
  const url = new URL(window.location.href);
3950
4149
  const keys = ["payment_intent", "payment_intent_client_secret", "redirect_status"];
@@ -3986,6 +4185,7 @@ function FloPayAutomaticPaymentButton({
3986
4185
  sessionId,
3987
4186
  createSession,
3988
4187
  paymentMethodId,
4188
+ checkoutMethod,
3989
4189
  clientId,
3990
4190
  items,
3991
4191
  subscriptions,
@@ -3997,7 +4197,6 @@ function FloPayAutomaticPaymentButton({
3997
4197
  utmMetadata,
3998
4198
  billingApiUrl,
3999
4199
  locale,
4000
- fallbackPublishableKey,
4001
4200
  buttonsTheme,
4002
4201
  buttonsStyles: stylesOverride,
4003
4202
  onSuccess,
@@ -4046,9 +4245,10 @@ function FloPayAutomaticPaymentButton({
4046
4245
  const automaticPaymentToken = useMemo4(
4047
4246
  () => paymentMethodId ? {
4048
4247
  id: paymentMethodId,
4049
- type: "card"
4248
+ type: "card",
4249
+ ...checkoutMethod === "paypal" ? { isPaypal: true } : {}
4050
4250
  } : void 0,
4051
- [paymentMethodId]
4251
+ [checkoutMethod, paymentMethodId]
4052
4252
  );
4053
4253
  const isMountedRef = useRef5(true);
4054
4254
  const resumeAttemptedRef = useRef5(false);
@@ -4130,13 +4330,14 @@ function FloPayAutomaticPaymentButton({
4130
4330
  let paymentResult = null;
4131
4331
  const redirectResult = getRedirectResultFromCheckoutProcessError(apiResult.autoProcessingError);
4132
4332
  const shouldTreatCreateSessionFlowAsServerAutoAttempt = options?.fromCreateSession && (apiResult.autoProcessingAttempted === true || !!apiResult.autoProcessingError || !!redirectResult);
4333
+ const shouldRetryPayPalClientSide = checkoutMethod === "paypal" && automaticPaymentToken?.isPaypal === true && options?.fromCreateSession === true;
4133
4334
  if (redirectResult) {
4134
4335
  const {
4135
4336
  publishableKey,
4136
4337
  paypalPublishableKey
4137
- } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4338
+ } = resolveSavedPaymentPublishableKeys(apiResult);
4138
4339
  if (redirectResult.type === "paypal_redirect_required") {
4139
- persistPayPalResumeState({
4340
+ persistPayPalResumeState2({
4140
4341
  sessionId: session.id || resolvedSessionId,
4141
4342
  publishableKey,
4142
4343
  paypalPublishableKey
@@ -4160,9 +4361,9 @@ function FloPayAutomaticPaymentButton({
4160
4361
  session
4161
4362
  });
4162
4363
  if (redirectResult.type === "paypal_redirect_required") {
4163
- clearPayPalResumeState();
4364
+ clearPayPalResumeState2();
4164
4365
  }
4165
- } else if (apiResult.autoProcessingError) {
4366
+ } else if (apiResult.autoProcessingError && !shouldRetryPayPalClientSide) {
4166
4367
  throw checkoutProcessErrorToFloPayError(
4167
4368
  apiResult.autoProcessingError,
4168
4369
  "Automatic payment failed. Please try again.",
@@ -4170,7 +4371,7 @@ function FloPayAutomaticPaymentButton({
4170
4371
  checkoutMethod: apiResult.autoProcessingError.checkoutMethod
4171
4372
  }
4172
4373
  );
4173
- } else if (shouldTreatCreateSessionFlowAsServerAutoAttempt) {
4374
+ } else if (shouldTreatCreateSessionFlowAsServerAutoAttempt && !shouldRetryPayPalClientSide) {
4174
4375
  throw checkoutProcessErrorToFloPayError(
4175
4376
  {
4176
4377
  type: "unknown",
@@ -4190,9 +4391,9 @@ function FloPayAutomaticPaymentButton({
4190
4391
  const {
4191
4392
  publishableKey,
4192
4393
  paypalPublishableKey
4193
- } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4394
+ } = resolveSavedPaymentPublishableKeys(apiResult);
4194
4395
  if (result.type === "paypal_redirect_required") {
4195
- persistPayPalResumeState({
4396
+ persistPayPalResumeState2({
4196
4397
  sessionId: session.id || resolvedSessionId,
4197
4398
  publishableKey,
4198
4399
  paypalPublishableKey
@@ -4216,17 +4417,19 @@ function FloPayAutomaticPaymentButton({
4216
4417
  session
4217
4418
  });
4218
4419
  if (result.type === "paypal_redirect_required") {
4219
- clearPayPalResumeState();
4420
+ clearPayPalResumeState2();
4220
4421
  }
4221
4422
  }
4222
4423
  }
4223
4424
  await showSuccess({
4224
4425
  result: paymentResult ? {
4225
4426
  ...paymentResult,
4226
- paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId
4427
+ paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId,
4428
+ checkoutMethod: paymentResult.checkoutMethod ?? checkoutMethod
4227
4429
  } : {
4228
4430
  status: "succeeded",
4229
- paymentMethodId: paymentMethodId ?? void 0
4431
+ paymentMethodId: paymentMethodId ?? void 0,
4432
+ checkoutMethod
4230
4433
  },
4231
4434
  session,
4232
4435
  sessionId: session.id || resolvedSessionId,
@@ -4241,7 +4444,7 @@ function FloPayAutomaticPaymentButton({
4241
4444
  const {
4242
4445
  publishableKey,
4243
4446
  paypalPublishableKey
4244
- } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4447
+ } = resolveSavedPaymentPublishableKeys(apiResult);
4245
4448
  const {
4246
4449
  flopay
4247
4450
  } = await loadSavedPaymentProviders({
@@ -4260,7 +4463,8 @@ function FloPayAutomaticPaymentButton({
4260
4463
  await showSuccess({
4261
4464
  result: {
4262
4465
  ...paymentResult,
4263
- paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId
4466
+ paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId,
4467
+ checkoutMethod: paymentResult.checkoutMethod ?? checkoutMethod
4264
4468
  },
4265
4469
  session,
4266
4470
  sessionId: fallbackSessionId,
@@ -4283,7 +4487,7 @@ function FloPayAutomaticPaymentButton({
4283
4487
  }
4284
4488
  }
4285
4489
  }, [
4286
- fallbackPublishableKey,
4490
+ checkoutMethod,
4287
4491
  locale,
4288
4492
  automaticPaymentToken,
4289
4493
  paymentMethodId,
@@ -4395,7 +4599,7 @@ function FloPayAutomaticPaymentButton({
4395
4599
  if (!clientSecret) {
4396
4600
  return;
4397
4601
  }
4398
- const resumeState = readPayPalResumeState();
4602
+ const resumeState = readPayPalResumeState2();
4399
4603
  if (!resumeState) {
4400
4604
  return;
4401
4605
  }
@@ -4438,9 +4642,16 @@ function FloPayAutomaticPaymentButton({
4438
4642
  { checkoutMethod: "paypal" }
4439
4643
  );
4440
4644
  }
4441
- 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;
4442
4648
  await showSuccess({
4443
- result: { status: "succeeded", paymentIntentId: paymentIntent.id },
4649
+ result: {
4650
+ status: resultStatus,
4651
+ paymentIntentId: paymentIntent.id,
4652
+ paymentMethodId: paymentMethodId2,
4653
+ checkoutMethod: "paypal"
4654
+ },
4444
4655
  session: null,
4445
4656
  sessionId: resumeState.sessionId,
4446
4657
  autoCompleted: false
@@ -4458,8 +4669,8 @@ function FloPayAutomaticPaymentButton({
4458
4669
  method: floPayErr.checkoutMethod ?? "paypal"
4459
4670
  });
4460
4671
  } finally {
4461
- clearPayPalResumeState();
4462
- clearPayPalRedirectParams();
4672
+ clearPayPalResumeState2();
4673
+ clearPayPalRedirectParams2();
4463
4674
  if (isMountedRef.current) {
4464
4675
  setOverlayStatus(null);
4465
4676
  setOverlayError(null);
@@ -4573,7 +4784,6 @@ function FloPayAutomaticPaymentButton({
4573
4784
  sessionId: fallbackSession.sessionId,
4574
4785
  checkoutMode: "full",
4575
4786
  billingApiUrl: resolvedBillingUrl,
4576
- fallbackPublishableKey,
4577
4787
  initialErrorMessage: fallbackSession.errorMessage,
4578
4788
  cardTitleContent: null,
4579
4789
  showSecurityFooter: false,