@flopay/react 1.2.6 → 1.2.8

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
@@ -633,6 +633,18 @@ function resolvePaymentIntentPaymentMethodId(paymentIntent) {
633
633
  }
634
634
  async function retrievePaymentIntentFromProvider(provider, clientSecret) {
635
635
  const retriever = provider;
636
+ if ((0, import_shared3.isSetupIntentClientSecret)(clientSecret)) {
637
+ if (!retriever?.retrieveSetupIntent) {
638
+ return null;
639
+ }
640
+ const { setupIntent, error: error2 } = await retriever.retrieveSetupIntent(clientSecret);
641
+ if (error2) {
642
+ throw new import_shared3.FloPayError(error2.message ?? "Failed to retrieve setup intent.", "api_error", {
643
+ ...error2.code ? { code: error2.code } : {}
644
+ });
645
+ }
646
+ return setupIntent ?? null;
647
+ }
636
648
  if (!retriever?.retrievePaymentIntent) {
637
649
  return null;
638
650
  }
@@ -644,6 +656,12 @@ async function retrievePaymentIntentFromProvider(provider, clientSecret) {
644
656
  }
645
657
  return paymentIntent ?? null;
646
658
  }
659
+ function resolveWalletElementsMode(amountInMinorUnits) {
660
+ if (typeof amountInMinorUnits !== "number" || !Number.isFinite(amountInMinorUnits) || amountInMinorUnits <= 0) {
661
+ return { mode: "setup" };
662
+ }
663
+ return { mode: "payment", amount: amountInMinorUnits };
664
+ }
647
665
  function resolveTokenizedPaymentMethodId(tokenizedBody) {
648
666
  const candidates = [
649
667
  tokenizedBody?.id,
@@ -1761,10 +1779,7 @@ function WalletButtonInner({
1761
1779
  const intentJson = await intentResponse.json();
1762
1780
  const intentClientSecret = intentJson.data?.id;
1763
1781
  if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
1764
- const { error: confirmError, paymentIntent } = await stripe.confirmCardPayment(
1765
- intentClientSecret,
1766
- { payment_method: paymentMethod.id }
1767
- );
1782
+ const { error: confirmError, intentId } = (0, import_shared6.isSetupIntentClientSecret)(intentClientSecret) ? await stripe.confirmCardSetup(intentClientSecret, { payment_method: paymentMethod.id }).then((r) => ({ error: r.error, intentId: r.setupIntent?.id })) : await stripe.confirmCardPayment(intentClientSecret, { payment_method: paymentMethod.id }).then((r) => ({ error: r.error, intentId: r.paymentIntent?.id }));
1768
1783
  if (confirmError) {
1769
1784
  const message = confirmError.message ?? "Wallet payment failed.";
1770
1785
  onErrorChange?.(message);
@@ -1776,7 +1791,7 @@ function WalletButtonInner({
1776
1791
  onTokenizedBody({
1777
1792
  id: paymentMethod.id,
1778
1793
  type: "card",
1779
- threeDSecureActionResultTokenId: paymentIntent?.id
1794
+ threeDSecureActionResultTokenId: intentId
1780
1795
  }, {
1781
1796
  accountPatch: prepared?.accountPatch,
1782
1797
  sessionId: effectiveSessionId,
@@ -2639,14 +2654,17 @@ function SplitCardFormInner({
2639
2654
  }
2640
2655
  };
2641
2656
  }, [appearance]);
2642
- const walletOptions = (0, import_react8.useMemo)(() => ({
2643
- mode: "payment",
2644
- amount: amountInCents,
2645
- currency: currency.toLowerCase(),
2646
- paymentMethodCreation: "manual",
2647
- captureMethod: "manual",
2648
- ...stripeAppearanceProp
2649
- }), [amountInCents, currency, stripeAppearanceProp]);
2657
+ const walletOptions = (0, import_react8.useMemo)(() => {
2658
+ const modeOptions = resolveWalletElementsMode(totalAmount);
2659
+ return {
2660
+ ...modeOptions,
2661
+ currency: currency.toLowerCase(),
2662
+ paymentMethodCreation: "manual",
2663
+ // captureMethod only applies to payment mode; Stripe rejects it in setup.
2664
+ ...modeOptions.mode === "payment" ? { captureMethod: "manual" } : {},
2665
+ ...stripeAppearanceProp
2666
+ };
2667
+ }, [totalAmount, currency, stripeAppearanceProp]);
2650
2668
  const paypalOptions = (0, import_react8.useMemo)(() => ({
2651
2669
  mode: "payment",
2652
2670
  amount: amountInCents,
@@ -4318,6 +4336,26 @@ function SplitCardFormInner({
4318
4336
  var import_js3 = require("@flopay/js");
4319
4337
  var import_shared7 = require("@flopay/shared");
4320
4338
  var DEFAULT_SAVED_PAYMENT_DECLINE_METHOD = "card";
4339
+ async function retrieveResumeIntent(stripe, clientSecret, isSetupIntent) {
4340
+ if (isSetupIntent) {
4341
+ if (typeof stripe.retrieveSetupIntent !== "function") return null;
4342
+ const { setupIntent, error: error2 } = await stripe.retrieveSetupIntent(clientSecret);
4343
+ return { error: error2, intent: setupIntent ?? null };
4344
+ }
4345
+ if (typeof stripe.retrievePaymentIntent !== "function") return null;
4346
+ const { paymentIntent, error } = await stripe.retrievePaymentIntent(clientSecret);
4347
+ return { error, intent: paymentIntent ?? null };
4348
+ }
4349
+ async function confirmResumeIntent(stripe, clientSecret, data, isSetupIntent) {
4350
+ if (isSetupIntent) {
4351
+ if (typeof stripe.confirmCardSetup !== "function") return null;
4352
+ const { setupIntent, error: error2 } = await stripe.confirmCardSetup(clientSecret, data);
4353
+ return { error: error2, intent: setupIntent ?? null };
4354
+ }
4355
+ if (typeof stripe.confirmCardPayment !== "function") return null;
4356
+ const { paymentIntent, error } = await stripe.confirmCardPayment(clientSecret, data);
4357
+ return { error, intent: paymentIntent ?? null };
4358
+ }
4321
4359
  function getRedirectResultFromCheckoutProcessError(error) {
4322
4360
  if (!error?.type || !error.threeDSecureToken) {
4323
4361
  return null;
@@ -4446,11 +4484,13 @@ async function processSavedPaymentForMode({
4446
4484
  billingApiUrl,
4447
4485
  sessionId,
4448
4486
  session,
4487
+ nonce,
4449
4488
  tokenizedData,
4450
4489
  returnUrl
4451
4490
  }) {
4452
4491
  const baseUrl = billingApiUrl.replace(/\/+$/, "");
4453
4492
  const resolvedSessionId = sessionId ?? session.id;
4493
+ const resolvedNonce = nonce ?? session.clientSecret;
4454
4494
  const customerId = session.customer?.id ?? session.accountData?.userId ?? "";
4455
4495
  const customerEmail = session.customer?.email ?? session.accountData?.email ?? "";
4456
4496
  const firstName = session.customer?.firstName ?? session.accountData?.firstName ?? "";
@@ -4460,7 +4500,7 @@ async function processSavedPaymentForMode({
4460
4500
  const api = new import_js3.PaymentAPI(baseUrl);
4461
4501
  const response = await retryOnceOnFetchFailure(() => api.processPayment(customerId, {
4462
4502
  sessionId: resolvedSessionId,
4463
- nonce: session.clientSecret,
4503
+ nonce: resolvedNonce,
4464
4504
  tokenizedData,
4465
4505
  accountData: {
4466
4506
  userId: customerId,
@@ -4503,7 +4543,7 @@ async function processSavedPaymentForMode({
4503
4543
  const recoveredRedirect = await recover3DSRedirectResult({
4504
4544
  billingApiUrl: baseUrl,
4505
4545
  sessionId: resolvedSessionId,
4506
- nonce: session.clientSecret,
4546
+ nonce: resolvedNonce,
4507
4547
  responseJson: json
4508
4548
  });
4509
4549
  if (recoveredRedirect) {
@@ -4551,64 +4591,66 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
4551
4591
  { checkoutMethod: "card" }
4552
4592
  );
4553
4593
  }
4594
+ const isSetupIntent = (0, import_shared7.isSetupIntentClientSecret)(redirectResult.threeDSecureToken);
4554
4595
  let paymentIntent = null;
4555
4596
  let savedPaymentMethodId = redirectResult.paymentMethodId;
4556
- if (typeof stripe.retrievePaymentIntent === "function") {
4557
- const { paymentIntent: existingPaymentIntent, error: retrieveError } = await stripe.retrievePaymentIntent(
4558
- redirectResult.threeDSecureToken
4559
- );
4560
- if (retrieveError) {
4597
+ const retrieved = await retrieveResumeIntent(stripe, redirectResult.threeDSecureToken, isSetupIntent);
4598
+ if (retrieved) {
4599
+ if (retrieved.error) {
4561
4600
  throw Object.assign(
4562
4601
  new import_shared7.FloPayError(
4563
- retrieveError.message ?? "Failed to retrieve 3DS payment status.",
4602
+ retrieved.error.message ?? `Failed to retrieve 3DS ${isSetupIntent ? "setup" : "payment"} status.`,
4564
4603
  "api_error",
4565
- { code: retrieveError.code }
4604
+ { code: retrieved.error.code }
4566
4605
  ),
4567
4606
  { checkoutMethod: "card" }
4568
4607
  );
4569
4608
  }
4570
- if (!savedPaymentMethodId && existingPaymentIntent?.payment_method) {
4571
- if (typeof existingPaymentIntent.payment_method === "string") {
4572
- savedPaymentMethodId = existingPaymentIntent.payment_method;
4573
- } else if ("id" in existingPaymentIntent.payment_method) {
4574
- savedPaymentMethodId = existingPaymentIntent.payment_method.id;
4609
+ const existingIntent = retrieved.intent;
4610
+ if (!savedPaymentMethodId && existingIntent?.payment_method) {
4611
+ if (typeof existingIntent.payment_method === "string") {
4612
+ savedPaymentMethodId = existingIntent.payment_method;
4613
+ } else if ("id" in existingIntent.payment_method) {
4614
+ savedPaymentMethodId = existingIntent.payment_method.id ?? void 0;
4575
4615
  }
4576
4616
  }
4577
4617
  }
4578
- if (savedPaymentMethodId && typeof stripe.confirmCardPayment === "function") {
4579
- const { error: confirmError, paymentIntent: confirmedPaymentIntent } = await stripe.confirmCardPayment(
4580
- redirectResult.threeDSecureToken,
4581
- {
4582
- payment_method: savedPaymentMethodId,
4583
- return_url: returnUrl ?? resolveSavedPaymentReturnUrl(session) ?? window.location.href
4584
- }
4585
- );
4586
- if (confirmError) {
4618
+ const confirmed = savedPaymentMethodId ? await confirmResumeIntent(
4619
+ stripe,
4620
+ redirectResult.threeDSecureToken,
4621
+ {
4622
+ payment_method: savedPaymentMethodId,
4623
+ return_url: returnUrl ?? resolveSavedPaymentReturnUrl(session) ?? window.location.href
4624
+ },
4625
+ isSetupIntent
4626
+ ) : null;
4627
+ if (confirmed) {
4628
+ if (confirmed.error) {
4587
4629
  throw Object.assign(
4588
4630
  new import_shared7.FloPayError(
4589
- confirmError.message ?? "3DS authentication failed.",
4631
+ confirmed.error.message ?? "3DS authentication failed.",
4590
4632
  "api_error",
4591
- { code: confirmError.code }
4633
+ { code: confirmed.error.code }
4592
4634
  ),
4593
4635
  { checkoutMethod: "card" }
4594
4636
  );
4595
4637
  }
4596
- paymentIntent = confirmedPaymentIntent ?? null;
4638
+ paymentIntent = confirmed.intent;
4597
4639
  } else {
4598
- const { error: nextActionError, paymentIntent: nextActionPaymentIntent } = await stripe.handleNextAction({
4640
+ const nextAction = await stripe.handleNextAction({
4599
4641
  clientSecret: redirectResult.threeDSecureToken
4600
4642
  });
4601
- if (nextActionError) {
4643
+ if (nextAction.error) {
4602
4644
  throw Object.assign(
4603
4645
  new import_shared7.FloPayError(
4604
- nextActionError.message ?? "3DS authentication failed.",
4646
+ nextAction.error.message ?? "3DS authentication failed.",
4605
4647
  "api_error",
4606
- { code: nextActionError.code }
4648
+ { code: nextAction.error.code }
4607
4649
  ),
4608
4650
  { checkoutMethod: "card" }
4609
4651
  );
4610
4652
  }
4611
- paymentIntent = nextActionPaymentIntent ?? null;
4653
+ paymentIntent = (isSetupIntent ? nextAction.setupIntent : nextAction.paymentIntent) ?? null;
4612
4654
  }
4613
4655
  if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded" || paymentIntent.status === "processing")) {
4614
4656
  const followUp = await processSavedPaymentForMode({
@@ -6681,6 +6723,7 @@ function resolveCreateSessionDraft(props) {
6681
6723
  }
6682
6724
  function FloPayAutomaticPaymentButton({
6683
6725
  sessionId,
6726
+ nonce,
6684
6727
  createSession,
6685
6728
  // Deprecated — accepted for back-compat and silently ignored. Backend
6686
6729
  // resolves the customer's latest payment method server-side.
@@ -6812,6 +6855,7 @@ function FloPayAutomaticPaymentButton({
6812
6855
  if (!session) {
6813
6856
  throw new import_shared11.FloPayError("No session data returned", "api_error");
6814
6857
  }
6858
+ const effectiveNonce = session.clientSecret || nonce;
6815
6859
  if (session.status === "complete") {
6816
6860
  await showSuccess({
6817
6861
  result: { status: "succeeded" },
@@ -6867,7 +6911,8 @@ function FloPayAutomaticPaymentButton({
6867
6911
  const result = await processSavedPaymentForMode({
6868
6912
  billingApiUrl: resolvedBillingUrl,
6869
6913
  sessionId: resolvedSessionId ?? session.id,
6870
- session
6914
+ session,
6915
+ nonce: effectiveNonce
6871
6916
  });
6872
6917
  if (result.type !== "success") {
6873
6918
  throw checkoutProcessErrorToFloPayError(
@@ -6894,11 +6939,13 @@ function FloPayAutomaticPaymentButton({
6894
6939
  if (shouldShowFallbackCheckout(floPayErr, fallbackSessionId) && fallbackSessionId && isMountedRef.current) {
6895
6940
  setFallbackSession({
6896
6941
  sessionId: fallbackSessionId,
6897
- errorMessage: floPayErr.message
6942
+ errorMessage: floPayErr.message,
6943
+ nonce: effectiveNonce || void 0
6898
6944
  });
6899
6945
  }
6900
6946
  }
6901
6947
  }, [
6948
+ nonce,
6902
6949
  resolvedBillingUrl,
6903
6950
  showError,
6904
6951
  showSuccess
@@ -6939,7 +6986,7 @@ function FloPayAutomaticPaymentButton({
6939
6986
  try {
6940
6987
  const api = new import_js7.PaymentAPI(resolvedBillingUrl);
6941
6988
  if (sessionId) {
6942
- const result = await api.getUnifiedCheckoutSession(sessionId);
6989
+ const result = await api.getUnifiedCheckoutSession(sessionId, nonce);
6943
6990
  await processResolvedSession(result, sessionId);
6944
6991
  return;
6945
6992
  }
@@ -6978,6 +7025,7 @@ function FloPayAutomaticPaymentButton({
6978
7025
  createSessionDraft,
6979
7026
  disabled,
6980
7027
  isProcessing,
7028
+ nonce,
6981
7029
  processResolvedSession,
6982
7030
  resolvedBillingUrl,
6983
7031
  sessionId,
@@ -7112,6 +7160,7 @@ function FloPayAutomaticPaymentButton({
7112
7160
  FloPayCheckout,
7113
7161
  {
7114
7162
  sessionId: fallbackSession.sessionId,
7163
+ nonce: fallbackSession.nonce,
7115
7164
  checkoutMode: "full",
7116
7165
  billingApiUrl: resolvedBillingUrl,
7117
7166
  locale,