@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.cjs CHANGED
@@ -126,13 +126,22 @@ function FloPayProvider({
126
126
  clientSecret: options?.clientSecret,
127
127
  amount: options?.amount,
128
128
  currency: options?.currency,
129
- paymentMethodCreation: options?.paymentMethodCreation
129
+ paymentMethodCreation: options?.paymentMethodCreation,
130
+ setupFutureUsage: options?.setupFutureUsage
130
131
  });
131
132
  setElements(els);
132
133
  return () => {
133
134
  els.destroy();
134
135
  };
135
- }, [flopay, options?.appearance, options?.clientSecret, options?.amount, options?.currency]);
136
+ }, [
137
+ flopay,
138
+ options?.appearance,
139
+ options?.clientSecret,
140
+ options?.amount,
141
+ options?.currency,
142
+ options?.paymentMethodCreation,
143
+ options?.setupFutureUsage
144
+ ]);
136
145
  const resolvedBillingApiUrl = (0, import_shared.resolveBillingApiUrl)(options?.billingApiUrl);
137
146
  const value = (0, import_react2.useMemo)(
138
147
  () => ({ flopay, paypalFlopay, elements, billingApiUrl: resolvedBillingApiUrl }),
@@ -550,6 +559,15 @@ function buildDeclineEvent(method, input, overrides) {
550
559
  ...declineCode ? { declineCode } : {}
551
560
  };
552
561
  }
