@flopay/react 1.1.0 → 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -215,7 +215,8 @@ Use `onButtonClick` to track button interactions, `onDecline` to track declines/
215
215
  layout="buttons"
216
216
  createSession={{
217
217
  clientId: 'your-client-id',
218
- items: [{ providerItemId: 'product-1', providerItemName: 'Widget', totalAmount: 29.99, currency: 'EUR' }],
218
+ currency: 'EUR',
219
+ items: [{ providerItemId: 'product-1', providerItemName: 'Widget', totalAmount: 29.99 }],
219
220
  account: { userId: 'user_1', email: 'test@email.com' },
220
221
  successUrl: '/success',
221
222
  cancelUrl: '/cancel',
@@ -258,7 +259,8 @@ Skip the backend API route — create the session directly in the component:
258
259
  layout="buttons"
259
260
  createSession={{
260
261
  clientId: 'your-client-id',
261
- items: [{ providerItemId: 'product-1', providerItemName: 'Widget', totalAmount: 29.99, currency: 'EUR' }],
262
+ currency: 'EUR',
263
+ items: [{ providerItemId: 'product-1', providerItemName: 'Widget', totalAmount: 29.99 }],
262
264
  account: { userId: 'user_1', email: 'user@example.com', firstName: 'Jane', lastName: 'Doe' },
263
265
  successUrl: '/success',
264
266
  cancelUrl: '/cancel',
@@ -269,6 +271,8 @@ Skip the backend API route — create the session directly in the component:
269
271
 
270
272
  The component POSTs to the billing API, gets the full session back, and renders the form — zero backend code needed.
271
273
 
274
+ Session-level `currency` is now **required**. The backend (#760) enforces `@IsNotEmpty` on the field; the SDK pre-validates and throws `FloPayError({ type: 'validation_error', code: 'CurrencyRequired' })` before issuing the request when neither the session nor any item/subscription/product carries a currency. Pass `currency` at the top of `createSession` (preferred), or rely on the legacy fallback to the first item/subscription/product `currency`.
275
+
272
276
  When you use `onBeforeButtonClick` with `createSession`, any returned `InlineSessionPatch` is merged into the draft session params before the selected buttons-layout flow continues. That lets you add tracking data or update account fields just in time without pre-creating a separate backend session.
273
277
 
274
278
  ### Advanced: Manual Provider Setup
@@ -396,6 +400,8 @@ The SDK supports two PayPal paths, selected per-session based on what the billin
396
400
 
397
401
  Renderer selection is mutually exclusive per session — direct PayPal takes priority over Stripe-rendered PayPal. Consumer props such as `showPayPal` continue to gate visibility on the client side.
398
402
 
403
+ **PayPal-only sessions:** When the backend advertises only `gateways.paypal` (no `gateways.stripe`), `FloPayCheckout` skips Stripe Elements entirely and renders `<DirectPayPalButton>` as the sole payment surface. Sessions that advertise no supported gateway at all throw a `validation_error` explaining the expected shape.
404
+
399
405
  The SDK exposes the relevant pieces in three ways:
400
406
 
401
407
  - **`SplitCardForm`** / **`FloPayCheckout`**: pick the renderer automatically based on `gateways.*`. No additional configuration needed.
package/dist/index.cjs CHANGED
@@ -495,15 +495,37 @@ function mergeInlineSessionPatch(params, patch) {
495
495
  };
496
496
  }
497
497
  function buildSyntheticSession(params, checkoutModeOverride) {
498
- const totalAmount = [
499
- ...(params.items ?? []).map((item) => item.overrideAmount ?? item.totalAmount ?? 0),
500
- ...(params.subscriptions ?? []).map((subscription) => subscription.overrideAmount ?? subscription.totalAmount ?? 0)
501
- ].reduce((sum, value) => sum + value, 0);
502
- const currency = params.currency ?? params.items?.find((i) => i.currency)?.currency ?? params.subscriptions?.find((s) => s.currency)?.currency ?? "USD";
498
+ const inputProducts = params.products ?? [
499
+ ...(params.subscriptions ?? []).map((s) => ({
500
+ type: "subscription",
501
+ code: s.code ?? s.providerPlanId,
502
+ name: s.subscriptionName ?? s.providerPlanName ?? s.code ?? s.providerPlanId ?? null,
503
+ quantity: s.quantity ?? 1,
504
+ totalAmount: s.totalAmount,
505
+ overrideAmount: s.overrideAmount,
506
+ currency: s.currency,
507
+ metadata: s.metadata
508
+ })),
509
+ ...(params.items ?? []).map((i) => ({
510
+ type: "item",
511
+ code: i.code ?? i.providerItemId,
512
+ name: i.itemName ?? i.providerItemName ?? i.code ?? i.providerItemId ?? null,
513
+ quantity: i.quantity ?? 1,
514
+ totalAmount: i.totalAmount,
515
+ overrideAmount: i.overrideAmount,
516
+ currency: i.currency,
517
+ metadata: i.metadata
518
+ }))
519
+ ];
520
+ const totalAmount = inputProducts.reduce(
521
+ (sum, p) => sum + (p.overrideAmount ?? p.totalAmount ?? 0),
522
+ 0
523
+ );
524
+ const currency = params.currency ?? inputProducts.find((p) => p.currency)?.currency ?? "USD";
503
525
  return {
504
526
  id: "",
505
527
  clientSecret: "",
506
- mode: "payment",
528
+ mode: inputProducts.some((p) => p.type === "subscription") ? "subscription" : "payment",
507
529
  amount: Math.round(totalAmount * 100),
508
530
  currency,
509
531
  status: "open",
@@ -521,40 +543,18 @@ function buildSyntheticSession(params, checkoutModeOverride) {
521
543
  successUrl: params.successUrl,
522
544
  cancelUrl: params.cancelUrl,
523
545
  checkoutMode: checkoutModeOverride ?? params.checkoutMode ?? "full",
524
- items: (params.items ?? []).map((item, idx) => {
525
- const code = item.code ?? item.providerItemId;
526
- const itemName = item.itemName ?? item.providerItemName ?? code;
527
- return {
528
- uuid: `synthetic-item-${idx}`,
529
- checkoutSessionId: "",
530
- code,
531
- providerItemId: code,
532
- itemName,
533
- providerItemName: itemName,
534
- quantity: item.quantity ?? 1,
535
- totalAmount: item.totalAmount,
536
- overrideAmount: item.overrideAmount ?? null,
537
- currency: item.currency ?? currency,
538
- metadata: item.metadata ?? null
539
- };
540
- }),
541
- subscriptions: (params.subscriptions ?? []).map((subscription, idx) => {
542
- const code = subscription.code ?? subscription.providerPlanId;
543
- const subscriptionName = subscription.subscriptionName ?? subscription.providerPlanName ?? code;
544
- return {
545
- uuid: `synthetic-sub-${idx}`,
546
- checkoutSessionId: "",
547
- code,
548
- providerPlanId: code,
549
- subscriptionName,
550
- providerPlanName: subscriptionName,
551
- quantity: subscription.quantity ?? 1,
552
- totalAmount: subscription.totalAmount,
553
- overrideAmount: subscription.overrideAmount ?? null,
554
- currency: subscription.currency ?? currency,
555
- metadata: subscription.metadata ?? null
556
- };
557
- })
546
+ products: inputProducts.map((p, idx) => ({
547
+ uuid: `synthetic-${p.type}-${idx}`,
548
+ checkoutSessionId: "",
549
+ type: p.type,
550
+ code: p.code,
551
+ name: p.name ?? null,
552
+ quantity: p.quantity ?? 1,
553
+ totalAmount: p.totalAmount,
554
+ overrideAmount: p.overrideAmount ?? null,
555
+ currency: p.currency ?? currency,
556
+ metadata: p.metadata ?? null
557
+ }))
558
558
  };
559
559
  }
560
560
  function mergeAccountPatch(base, patch) {
@@ -582,9 +582,25 @@ function readString(payload, key) {
582
582
  const value = payload?.[key];
583
583
  return typeof value === "string" && value.trim() ? value : void 0;
584
584
  }
585
+ var FRIENDLY_MESSAGE_OVERRIDES = [
586
+ {
587
+ match: /^paypal authorization required\.?$/i,
588
+ replacement: "For your additional security, please re-authenticate this payment via PayPal."
589
+ }
590
+ ];
591
+ function applyFriendlyMessageOverride(message) {
592
+ if (typeof message !== "string") return message ?? void 0;
593
+ const trimmed = message.trim();
594
+ if (!trimmed) return message;
595
+ for (const { match, replacement } of FRIENDLY_MESSAGE_OVERRIDES) {
596
+ if (match.test(trimmed)) return replacement;
597
+ }
598
+ return message;
599
+ }
585
600
  function buildFloPayApiError(payload, fallbackMessage) {
586
601
  const nestedError = isRecord(payload?.error) ? payload.error : null;
587
- const message = readString(payload, "message") ?? readString(nestedError, "message") ?? fallbackMessage;
602
+ const rawMessage = readString(payload, "message") ?? readString(nestedError, "message") ?? fallbackMessage;
603
+ const message = applyFriendlyMessageOverride(rawMessage) ?? rawMessage;
588
604
  const code = readString(payload, "code") ?? readString(payload, "gatewayErrorCode") ?? readString(nestedError, "code");
589
605
  const declineCode = readString(payload, "declineCode") ?? readString(payload, "gatewayDeclineReason") ?? readString(payload, "decline_code") ?? readString(nestedError, "decline_code");
590
606
  return new import_shared3.FloPayError(message, "api_error", {
@@ -775,7 +791,7 @@ function DirectPayPalButton({
775
791
  if (!clientId) {
776
792
  appendDebug("FAIL: clientId empty \u2014 gateway misconfigured");
777
793
  setFailed(true);
778
- onErrorChangeRef.current?.("Direct PayPal gateway is misconfigured.");
794
+ console.error("[FloPay] DirectPayPal: clientId empty \u2014 gateway misconfigured");
779
795
  return;
780
796
  }
781
797
  if (!containerRef.current) {
@@ -886,7 +902,8 @@ function DirectPayPalButton({
886
902
  const forwardError = (message) => {
887
903
  if (cancelled) return;
888
904
  if (isZoidLifecycleMessage(message)) return;
889
- onErrorChangeRef.current?.(message);
905
+ const friendly = applyFriendlyMessageOverride(message) ?? message;
906
+ onErrorChangeRef.current?.(friendly);
890
907
  };
891
908
  const markRenderFailed = (message) => {
892
909
  if (cancelled) return;
@@ -895,7 +912,7 @@ function DirectPayPalButton({
895
912
  return;
896
913
  }
897
914
  setFailed(true);
898
- forwardError(message);
915
+ console.error("[FloPay] DirectPayPal load/render failure:", message);
899
916
  };
900
917
  setFailed(false);
901
918
  const dispatchTokenizedBody = async (body) => {
@@ -933,14 +950,16 @@ function DirectPayPalButton({
933
950
  return;
934
951
  }
935
952
  const json = await response.json().catch(() => null);
936
- const message = json?.["message"] ?? "PayPal payment failed.";
953
+ const rawMessage = json?.["message"] ?? "PayPal payment failed.";
954
+ const message = applyFriendlyMessageOverride(rawMessage) ?? rawMessage;
937
955
  forwardError(message);
938
956
  onDeclineRef.current?.(buildDeclineEvent("paypal", message, {
939
957
  code: json?.["code"],
940
958
  declineCode: json?.["declineCode"]
941
959
  }));
942
960
  } catch (err) {
943
- const message = err instanceof Error ? err.message : "PayPal payment failed.";
961
+ const rawMessage = err instanceof Error ? err.message : "PayPal payment failed.";
962
+ const message = applyFriendlyMessageOverride(rawMessage) ?? rawMessage;
944
963
  forwardError(message);
945
964
  onDeclineRef.current?.(buildDeclineEvent("paypal", message));
946
965
  }
@@ -3090,9 +3109,11 @@ function getRedirectResultFromCheckoutProcessError(error) {
3090
3109
  }
3091
3110
  function checkoutProcessErrorToFloPayError(error, fallbackMessage = "Payment failed. Please try again.", options) {
3092
3111
  const checkoutMethod = options?.checkoutMethod ?? error?.checkoutMethod ?? (error?.type === "paypal_redirect_required" ? "paypal" : "card");
3112
+ const rawMessage = error?.message ?? fallbackMessage;
3113
+ const message = applyFriendlyMessageOverride(rawMessage) ?? rawMessage;
3093
3114
  return Object.assign(
3094
3115
  new import_shared7.FloPayError(
3095
- error?.message ?? fallbackMessage,
3116
+ message,
3096
3117
  "api_error",
3097
3118
  {
3098
3119
  code: error?.gatewayErrorCode
@@ -3549,21 +3570,36 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
3549
3570
  }
3550
3571
  function normalizeSavedPaymentError(err) {
3551
3572
  if (err instanceof import_shared7.FloPayError) {
3573
+ const friendly = applyFriendlyMessageOverride(err.message);
3574
+ if (friendly && friendly !== err.message) {
3575
+ return Object.assign(
3576
+ new import_shared7.FloPayError(friendly, err.type, {
3577
+ code: err.code,
3578
+ declineCode: err.declineCode,
3579
+ param: err.param,
3580
+ statusCode: err.statusCode
3581
+ }),
3582
+ { checkoutMethod: err.checkoutMethod }
3583
+ );
3584
+ }
3552
3585
  return err;
3553
3586
  }
3554
- return new import_shared7.FloPayError(
3555
- err instanceof Error ? err.message : "Payment failed. Please try again.",
3556
- "api_error"
3557
- );
3587
+ const rawMessage = err instanceof Error ? err.message : "Payment failed. Please try again.";
3588
+ const message = applyFriendlyMessageOverride(rawMessage) ?? rawMessage;
3589
+ return new import_shared7.FloPayError(message, "api_error");
3558
3590
  }
3559
3591
  function resolveSavedPaymentPublishableKeys(unified) {
3560
3592
  const publishableKey = unified.data.stripe?.publishableKey;
3561
- if (!publishableKey) {
3593
+ const hasDirectPaypal = Boolean(unified.data.paypal?.publishableKey);
3594
+ if (!publishableKey && !hasDirectPaypal) {
3562
3595
  throw new import_shared7.FloPayError(
3563
- "No publishable key found in the checkout session. Ensure the session advertises gateways.stripe.",
3596
+ "Session advertises no supported gateways (expected `gateways.stripe` and/or `gateways.paypal`).",
3564
3597
  "validation_error"
3565
3598
  );
3566
3599
  }
3600
+ if (!publishableKey) {
3601
+ return {};
3602
+ }
3567
3603
  return {
3568
3604
  publishableKey,
3569
3605
  // Direct-PayPal sessions can still use Stripe's PayPal Element for the
@@ -3580,6 +3616,9 @@ async function loadSavedPaymentProviders({
3580
3616
  billingApiUrl,
3581
3617
  locale
3582
3618
  }) {
3619
+ if (!publishableKey) {
3620
+ return { flopay: null, paypalFlopay: null };
3621
+ }
3583
3622
  const needsSeparatePaypal = Boolean(paypalPublishableKey) && paypalPublishableKey !== publishableKey;
3584
3623
  const [instance, paypalInstanceOrError] = await Promise.all([
3585
3624
  (0, import_js3.loadFloPay)(publishableKey, {
@@ -3616,6 +3655,11 @@ function resolveDirectPaypalConfig(unified) {
3616
3655
  environment: unified?.data.paypal?.environment
3617
3656
  };
3618
3657
  }
3658
+ function isPayPalOnlyUnified(unified) {
3659
+ if (!unified) return false;
3660
+ if (unified.data.stripe?.publishableKey) return false;
3661
+ return Boolean(resolveDirectPaypalConfig(unified)?.clientId);
3662
+ }
3619
3663
  function canUseStorage() {
3620
3664
  return typeof window !== "undefined" && typeof window.sessionStorage !== "undefined";
3621
3665
  }
@@ -3794,7 +3838,7 @@ function FloPayCheckout({
3794
3838
  const redirectResult = getRedirectResultFromCheckoutProcessError(options?.initialAutoProcessingError);
3795
3839
  let paymentResult;
3796
3840
  if (redirectResult) {
3797
- if (redirectResult.type === "paypal_redirect_required" && savedPaymentKeysRef.current) {
3841
+ if (redirectResult.type === "paypal_redirect_required" && savedPaymentKeysRef.current?.publishableKey) {
3798
3842
  persistPayPalResumeState({
3799
3843
  sessionId: activeSessionId2,
3800
3844
  publishableKey: savedPaymentKeysRef.current.publishableKey,
@@ -3846,7 +3890,7 @@ function FloPayCheckout({
3846
3890
  if (result.type === "success") {
3847
3891
  paymentResult = result.result;
3848
3892
  } else {
3849
- if (result.type === "paypal_redirect_required" && savedPaymentKeysRef.current) {
3893
+ if (result.type === "paypal_redirect_required" && savedPaymentKeysRef.current?.publishableKey) {
3850
3894
  persistPayPalResumeState({
3851
3895
  sessionId: activeSessionId2,
3852
3896
  publishableKey: savedPaymentKeysRef.current.publishableKey,
@@ -3937,7 +3981,7 @@ function FloPayCheckout({
3937
3981
  billingApiUrl: resolvedBillingUrl,
3938
3982
  locale
3939
3983
  });
3940
- const paypalStripe = (resumePaypalFlopay ?? resumeFlopay).getRawProvider();
3984
+ const paypalStripe = (resumePaypalFlopay ?? resumeFlopay)?.getRawProvider();
3941
3985
  if (!paypalStripe) {
3942
3986
  throw Object.assign(
3943
3987
  new import_shared8.FloPayError("PayPal is not available.", "api_error"),
@@ -4205,7 +4249,8 @@ function FloPayCheckout({
4205
4249
  const resolved = await bootstrapInlineSession();
4206
4250
  const resolvedSession = resolved.result.data.session ?? null;
4207
4251
  savedPaymentKeysRef.current = resolveSavedPaymentPublishableKeys(resolved.result);
4208
- if (!cancelled && shouldAutoProcessInlineSession && resolvedSession) {
4252
+ const resolvedPaypalOnly = isPayPalOnlyUnified(resolved.result);
4253
+ if (!cancelled && shouldAutoProcessInlineSession && resolvedSession && !resolvedPaypalOnly) {
4209
4254
  autoCheckoutAttempted.current = true;
4210
4255
  await runSavedPaymentFlow(resolvedSession, {
4211
4256
  attempt3DS: true,
@@ -4219,6 +4264,9 @@ function FloPayCheckout({
4219
4264
  return;
4220
4265
  }
4221
4266
  if (!cancelled && shouldAutoProcessInlineSession) {
4267
+ if (resolvedPaypalOnly) {
4268
+ autoCheckoutAttempted.current = true;
4269
+ }
4222
4270
  setModeOverlayStatus(null);
4223
4271
  setModeOverlayError(null);
4224
4272
  }
@@ -4273,7 +4321,8 @@ function FloPayCheckout({
4273
4321
  const effectiveMode = checkoutModeProp ?? sess.checkoutMode ?? "full";
4274
4322
  setCurrentMode(effectiveMode);
4275
4323
  const hasPayPalRedirectParams = typeof window !== "undefined" && new URLSearchParams(window.location.search).has("payment_intent");
4276
- if (effectiveMode === "auto" && !autoCheckoutAttempted.current && !hasPayPalRedirectParams) {
4324
+ const paypalOnly = isPayPalOnlyUnified(result);
4325
+ if (effectiveMode === "auto" && !autoCheckoutAttempted.current && !hasPayPalRedirectParams && !paypalOnly) {
4277
4326
  autoCheckoutAttempted.current = true;
4278
4327
  const stripeInitPromise = initStripe(result);
4279
4328
  try {
@@ -4353,8 +4402,10 @@ function FloPayCheckout({
4353
4402
  setConfirmProcessing(false);
4354
4403
  }
4355
4404
  }, [activeSessionId, confirmProcessing, runSavedPaymentFlow, session]);
4405
+ const directPaypalConfig = resolveDirectPaypalConfig(unified);
4406
+ const isPaypalOnlySession = isPayPalOnlyUnified(unified);
4356
4407
  const providerOptions = (0, import_react9.useMemo)(() => {
4357
- if (!unified || !session) return void 0;
4408
+ if (!unified || !session || !unified.data.stripe?.publishableKey) return void 0;
4358
4409
  const opts = {
4359
4410
  appearance,
4360
4411
  paymentMethodCreation: "manual",
@@ -4393,7 +4444,7 @@ function FloPayCheckout({
4393
4444
  cardBootstrapPending
4394
4445
  ]
4395
4446
  );
4396
- const shouldShowInterimButtons = Boolean(createSessionParams) && layout === "buttons" && (!flopay || !providerOptions);
4447
+ const shouldShowInterimButtons = Boolean(createSessionParams) && layout === "buttons" && !isPaypalOnlySession && (!flopay || !providerOptions);
4397
4448
  const modeOverlay = modeOverlayStatus ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4398
4449
  ProcessingOverlay,
4399
4450
  {
@@ -4476,6 +4527,48 @@ function FloPayCheckout({
4476
4527
  modeOverlay
4477
4528
  ] });
4478
4529
  }
4530
+ if (isPaypalOnlySession && session && directPaypalConfig) {
4531
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
4532
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className, style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
4533
+ modeError && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4534
+ "div",
4535
+ {
4536
+ role: "alert",
4537
+ "data-testid": "flopay-error",
4538
+ style: {
4539
+ padding: "0.625rem 0.875rem",
4540
+ background: "#FEF2F2",
4541
+ border: "1px solid #FECACA",
4542
+ borderRadius: "8px",
4543
+ color: "#991B1B",
4544
+ fontSize: "0.85rem",
4545
+ fontWeight: 600
4546
+ },
4547
+ children: modeError
4548
+ }
4549
+ ),
4550
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4551
+ DirectPayPalButton,
4552
+ {
4553
+ sessionId: activeSessionId,
4554
+ billingApiUrl: resolvedBillingUrl,
4555
+ email: session.customer?.email,
4556
+ clientId: directPaypalConfig.clientId,
4557
+ environment: directPaypalConfig.environment,
4558
+ currency: (session.currency ?? "usd").toUpperCase(),
4559
+ isSubscription: session.mode === "subscription",
4560
+ onComplete,
4561
+ onErrorChange: setModeError,
4562
+ onDecline,
4563
+ onButtonClick,
4564
+ session,
4565
+ debug
4566
+ }
4567
+ )
4568
+ ] }) }),
4569
+ modeOverlay
4570
+ ] });
4571
+ }
4479
4572
  if (!flopay || !providerOptions) {
4480
4573
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_jsx_runtime7.Fragment, { children: modeOverlay });
4481
4574
  }
@@ -5630,7 +5723,7 @@ function FloPayAutomaticPaymentButton({
5630
5723
  publishableKey,
5631
5724
  paypalPublishableKey
5632
5725
  } = resolveSavedPaymentPublishableKeys(apiResult);
5633
- if (redirectResult.type === "paypal_redirect_required") {
5726
+ if (redirectResult.type === "paypal_redirect_required" && publishableKey) {
5634
5727
  persistPayPalResumeState2({
5635
5728
  sessionId: session.id || resolvedSessionId,
5636
5729
  publishableKey,
@@ -5686,7 +5779,7 @@ function FloPayAutomaticPaymentButton({
5686
5779
  publishableKey,
5687
5780
  paypalPublishableKey
5688
5781
  } = resolveSavedPaymentPublishableKeys(apiResult);
5689
- if (result.type === "paypal_redirect_required") {
5782
+ if (result.type === "paypal_redirect_required" && publishableKey) {
5690
5783
  persistPayPalResumeState2({
5691
5784
  sessionId: session.id || resolvedSessionId,
5692
5785
  publishableKey,
@@ -5747,6 +5840,13 @@ function FloPayAutomaticPaymentButton({
5747
5840
  billingApiUrl: resolvedBillingUrl,
5748
5841
  locale
5749
5842
  });
5843
+ if (!flopay) {
5844
+ throw new import_shared11.FloPayError(
5845
+ "Stripe is not available for 3DS authentication.",
5846
+ "api_error",
5847
+ { code: "authentication_required" }
5848
+ );
5849
+ }
5750
5850
  const paymentResult = await processSavedPaymentWithIntent({
5751
5851
  billingApiUrl: resolvedBillingUrl,
5752
5852
  sessionId: fallbackSessionId,
@@ -5933,7 +6033,7 @@ function FloPayAutomaticPaymentButton({
5933
6033
  billingApiUrl: resolvedBillingUrl,
5934
6034
  locale
5935
6035
  });
5936
- const paypalStripe = (paypalFlopay ?? flopay).getRawProvider();
6036
+ const paypalStripe = (paypalFlopay ?? flopay)?.getRawProvider();
5937
6037
  if (!paypalStripe) {
5938
6038
  throw Object.assign(
5939
6039
  new import_shared11.FloPayError("PayPal is not available.", "api_error"),