562
+ function mapPayPalIntentStatusToPaymentResult(status) {
563
+ if (status === "succeeded") {
564
+ return "succeeded";
565
+ }
566
+ if (status === "processing" || status === "requires_capture") {
567
+ return "processing";
568
+ }
569
+ return "failed";
570
+ }
553
571
  function resolveTokenizedPaymentMethodId(tokenizedBody) {
554
572
  const candidates = [
555
573
  tokenizedBody?.id,
@@ -777,7 +795,10 @@ function PayPalButtonInner({
777
795
  const createPM = stripe.createPaymentMethod;
778
796
  const { error: pmError, paymentMethod } = await createPM({ type: "paypal" });
779
797
  if (pmError) {
780
- console.warn("[FloPay] Could not create PayPal PM upfront:", pmError.message);
798
+ const message = pmError.message ?? "PayPal payment failed.";
799
+ onErrorChange?.(message);
800
+ event.paymentFailed({ reason: "fail", message });
801
+ return;
781
802
  }
782
803
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
783
804
  method: "POST",
@@ -786,7 +807,9 @@ function PayPalButtonInner({
786
807
  sessionId: effectiveSessionId,
787
808
  email: effectiveEmail,
788
809
  paymentMethodType: paymentMethod?.id ?? "paypal",
789
- isPaypal: "true"
810
+ isPaypal: "true",
811
+ setupFutureUsage: "off_session",
812
+ setup_future_usage: "off_session"
790
813
  })
791
814
  });
792
815
  if (!intentResponse.ok) throw new Error("Failed to create payment intent");
@@ -822,6 +845,7 @@ function PayPalButtonInner({
822
845
  accountPatch: prepared?.accountPatch,
823
846
  sessionId: effectiveSessionId
824
847
  });
848
+ return;
825
849
  } catch (err) {
826
850
  onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
827
851
  } finally {
@@ -842,6 +866,9 @@ function PayPalButtonInner({
842
866
  },
843
867
  options: {
844
868
  buttonType: { paypal: "paypal" },
869
+ billingAddressRequired: false,
870
+ phoneNumberRequired: false,
871
+ shippingAddressRequired: false,
845
872
  paymentMethods: {
846
873
  applePay: "never",
847
874
  googlePay: "never",
@@ -1121,7 +1148,8 @@ function SplitCardFormInner({
1121
1148
  mode: "payment",
1122
1149
  amount: amountInCents,
1123
1150
  currency: currency.toLowerCase(),
1124
- captureMethod: "manual"
1151
+ captureMethod: "manual",
1152
+ setupFutureUsage: "off_session"
1125
1153
  }), [amountInCents, currency]);
1126
1154
  const updateError = (0, import_react7.useCallback)(
1127
1155
  (err) => {
@@ -1206,6 +1234,8 @@ function SplitCardFormInner({
1206
1234
  updateError(null);
1207
1235
  const effectiveSessionId = overrides?.sessionId ?? sessionId;
1208
1236
  const effectiveAccount = mergeAccountPatch(resolvedAccount, overrides?.accountPatch);
1237
+ const resolvedCompletionPaymentMethodId = overrides?.completionPaymentMethodId ?? resolveTokenizedPaymentMethodId(tokenizedBody);
1238
+ const requestTokenizedBody = tokenizedBody.originalPaymentMethodId ? { ...tokenizedBody, originalPaymentMethodId: void 0 } : tokenizedBody;
1209
1239
  try {
1210
1240
  const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
1211
1241
  method: "POST",
@@ -1215,7 +1245,7 @@ function SplitCardFormInner({
1215
1245
  },
1216
1246
  body: JSON.stringify({
1217
1247
  sessionId: effectiveSessionId,
1218
- tokenizedData: tokenizedBody,
1248
+ tokenizedData: requestTokenizedBody,
1219
1249
  accountData: {
1220
1250
  userId: effectiveAccount.userId ?? "",
1221
1251
  email: effectiveAccount.email ?? "",
@@ -1232,7 +1262,8 @@ function SplitCardFormInner({
1232
1262
  onComplete?.({
1233
1263
  status: "succeeded",
1234
1264
  paymentIntentId: tokenizedBody.threeDSecureActionResultTokenId,
1235
- paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
1265
+ paymentMethodId: resolvedCompletionPaymentMethodId,
1266
+ checkoutMethod: tokenizedBody.isPaypal ? "paypal" : "card"
1236
1267
  });
1237
1268
  return;
1238
1269
  }
@@ -1246,9 +1277,22 @@ function SplitCardFormInner({
1246
1277
  }
1247
1278
  setIs3DSActive(true);
1248
1279
  try {
1280
+ const retryBillingAddress = {
1281
+ ...(enableAVS ? selectedCountryRef.current : effectiveAccount.country) ? {
1282
+ country: enableAVS ? selectedCountryRef.current : effectiveAccount.country
1283
+ } : {},
1284
+ ...(enableAVS ? zipCodeRef.current.trim() : effectiveAccount.zip) || "" ? {
1285
+ postal_code: enableAVS ? zipCodeRef.current.trim() : effectiveAccount.zip
1286
+ } : {}
1287
+ };
1249
1288
  const result = await flopay.confirmPayment({
1250
1289
  clientSecret: secret,
1251
- returnUrl: window.location.href
1290
+ returnUrl: window.location.href,
1291
+ billingDetails: {
1292
+ ...effectiveAccount.email ? { email: effectiveAccount.email } : {},
1293
+ ...fullName.trim() ? { name: fullName.trim() } : {},
1294
+ ...Object.keys(retryBillingAddress).length > 0 ? { address: retryBillingAddress } : {}
1295
+ }
1252
1296
  });
1253
1297
  if (result.error) {
1254
1298
  setOverlayStatus("error");
@@ -1262,8 +1306,9 @@ function SplitCardFormInner({
1262
1306
  await processPaymentInternal({
1263
1307
  id: result.paymentIntentId,
1264
1308
  type: "card",
1265
- threeDSecureActionResultTokenId: result.paymentIntentId,
1266
- originalPaymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
1309
+ threeDSecureActionResultTokenId: result.paymentIntentId
1310
+ }, {
1311
+ completionPaymentMethodId: resolvedCompletionPaymentMethodId
1267
1312
  });
1268
1313
  }
1269
1314
  } finally {
@@ -2088,7 +2133,8 @@ async function processSavedPaymentForMode({
2088
2133
  result: {
2089
2134
  status: "succeeded",
2090
2135
  paymentIntentId: tokenizedData?.threeDSecureActionResultTokenId,
2091
- paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedData)
2136
+ paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedData),
2137
+ checkoutMethod: tokenizedData ? tokenizedData.isPaypal ? "paypal" : "card" : void 0
2092
2138
  }
2093
2139
  };
2094
2140
  }
@@ -2207,8 +2253,7 @@ async function processSavedPaymentWithIntent({
2207
2253
  tokenizedData: {
2208
2254
  id: paymentMethodId,
2209
2255
  type: "card",
2210
- threeDSecureActionResultTokenId: confirmResult.paymentIntentId,
2211
- originalPaymentMethodId: paymentMethodId
2256
+ threeDSecureActionResultTokenId: confirmResult.paymentIntentId
2212
2257
  },
2213
2258
  returnUrl
2214
2259
  });
@@ -2224,7 +2269,8 @@ async function processSavedPaymentWithIntent({
2224
2269
  return {
2225
2270
  ...finalResult,
2226
2271
  paymentIntentId: finalResult.paymentIntentId ?? confirmResult.paymentIntentId,
2227
- paymentMethodId: finalResult.paymentMethodId ?? paymentMethodId
2272
+ paymentMethodId: finalResult.paymentMethodId ?? paymentMethodId,
2273
+ checkoutMethod: finalResult.checkoutMethod ?? "card"
2228
2274
  };
2229
2275
  }
2230
2276
  async function handleSavedPaymentRedirectResult(redirectResult, {
@@ -2318,8 +2364,7 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
2318
2364
  tokenizedData: {
2319
2365
  id: paymentIntent.id,
2320
2366
  type: "card",
2321
- threeDSecureActionResultTokenId: paymentIntent.id,
2322
- originalPaymentMethodId: savedPaymentMethodId
2367
+ threeDSecureActionResultTokenId: paymentIntent.id
2323
2368
  },
2324
2369
  returnUrl
2325
2370
  });
@@ -2374,7 +2419,10 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
2374
2419
  { checkoutMethod: "paypal" }
2375
2420
  );
2376
2421
  }
2377
- return { status: "succeeded" };
2422
+ return {
2423
+ status: "succeeded",
2424
+ checkoutMethod: "paypal"
2425
+ };
2378
2426
  }
2379
2427
  throw new import_shared6.FloPayError("Unsupported payment redirect state.", "api_error");
2380
2428
  }
@@ -2387,19 +2435,16 @@ function normalizeSavedPaymentError(err) {
2387
2435
  "api_error"
2388
2436
  );
2389
2437
  }
2390
- function resolveSavedPaymentPublishableKeys(unified, fallbackPublishableKey) {
2438
+ function resolveSavedPaymentPublishableKeys(unified) {
2391
2439
  let publishableKey;
2392
2440
  let paypalPublishableKey;
2393
2441
  if (unified.provider === "stripe") {
2394
2442
  publishableKey = unified.data.stripe?.publishableKey;
2395
2443
  paypalPublishableKey = unified.data.stripe?.paypalPublishableKey ?? void 0;
2396
2444
  }
2397
- if (!publishableKey) {
2398
- publishableKey = fallbackPublishableKey;
2399
- }
2400
2445
  if (!publishableKey) {
2401
2446
  throw new import_shared6.FloPayError(
2402
- "No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
2447
+ "No publishable key found in the checkout session. Ensure the session includes gatewayData.publishableKey.",
2403
2448
  "validation_error"
2404
2449
  );
2405
2450
  }
@@ -2437,9 +2482,60 @@ async function loadSavedPaymentProviders({
2437
2482
  // src/flopay-checkout.tsx
2438
2483
  var import_jsx_runtime6 = require("react/jsx-runtime");
2439
2484
  var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2 = 44;
2485
+ var PAYPAL_RESUME_STORAGE_KEY = "flopay_checkout_saved_payment_resume";
2440
2486
  function sleep(ms) {
2441
2487
  return new Promise((resolve) => setTimeout(resolve, ms));
2442
2488
  }
2489
+ function canUseStorage() {
2490
+ return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
2491
+ }
2492
+ function readPayPalResumeState() {
2493
+ if (!canUseStorage()) return null;
2494
+ try {
2495
+ const raw = window.sessionStorage.getItem(PAYPAL_RESUME_STORAGE_KEY);
2496
+ if (!raw) return null;
2497
+ return JSON.parse(raw);
2498
+ } catch {
2499
+ return null;
2500
+ }
2501
+ }
2502
+ function persistPayPalResumeState(state) {
2503
+ if (!canUseStorage()) return;
2504
+ try {
2505
+ window.sessionStorage.setItem(PAYPAL_RESUME_STORAGE_KEY, JSON.stringify(state));
2506
+ } catch (error) {
2507
+ console.warn("[FloPayCheckout] Failed to persist PayPal resume state.", error);
2508
+ }
2509
+ }
2510
+ function clearPayPalResumeState() {
2511
+ if (!canUseStorage()) return;
2512
+ try {
2513
+ window.sessionStorage.removeItem(PAYPAL_RESUME_STORAGE_KEY);
2514
+ } catch (error) {
2515
+ console.warn("[FloPayCheckout] Failed to clear PayPal resume state.", error);
2516
+ }
2517
+ }
2518
+ function clearPayPalRedirectParams() {
2519
+ if (typeof window === "undefined") return;
2520
+ const url = new URL(window.location.href);
2521
+ const keys = ["payment_intent", "payment_intent_client_secret", "redirect_status"];
2522
+ let changed = false;
2523
+ for (const key of keys) {
2524
+ if (url.searchParams.has(key)) {
2525
+ url.searchParams.delete(key);
2526
+ changed = true;
2527
+ }
2528
+ }
2529
+ if (changed) {
2530
+ window.history.replaceState(window.history.state, "", url.toString());
2531
+ }
2532
+ }
2533
+ function replaceCheckoutModeQueryParam(mode) {
2534
+ if (typeof window === "undefined") return;
2535
+ const url = new URL(window.location.href);
2536
+ url.searchParams.set("mode", mode);
2537
+ window.history.replaceState(window.history.state, "", url.toString());
2538
+ }
2443
2539
  function hasInlineSessionPatchData(patch) {
2444
2540
  if (!patch) return false;
2445
2541
  return Boolean(
@@ -2452,7 +2548,6 @@ function FloPayCheckout({
2452
2548
  billingApiUrl,
2453
2549
  appearance,
2454
2550
  locale,
2455
- fallbackPublishableKey,
2456
2551
  loading: loadingNode,
2457
2552
  error: errorNode,
2458
2553
  onComplete,
@@ -2507,6 +2602,8 @@ function FloPayCheckout({
2507
2602
  const [createSessionPatchBaseHash, setCreateSessionPatchBaseHash] = (0, import_react8.useState)("");
2508
2603
  const [cardBootstrapPending, setCardBootstrapPending] = (0, import_react8.useState)(false);
2509
2604
  const autoCheckoutAttempted = (0, import_react8.useRef)(false);
2605
+ const paypalResumeAttempted = (0, import_react8.useRef)(false);
2606
+ const savedPaymentKeysRef = (0, import_react8.useRef)(null);
2510
2607
  const onCompleteRef = (0, import_react8.useRef)(onComplete);
2511
2608
  onCompleteRef.current = onComplete;
2512
2609
  const onErrorRef = (0, import_react8.useRef)(onError);
@@ -2566,6 +2663,13 @@ function FloPayCheckout({
2566
2663
  const redirectResult = getRedirectResultFromCheckoutProcessError(options?.initialAutoProcessingError);
2567
2664
  let paymentResult;
2568
2665
  if (redirectResult) {
2666
+ if (redirectResult.type === "paypal_redirect_required" && savedPaymentKeysRef.current) {
2667
+ persistPayPalResumeState({
2668
+ sessionId: activeSessionId2,
2669
+ publishableKey: savedPaymentKeysRef.current.publishableKey,
2670
+ paypalPublishableKey: savedPaymentKeysRef.current.paypalPublishableKey
2671
+ });
2672
+ }
2569
2673
  paymentResult = await handleSavedPaymentRedirectResult(redirectResult, {
2570
2674
  flopay: flopayRef.current,
2571
2675
  paypalFlopay: paypalFlopayRef.current,
@@ -2574,6 +2678,9 @@ function FloPayCheckout({
2574
2678
  sessionId: activeSessionId2,
2575
2679
  session: sess
2576
2680
  });
2681
+ if (redirectResult.type === "paypal_redirect_required") {
2682
+ clearPayPalResumeState();
2683
+ }
2577
2684
  } else if (options?.initialAutoProcessingError) {
2578
2685
  throw checkoutProcessErrorToFloPayError(
2579
2686
  options.initialAutoProcessingError,
@@ -2585,6 +2692,7 @@ function FloPayCheckout({
2585
2692
  } else if (options?.fromCreateSession) {
2586
2693
  if (options?.fallbackToFull) {
2587
2694
  setCurrentMode("full");
2695
+ replaceCheckoutModeQueryParam("full");
2588
2696
  }
2589
2697
  return false;
2590
2698
  } else {
@@ -2593,14 +2701,28 @@ function FloPayCheckout({
2593
2701
  sessionId: activeSessionId2,
2594
2702
  session: sess
2595
2703
  });
2596
- paymentResult = result.type === "success" ? result.result : await handleSavedPaymentRedirectResult(result, {
2597
- flopay: flopayRef.current,
2598
- paypalFlopay: paypalFlopayRef.current,
2599
- attempt3DS: options?.attempt3DS,
2600
- billingApiUrl: resolvedBillingUrl,
2601
- sessionId: activeSessionId2,
2602
- session: sess
2603
- });
2704
+ if (result.type === "success") {
2705
+ paymentResult = result.result;
2706
+ } else {
2707
+ if (result.type === "paypal_redirect_required" && savedPaymentKeysRef.current) {
2708
+ persistPayPalResumeState({
2709
+ sessionId: activeSessionId2,
2710
+ publishableKey: savedPaymentKeysRef.current.publishableKey,
2711
+ paypalPublishableKey: savedPaymentKeysRef.current.paypalPublishableKey
2712
+ });
2713
+ }
2714
+ paymentResult = await handleSavedPaymentRedirectResult(result, {
2715
+ flopay: flopayRef.current,
2716
+ paypalFlopay: paypalFlopayRef.current,
2717
+ attempt3DS: options?.attempt3DS,
2718
+ billingApiUrl: resolvedBillingUrl,
2719
+ sessionId: activeSessionId2,
2720
+ session: sess
2721
+ });
2722
+ if (result.type === "paypal_redirect_required") {
2723
+ clearPayPalResumeState();
2724
+ }
2725
+ }
2604
2726
  }
2605
2727
  setModeOverlayStatus("success");
2606
2728
  await sleep(PROCESSING_OVERLAY_SUCCESS_DELAY_MS);
@@ -2613,6 +2735,7 @@ function FloPayCheckout({
2613
2735
  setModeOverlayError(floPayErr.message);
2614
2736
  if (options?.fallbackToFull) {
2615
2737
  setCurrentMode("full");
2738
+ replaceCheckoutModeQueryParam("full");
2616
2739
  }
2617
2740
  onErrorRef.current?.(floPayErr);
2618
2741
  emitDecline(method, floPayErr, {
@@ -2636,6 +2759,96 @@ function FloPayCheckout({
2636
2759
  resolvedBillingUrl
2637
2760
  ]
2638
2761
  );
2762
+ (0, import_react8.useEffect)(() => {
2763
+ if (typeof window === "undefined" || paypalResumeAttempted.current) {
2764
+ return;
2765
+ }
2766
+ const params = new URLSearchParams(window.location.search);
2767
+ const clientSecret = params.get("payment_intent_client_secret");
2768
+ if (!clientSecret) {
2769
+ return;
2770
+ }
2771
+ const resumeState = readPayPalResumeState();
2772
+ if (!resumeState) {
2773
+ return;
2774
+ }
2775
+ paypalResumeAttempted.current = true;
2776
+ void (async () => {
2777
+ setModeError(null);
2778
+ setModeOverlayError(null);
2779
+ setModeOverlayStatus("processing");
2780
+ setConfirmProcessing(true);
2781
+ try {
2782
+ if (params.get("redirect_status") === "failed") {
2783
+ throw Object.assign(
2784
+ new import_shared7.FloPayError("PayPal payment was declined. Please try again.", "api_error"),
2785
+ { checkoutMethod: "paypal" }
2786
+ );
2787
+ }
2788
+ const {
2789
+ flopay: resumeFlopay,
2790
+ paypalFlopay: resumePaypalFlopay
2791
+ } = await loadSavedPaymentProviders({
2792
+ publishableKey: resumeState.publishableKey,
2793
+ paypalPublishableKey: resumeState.paypalPublishableKey,
2794
+ billingApiUrl: resolvedBillingUrl,
2795
+ locale
2796
+ });
2797
+ const paypalStripe = (resumePaypalFlopay ?? resumeFlopay).getRawProvider();
2798
+ if (!paypalStripe) {
2799
+ throw Object.assign(
2800
+ new import_shared7.FloPayError("PayPal is not available.", "api_error"),
2801
+ { checkoutMethod: "paypal" }
2802
+ );
2803
+ }
2804
+ const { paymentIntent, error } = await paypalStripe.retrievePaymentIntent(clientSecret);
2805
+ if (error) {
2806
+ throw Object.assign(
2807
+ new import_shared7.FloPayError(
2808
+ error.message ?? "Failed to retrieve PayPal payment status.",
2809
+ "api_error",
2810
+ { code: error.code }
2811
+ ),
2812
+ { checkoutMethod: "paypal" }
2813
+ );
2814
+ }
2815
+ const resultStatus = mapPayPalIntentStatusToPaymentResult(paymentIntent?.status);
2816
+ if (!paymentIntent || resultStatus === "failed") {
2817
+ throw Object.assign(
2818
+ new import_shared7.FloPayError("PayPal payment was not completed. Please try again.", "api_error"),
2819
+ { checkoutMethod: "paypal" }
2820
+ );
2821
+ }
2822
+ const paymentMethodId = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
2823
+ setModeOverlayStatus("success");
2824
+ await sleep(PROCESSING_OVERLAY_SUCCESS_DELAY_MS);
2825
+ onCompleteRef.current?.({
2826
+ status: resultStatus,
2827
+ paymentIntentId: paymentIntent.id,
2828
+ paymentMethodId,
2829
+ checkoutMethod: "paypal"
2830
+ });
2831
+ } catch (err) {
2832
+ const floPayErr = normalizeSavedPaymentError(err);
2833
+ const method = floPayErr.checkoutMethod ?? "paypal";
2834
+ setModeError(floPayErr.message);
2835
+ setModeOverlayError(floPayErr.message);
2836
+ onErrorRef.current?.(floPayErr);
2837
+ emitDecline(method, floPayErr, {
2838
+ code: floPayErr.code,
2839
+ declineCode: floPayErr.declineCode
2840
+ });
2841
+ setModeOverlayStatus("error");
2842
+ await sleep(PROCESSING_OVERLAY_ERROR_DELAY_MS);
2843
+ } finally {
2844
+ clearPayPalResumeState();
2845
+ clearPayPalRedirectParams();
2846
+ setModeOverlayStatus(null);
2847
+ setModeOverlayError(null);
2848
+ setConfirmProcessing(false);
2849
+ }
2850
+ })();
2851
+ }, [emitDecline, locale, normalizeSavedPaymentError, resolvedBillingUrl]);
2639
2852
  const inflightRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
2640
2853
  const initializedHashRef = (0, import_react8.useRef)(null);
2641
2854
  function hashCreateParams(params) {
@@ -2734,7 +2947,7 @@ function FloPayCheckout({
2734
2947
  const {
2735
2948
  publishableKey,
2736
2949
  paypalPublishableKey
2737
- } = resolveSavedPaymentPublishableKeys(realResult, fallbackPublishableKey);
2950
+ } = resolveSavedPaymentPublishableKeys(realResult);
2738
2951
  const {
2739
2952
  flopay: instance,
2740
2953
  paypalFlopay: paypalInstance
@@ -2757,7 +2970,7 @@ function FloPayCheckout({
2757
2970
  }
2758
2971
  return resolved;
2759
2972
  },
2760
- [fallbackPublishableKey, locale, resolvedBillingUrl]
2973
+ [locale, resolvedBillingUrl]
2761
2974
  );
2762
2975
  const handleInlineSessionPatch = (0, import_react8.useCallback)(async (patch) => {
2763
2976
  if (!hasInlineSessionPatchData(patch) || cardBootstrapPending) {
@@ -2805,6 +3018,7 @@ function FloPayCheckout({
2805
3018
  try {
2806
3019
  const resolved = await bootstrapInlineSession();
2807
3020
  const resolvedSession = resolved.result.data.session ?? null;
3021
+ savedPaymentKeysRef.current = resolveSavedPaymentPublishableKeys(resolved.result);
2808
3022
  if (!cancelled && shouldAutoProcessInlineSession && resolvedSession) {
2809
3023
  autoCheckoutAttempted.current = true;
2810
3024
  await runSavedPaymentFlow(resolvedSession, {
@@ -2903,7 +3117,11 @@ function FloPayCheckout({
2903
3117
  const {
2904
3118
  publishableKey,
2905
3119
  paypalPublishableKey
2906
- } = resolveSavedPaymentPublishableKeys(result, fallbackPublishableKey);
3120
+ } = resolveSavedPaymentPublishableKeys(result);
3121
+ savedPaymentKeysRef.current = {
3122
+ publishableKey,
3123
+ paypalPublishableKey
3124
+ };
2907
3125
  const {
2908
3126
  flopay: instance,
2909
3127
  paypalFlopay: paypalInstance
@@ -3456,9 +3674,11 @@ function CheckoutFormInner({
3456
3674
  [onDecline]
3457
3675
  );
3458
3676
  const processPaymentInternal = (0, import_react9.useCallback)(
3459
- async (tokenizedBody) => {
3677
+ async (tokenizedBody, completionPaymentMethodId) => {
3460
3678
  setProcessing(true);
3461
3679
  updateError(null);
3680
+ const resolvedCompletionPaymentMethodId = completionPaymentMethodId ?? resolveTokenizedPaymentMethodId(tokenizedBody);
3681
+ const requestTokenizedBody = tokenizedBody.originalPaymentMethodId ? { ...tokenizedBody, originalPaymentMethodId: void 0 } : tokenizedBody;
3462
3682
  try {
3463
3683
  const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
3464
3684
  method: "POST",
@@ -3468,7 +3688,7 @@ function CheckoutFormInner({
3468
3688
  },
3469
3689
  body: JSON.stringify({
3470
3690
  sessionId,
3471
- tokenizedData: tokenizedBody,
3691
+ tokenizedData: requestTokenizedBody,
3472
3692
  accountData: {
3473
3693
  userId: userId ?? "",
3474
3694
  email: email ?? "",
@@ -3482,7 +3702,8 @@ function CheckoutFormInner({
3482
3702
  onComplete?.({
3483
3703
  status: "succeeded",
3484
3704
  paymentIntentId: tokenizedBody.threeDSecureActionResultTokenId,
3485
- paymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
3705
+ paymentMethodId: resolvedCompletionPaymentMethodId,
3706
+ checkoutMethod: tokenizedBody.isPaypal ? "paypal" : "card"
3486
3707
  });
3487
3708
  return;
3488
3709
  }
@@ -3495,9 +3716,14 @@ function CheckoutFormInner({
3495
3716
  }
3496
3717
  setIs3DSActive(true);
3497
3718
  try {
3719
+ const retryFullName = `${firstName ?? ""} ${lastName ?? ""}`.trim();
3498
3720
  const result = await flopay.confirmPayment({
3499
3721
  clientSecret: secret,
3500
- returnUrl: window.location.href
3722
+ returnUrl: window.location.href,
3723
+ billingDetails: {
3724
+ ...email ? { email } : {},
3725
+ ...retryFullName ? { name: retryFullName } : {}
3726
+ }
3501
3727
  });
3502
3728
  if (result.error) {
3503
3729
  updateError(result.error.message);
@@ -3509,9 +3735,8 @@ function CheckoutFormInner({
3509
3735
  await processPaymentInternal({
3510
3736
  id: result.paymentIntentId,
3511
3737
  type: "card",
3512
- threeDSecureActionResultTokenId: result.paymentIntentId,
3513
- originalPaymentMethodId: resolveTokenizedPaymentMethodId(tokenizedBody)
3514
- });
3738
+ threeDSecureActionResultTokenId: result.paymentIntentId
3739
+ }, resolvedCompletionPaymentMethodId);
3515
3740
  }
3516
3741
  } finally {
3517
3742
  setIs3DSActive(false);
@@ -3838,33 +4063,23 @@ function PayPalButton({
3838
4063
  if (!sessionId || !email) {
3839
4064
  throw new import_shared9.FloPayError("Missing sessionId or email for PayPal payment", "validation_error");
3840
4065
  }
3841
- const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
3842
- method: "POST",
3843
- headers: { "Content-Type": "application/json" },
3844
- body: JSON.stringify({
3845
- sessionId,
3846
- email,
3847
- paymentMethodType: "paypal",
3848
- isPaypal: "true"
3849
- })
3850
- });
3851
- if (!intentResponse.ok) throw new import_shared9.FloPayError("Failed to create payment intent", "api_error");
3852
- const intentJson = await intentResponse.json();
3853
- const intentClientSecret = intentJson.data?.id;
3854
- if (!intentClientSecret) throw new import_shared9.FloPayError("No client_secret in response", "api_error");
3855
- const result = await flopay.confirmPayment({
3856
- clientSecret: intentClientSecret,
4066
+ const result = await flopay.confirmPayPalPayment({
4067
+ billingApiUrl: baseUrl,
4068
+ sessionId,
4069
+ email,
3857
4070
  returnUrl: window.location.href
3858
4071
  });
3859
- if (result.status === "succeeded" || result.status === "processing") {
4072
+ if (result.error) {
4073
+ onErrorChange?.(result.error.message);
4074
+ return;
4075
+ }
4076
+ if (result.paymentIntentId && (result.status === "succeeded" || result.status === "processing" || result.status === "requires_capture")) {
3860
4077
  dispatchTokenizedBody({
3861
- id: result.paymentIntentId,
4078
+ id: result.paymentMethodId ?? result.paymentIntentId,
3862
4079
  type: "card",
3863
4080
  threeDSecureActionResultTokenId: result.paymentIntentId,
3864
4081
  isPaypal: true
3865
4082
  });
3866
- } else if (result.error) {
3867
- onErrorChange?.(result.error.message);
3868
4083
  }
3869
4084
  } catch (err) {
3870
4085
  onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
@@ -3924,7 +4139,7 @@ var import_js3 = require("@flopay/js");
3924
4139
  var import_shared10 = require("@flopay/shared");
3925
4140
  var import_jsx_runtime9 = require("react/jsx-runtime");
3926
4141
  var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT3 = 44;
3927
- var PAYPAL_RESUME_STORAGE_KEY = "flopay_automatic_payment_button_resume";
4142
+ var PAYPAL_RESUME_STORAGE_KEY2 = "flopay_automatic_payment_button_resume";
3928
4143
  function sleep2(ms) {
3929
4144
  return new Promise((resolve) => setTimeout(resolve, ms));
3930
4145
  }
@@ -3937,42 +4152,44 @@ function coerceError(err, fallbackMessage) {
3937
4152
  "api_error"
3938
4153
  );
3939
4154
  }
3940
- function canUseStorage() {
4155
+ function canUseStorage2() {
3941
4156
  return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
3942
4157
  }
3943
4158
  function shouldShowFallbackCheckout(error, sessionId) {
3944
4159
  if (!sessionId) return false;
3945
4160
  if (error.type === "validation_error") return false;
3946
- if (error.checkoutMethod && error.checkoutMethod !== "card") return false;
4161
+ if (error.checkoutMethod && error.checkoutMethod !== "card" && error.checkoutMethod !== "paypal") {
4162
+ return false;
4163
+ }
3947
4164
  return true;
3948
4165
  }
3949
- function readPayPalResumeState() {
3950
- if (!canUseStorage()) return null;
4166
+ function readPayPalResumeState2() {
4167
+ if (!canUseStorage2()) return null;
3951
4168
  try {
3952
- const raw = window.sessionStorage.getItem(PAYPAL_RESUME_STORAGE_KEY);
4169
+ const raw = window.sessionStorage.getItem(PAYPAL_RESUME_STORAGE_KEY2);
3953
4170
  if (!raw) return null;
3954
4171
  return JSON.parse(raw);
3955
4172
  } catch {
3956
4173
  return null;
3957
4174
  }
3958
4175
  }
3959
- function persistPayPalResumeState(state) {
3960
- if (!canUseStorage()) return;
4176
+ function persistPayPalResumeState2(state) {
4177
+ if (!canUseStorage2()) return;
3961
4178
  try {
3962
- window.sessionStorage.setItem(PAYPAL_RESUME_STORAGE_KEY, JSON.stringify(state));
4179
+ window.sessionStorage.setItem(PAYPAL_RESUME_STORAGE_KEY2, JSON.stringify(state));
3963
4180
  } catch (error) {
3964
4181
  console.warn("[FloPayAutomaticPaymentButton] Failed to persist PayPal resume state.", error);
3965
4182
  }
3966
4183
  }
3967
- function clearPayPalResumeState() {
3968
- if (!canUseStorage()) return;
4184
+ function clearPayPalResumeState2() {
4185
+ if (!canUseStorage2()) return;
3969
4186
  try {
3970
- window.sessionStorage.removeItem(PAYPAL_RESUME_STORAGE_KEY);
4187
+ window.sessionStorage.removeItem(PAYPAL_RESUME_STORAGE_KEY2);
3971
4188
  } catch (error) {
3972
4189
  console.warn("[FloPayAutomaticPaymentButton] Failed to clear PayPal resume state.", error);
3973
4190
  }
3974
4191
  }
3975
- function clearPayPalRedirectParams() {
4192
+ function clearPayPalRedirectParams2() {
3976
4193
  if (typeof window === "undefined") return;
3977
4194
  const url = new URL(window.location.href);
3978
4195
  const keys = ["payment_intent", "payment_intent_client_secret", "redirect_status"];
@@ -4014,6 +4231,7 @@ function FloPayAutomaticPaymentButton({
4014
4231
  sessionId,
4015
4232
  createSession,
4016
4233
  paymentMethodId,
4234
+ checkoutMethod,
4017
4235
  clientId,
4018
4236
  items,
4019
4237
  subscriptions,
@@ -4025,7 +4243,6 @@ function FloPayAutomaticPaymentButton({
4025
4243
  utmMetadata,
4026
4244
  billingApiUrl,
4027
4245
  locale,
4028
- fallbackPublishableKey,
4029
4246
  buttonsTheme,
4030
4247
  buttonsStyles: stylesOverride,
4031
4248
  onSuccess,
@@ -4074,9 +4291,10 @@ function FloPayAutomaticPaymentButton({
4074
4291
  const automaticPaymentToken = (0, import_react11.useMemo)(
4075
4292
  () => paymentMethodId ? {
4076
4293
  id: paymentMethodId,
4077
- type: "card"
4294
+ type: "card",
4295
+ ...checkoutMethod === "paypal" ? { isPaypal: true } : {}
4078
4296
  } : void 0,
4079
- [paymentMethodId]
4297
+ [checkoutMethod, paymentMethodId]
4080
4298
  );
4081
4299
  const isMountedRef = (0, import_react11.useRef)(true);
4082
4300
  const resumeAttemptedRef = (0, import_react11.useRef)(false);
@@ -4158,13 +4376,14 @@ function FloPayAutomaticPaymentButton({
4158
4376
  let paymentResult = null;
4159
4377
  const redirectResult = getRedirectResultFromCheckoutProcessError(apiResult.autoProcessingError);
4160
4378
  const shouldTreatCreateSessionFlowAsServerAutoAttempt = options?.fromCreateSession && (apiResult.autoProcessingAttempted === true || !!apiResult.autoProcessingError || !!redirectResult);
4379
+ const shouldRetryPayPalClientSide = checkoutMethod === "paypal" && automaticPaymentToken?.isPaypal === true && options?.fromCreateSession === true;
4161
4380
  if (redirectResult) {
4162
4381
  const {
4163
4382
  publishableKey,
4164
4383
  paypalPublishableKey
4165
- } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4384
+ } = resolveSavedPaymentPublishableKeys(apiResult);
4166
4385
  if (redirectResult.type === "paypal_redirect_required") {
4167
- persistPayPalResumeState({
4386
+ persistPayPalResumeState2({
4168
4387
  sessionId: session.id || resolvedSessionId,
4169
4388
  publishableKey,
4170
4389
  paypalPublishableKey
@@ -4188,9 +4407,9 @@ function FloPayAutomaticPaymentButton({
4188
4407
  session
4189
4408
  });
4190
4409
  if (redirectResult.type === "paypal_redirect_required") {
4191
- clearPayPalResumeState();
4410
+ clearPayPalResumeState2();
4192
4411
  }
4193
- } else if (apiResult.autoProcessingError) {
4412
+ } else if (apiResult.autoProcessingError && !shouldRetryPayPalClientSide) {
4194
4413
  throw checkoutProcessErrorToFloPayError(
4195
4414
  apiResult.autoProcessingError,
4196
4415
  "Automatic payment failed. Please try again.",
@@ -4198,7 +4417,7 @@ function FloPayAutomaticPaymentButton({
4198
4417
  checkoutMethod: apiResult.autoProcessingError.checkoutMethod
4199
4418
  }
4200
4419
  );
4201
- } else if (shouldTreatCreateSessionFlowAsServerAutoAttempt) {
4420
+ } else if (shouldTreatCreateSessionFlowAsServerAutoAttempt && !shouldRetryPayPalClientSide) {
4202
4421
  throw checkoutProcessErrorToFloPayError(
4203
4422
  {
4204
4423
  type: "unknown",
@@ -4218,9 +4437,9 @@ function FloPayAutomaticPaymentButton({
4218
4437
  const {
4219
4438
  publishableKey,
4220
4439
  paypalPublishableKey
4221
- } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4440
+ } = resolveSavedPaymentPublishableKeys(apiResult);
4222
4441
  if (result.type === "paypal_redirect_required") {
4223
- persistPayPalResumeState({
4442
+ persistPayPalResumeState2({
4224
4443
  sessionId: session.id || resolvedSessionId,
4225
4444
  publishableKey,
4226
4445
  paypalPublishableKey
@@ -4244,17 +4463,19 @@ function FloPayAutomaticPaymentButton({
4244
4463
  session
4245
4464
  });
4246
4465
  if (result.type === "paypal_redirect_required") {
4247
- clearPayPalResumeState();
4466
+ clearPayPalResumeState2();
4248
4467
  }
4249
4468
  }
4250
4469
  }
4251
4470
  await showSuccess({
4252
4471
  result: paymentResult ? {
4253
4472
  ...paymentResult,
4254
- paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId
4473
+ paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId,
4474
+ checkoutMethod: paymentResult.checkoutMethod ?? checkoutMethod
4255
4475
  } : {
4256
4476
  status: "succeeded",
4257
- paymentMethodId: paymentMethodId ?? void 0
4477
+ paymentMethodId: paymentMethodId ?? void 0,
4478
+ checkoutMethod
4258
4479
  },
4259
4480
  session,
4260
4481
  sessionId: session.id || resolvedSessionId,
@@ -4269,7 +4490,7 @@ function FloPayAutomaticPaymentButton({
4269
4490
  const {
4270
4491
  publishableKey,
4271
4492
  paypalPublishableKey
4272
- } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4493
+ } = resolveSavedPaymentPublishableKeys(apiResult);
4273
4494
  const {
4274
4495
  flopay
4275
4496
  } = await loadSavedPaymentProviders({
@@ -4288,7 +4509,8 @@ function FloPayAutomaticPaymentButton({
4288
4509
  await showSuccess({
4289
4510
  result: {
4290
4511
  ...paymentResult,
4291
- paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId
4512
+ paymentMethodId: paymentResult.paymentMethodId ?? paymentMethodId,
4513
+ checkoutMethod: paymentResult.checkoutMethod ?? checkoutMethod
4292
4514
  },
4293
4515
  session,
4294
4516
  sessionId: fallbackSessionId,
@@ -4311,7 +4533,7 @@ function FloPayAutomaticPaymentButton({
4311
4533
  }
4312
4534
  }
4313
4535
  }, [
4314
- fallbackPublishableKey,
4536
+ checkoutMethod,
4315
4537
  locale,
4316
4538
  automaticPaymentToken,
4317
4539
  paymentMethodId,
@@ -4423,7 +4645,7 @@ function FloPayAutomaticPaymentButton({
4423
4645
  if (!clientSecret) {
4424
4646
  return;
4425
4647
  }
4426
- const resumeState = readPayPalResumeState();
4648
+ const resumeState = readPayPalResumeState2();
4427
4649
  if (!resumeState) {
4428
4650
  return;
4429
4651
  }
@@ -4466,9 +4688,16 @@ function FloPayAutomaticPaymentButton({
4466
4688
  { checkoutMethod: "paypal" }
4467
4689
  );
4468
4690
  }
4469
- if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded" || paymentIntent.status === "processing")) {
4691
+ const resultStatus = mapPayPalIntentStatusToPaymentResult(paymentIntent?.status);
4692
+ if (paymentIntent && resultStatus !== "failed") {
4693
+ const paymentMethodId2 = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
4470
4694
  await showSuccess({
4471
- result: { status: "succeeded", paymentIntentId: paymentIntent.id },
4695
+ result: {
4696
+ status: resultStatus,
4697
+ paymentIntentId: paymentIntent.id,
4698
+ paymentMethodId: paymentMethodId2,
4699
+ checkoutMethod: "paypal"
4700
+ },
4472
4701
  session: null,
4473
4702
  sessionId: resumeState.sessionId,
4474
4703
  autoCompleted: false
@@ -4486,8 +4715,8 @@ function FloPayAutomaticPaymentButton({
4486
4715
  method: floPayErr.checkoutMethod ?? "paypal"
4487
4716
  });
4488
4717
  } finally {
4489
- clearPayPalResumeState();
4490
- clearPayPalRedirectParams();
4718
+ clearPayPalResumeState2();
4719
+ clearPayPalRedirectParams2();
4491
4720
  if (isMountedRef.current) {
4492
4721
  setOverlayStatus(null);
4493
4722
  setOverlayError(null);
@@ -4601,7 +4830,6 @@ function FloPayAutomaticPaymentButton({
4601
4830
  sessionId: fallbackSession.sessionId,
4602
4831
  checkoutMode: "full",
4603
4832
  billingApiUrl: resolvedBillingUrl,
4604
- fallbackPublishableKey,
4605
4833
  initialErrorMessage: fallbackSession.errorMessage,
4606
4834
  cardTitleContent: null,
4607
4835
  showSecurityFooter: false,