@flopay/react 0.4.4 → 0.4.7

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.d.cts CHANGED
@@ -128,6 +128,8 @@ interface FloPayCheckoutProps {
128
128
  submitLabel?: string;
129
129
  /** Additional CSS class for the wrapper. */
130
130
  className?: string;
131
+ /** Seed an initial checkout error message for the rendered payment form. */
132
+ initialErrorMessage?: string | null;
131
133
  /**
132
134
  * Override the default `SplitCardForm`. When provided, children are rendered
133
135
  * inside the initialized `FloPayProvider` with session props auto-injected.
@@ -167,7 +169,7 @@ interface FloPayCheckoutProps {
167
169
  * />
168
170
  * ```
169
171
  */
170
- declare function FloPayCheckout({ sessionId: sessionIdProp, createSession: createSessionParams, billingApiUrl, appearance, locale, fallbackPublishableKey, loading: loadingNode, error: errorNode, onComplete, onError, onDecline, onFullNameChange, onCountryChange, onZipChange, showPayPal, showApplePay, showGooglePay, layout, buttonsTheme, buttonsStyles, cardButtonContent, cardBackButtonContent, cardTitleContent, showSecurityFooter, onButtonClick, onBeforeButtonClick, enableAVS, avsLayout, submitLabel, className, children, checkoutMode: checkoutModeProp, confirmLabel, renderConfirmButton, onSessionCompleted, }: FloPayCheckoutProps): React.ReactElement;
172
+ declare function FloPayCheckout({ sessionId: sessionIdProp, createSession: createSessionParams, billingApiUrl, appearance, locale, fallbackPublishableKey, loading: loadingNode, error: errorNode, onComplete, onError, onDecline, onFullNameChange, onCountryChange, onZipChange, showPayPal, showApplePay, showGooglePay, layout, buttonsTheme, buttonsStyles, cardButtonContent, cardBackButtonContent, cardTitleContent, showSecurityFooter, onButtonClick, onBeforeButtonClick, enableAVS, avsLayout, submitLabel, className, initialErrorMessage, children, checkoutMode: checkoutModeProp, confirmLabel, renderConfirmButton, onSessionCompleted, }: FloPayCheckoutProps): React.ReactElement;
171
173
 
172
174
  /**
173
175
  * Returns the current `FloPay` instance, or `null` if the provider
package/dist/index.d.ts CHANGED
@@ -128,6 +128,8 @@ interface FloPayCheckoutProps {
128
128
  submitLabel?: string;
129
129
  /** Additional CSS class for the wrapper. */
130
130
  className?: string;
131
+ /** Seed an initial checkout error message for the rendered payment form. */
132
+ initialErrorMessage?: string | null;
131
133
  /**
132
134
  * Override the default `SplitCardForm`. When provided, children are rendered
133
135
  * inside the initialized `FloPayProvider` with session props auto-injected.
@@ -167,7 +169,7 @@ interface FloPayCheckoutProps {
167
169
  * />
168
170
  * ```
169
171
  */
170
- declare function FloPayCheckout({ sessionId: sessionIdProp, createSession: createSessionParams, billingApiUrl, appearance, locale, fallbackPublishableKey, loading: loadingNode, error: errorNode, onComplete, onError, onDecline, onFullNameChange, onCountryChange, onZipChange, showPayPal, showApplePay, showGooglePay, layout, buttonsTheme, buttonsStyles, cardButtonContent, cardBackButtonContent, cardTitleContent, showSecurityFooter, onButtonClick, onBeforeButtonClick, enableAVS, avsLayout, submitLabel, className, children, checkoutMode: checkoutModeProp, confirmLabel, renderConfirmButton, onSessionCompleted, }: FloPayCheckoutProps): React.ReactElement;
172
+ declare function FloPayCheckout({ sessionId: sessionIdProp, createSession: createSessionParams, billingApiUrl, appearance, locale, fallbackPublishableKey, loading: loadingNode, error: errorNode, onComplete, onError, onDecline, onFullNameChange, onCountryChange, onZipChange, showPayPal, showApplePay, showGooglePay, layout, buttonsTheme, buttonsStyles, cardButtonContent, cardBackButtonContent, cardTitleContent, showSecurityFooter, onButtonClick, onBeforeButtonClick, enableAVS, avsLayout, submitLabel, className, initialErrorMessage, children, checkoutMode: checkoutModeProp, confirmLabel, renderConfirmButton, onSessionCompleted, }: FloPayCheckoutProps): React.ReactElement;
171
173
 
172
174
  /**
173
175
  * Returns the current `FloPay` instance, or `null` if the provider
package/dist/index.mjs CHANGED
@@ -1361,13 +1361,19 @@ function SplitCardFormInner({
1361
1361
  onError?.(submitResult.error);
1362
1362
  return;
1363
1363
  }
1364
- const billingDetails = enableAVS ? {
1365
- name: fullName,
1366
- address: {
1367
- country: selectedCountryRef.current,
1368
- postal_code: zipCodeRef.current
1369
- }
1370
- } : void 0;
1364
+ const billingAddress = {
1365
+ ...(enableAVS ? selectedCountryRef.current : resolvedAccount.country) ? {
1366
+ country: enableAVS ? selectedCountryRef.current : resolvedAccount.country
1367
+ } : {},
1368
+ ...(enableAVS ? zipCodeRef.current.trim() : resolvedAccount.zip) || "" ? {
1369
+ postal_code: enableAVS ? zipCodeRef.current.trim() : resolvedAccount.zip
1370
+ } : {}
1371
+ };
1372
+ const billingDetails = {
1373
+ ...resolvedAccount.email ? { email: resolvedAccount.email } : {},
1374
+ ...fullName.trim() ? { name: fullName.trim() } : {},
1375
+ ...Object.keys(billingAddress).length > 0 ? { address: billingAddress } : {}
1376
+ };
1371
1377
  const pmResult = await flopay.createPaymentMethod(billingDetails);
1372
1378
  if (pmResult.error || !pmResult.paymentMethodId) {
1373
1379
  updateError(pmResult.error?.message ?? "Failed to create payment method.");
@@ -1877,6 +1883,32 @@ function SplitCardFormInner({
1877
1883
  import { loadFloPay, PaymentAPI } from "@flopay/js";
1878
1884
  import { FloPayError as FloPayError3 } from "@flopay/shared";
1879
1885
  var DEFAULT_SAVED_PAYMENT_DECLINE_METHOD = "card";
1886
+ function getRedirectResultFromCheckoutProcessError(error) {
1887
+ if (!error?.type || !error.threeDSecureToken) {
1888
+ return null;
1889
+ }
1890
+ if (error.type !== "3ds_required" && error.type !== "paypal_redirect_required") {
1891
+ return null;
1892
+ }
1893
+ return {
1894
+ type: error.type,
1895
+ threeDSecureToken: error.threeDSecureToken,
1896
+ paymentMethodId: error.paymentMethodId
1897
+ };
1898
+ }
1899
+ function checkoutProcessErrorToFloPayError(error, fallbackMessage = "Payment failed. Please try again.", options) {
1900
+ const checkoutMethod = options?.checkoutMethod ?? error?.checkoutMethod ?? (error?.type === "paypal_redirect_required" ? "paypal" : "card");
1901
+ return Object.assign(
1902
+ new FloPayError3(
1903
+ error?.message ?? fallbackMessage,
1904
+ "api_error",
1905
+ {
1906
+ code: error?.gatewayErrorCode
1907
+ }
1908
+ ),
1909
+ { checkoutMethod }
1910
+ );
1911
+ }
1880
1912
  function resolveSavedPaymentReturnUrl(session) {
1881
1913
  if (typeof window !== "undefined" && window.location.href) {
1882
1914
  return window.location.href;
@@ -2399,6 +2431,7 @@ function FloPayCheckout({
2399
2431
  avsLayout,
2400
2432
  submitLabel,
2401
2433
  className,
2434
+ initialErrorMessage = null,
2402
2435
  children,
2403
2436
  checkoutMode: checkoutModeProp,
2404
2437
  confirmLabel,
@@ -2421,7 +2454,7 @@ function FloPayCheckout({
2421
2454
  const [loadError, setLoadError] = useState3(null);
2422
2455
  const [currentMode, setCurrentMode] = useState3("full");
2423
2456
  const [confirmProcessing, setConfirmProcessing] = useState3(false);
2424
- const [modeError, setModeError] = useState3(null);
2457
+ const [modeError, setModeError] = useState3(initialErrorMessage);
2425
2458
  const [modeOverlayStatus, setModeOverlayStatus] = useState3(null);
2426
2459
  const [modeOverlayError, setModeOverlayError] = useState3(null);
2427
2460
  const [createSessionPatch, setCreateSessionPatch] = useState3(void 0);
@@ -2468,6 +2501,9 @@ function FloPayCheckout({
2468
2501
  setCreateSessionPatch(void 0);
2469
2502
  setCreateSessionPatchBaseHash(baseCreateSessionHash);
2470
2503
  }, [baseCreateSessionHash]);
2504
+ useEffect4(() => {
2505
+ setModeError(initialErrorMessage);
2506
+ }, [initialErrorMessage]);
2471
2507
  const emitDecline = useCallback2(
2472
2508
  (method, input, overrides) => {
2473
2509
  onDeclineRef.current?.(buildDeclineEvent(method, input, overrides));
@@ -2481,19 +2517,45 @@ function FloPayCheckout({
2481
2517
  setModeOverlayStatus("processing");
2482
2518
  const activeSessionId2 = options?.sessionId ?? sess.id;
2483
2519
  try {
2484
- const result = await processSavedPaymentForMode({
2485
- billingApiUrl: resolvedBillingUrl,
2486
- sessionId: activeSessionId2,
2487
- session: sess
2488
- });
2489
- const paymentResult = result.type === "success" ? result.result : await handleSavedPaymentRedirectResult(result, {
2490
- flopay: flopayRef.current,
2491
- paypalFlopay: paypalFlopayRef.current,
2492
- attempt3DS: options?.attempt3DS,
2493
- billingApiUrl: resolvedBillingUrl,
2494
- sessionId: activeSessionId2,
2495
- session: sess
2496
- });
2520
+ const redirectResult = getRedirectResultFromCheckoutProcessError(options?.initialAutoProcessingError);
2521
+ let paymentResult;
2522
+ if (redirectResult) {
2523
+ paymentResult = await handleSavedPaymentRedirectResult(redirectResult, {
2524
+ flopay: flopayRef.current,
2525
+ paypalFlopay: paypalFlopayRef.current,
2526
+ attempt3DS: options?.attempt3DS,
2527
+ billingApiUrl: resolvedBillingUrl,
2528
+ sessionId: activeSessionId2,
2529
+ session: sess
2530
+ });
2531
+ } else if (options?.initialAutoProcessingError) {
2532
+ throw checkoutProcessErrorToFloPayError(
2533
+ options.initialAutoProcessingError,
2534
+ "Automatic payment failed. Please try again.",
2535
+ {
2536
+ checkoutMethod: options.initialAutoProcessingError.checkoutMethod
2537
+ }
2538
+ );
2539
+ } else if (options?.fromCreateSession) {
2540
+ if (options?.fallbackToFull) {
2541
+ setCurrentMode("full");
2542
+ }
2543
+ return false;
2544
+ } else {
2545
+ const result = await processSavedPaymentForMode({
2546
+ billingApiUrl: resolvedBillingUrl,
2547
+ sessionId: activeSessionId2,
2548
+ session: sess
2549
+ });
2550
+ paymentResult = result.type === "success" ? result.result : await handleSavedPaymentRedirectResult(result, {
2551
+ flopay: flopayRef.current,
2552
+ paypalFlopay: paypalFlopayRef.current,
2553
+ attempt3DS: options?.attempt3DS,
2554
+ billingApiUrl: resolvedBillingUrl,
2555
+ sessionId: activeSessionId2,
2556
+ session: sess
2557
+ });
2558
+ }
2497
2559
  setModeOverlayStatus("success");
2498
2560
  await sleep(PROCESSING_OVERLAY_SUCCESS_DELAY_MS);
2499
2561
  onCompleteRef.current?.(paymentResult);
@@ -2560,10 +2622,10 @@ function FloPayCheckout({
2560
2622
  }, [sessionIdProp]);
2561
2623
  useEffect4(() => {
2562
2624
  autoCheckoutAttempted.current = false;
2563
- setModeError(null);
2625
+ setModeError(initialErrorMessage);
2564
2626
  setModeOverlayError(null);
2565
2627
  setModeOverlayStatus(null);
2566
- }, [sessionIdProp, createSessionHash]);
2628
+ }, [createSessionHash, initialErrorMessage, sessionIdProp]);
2567
2629
  async function resolveInlineSession(params, cacheKey) {
2568
2630
  const api = new PaymentAPI2(resolvedBillingUrl);
2569
2631
  let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
@@ -2702,6 +2764,9 @@ function FloPayCheckout({
2702
2764
  await runSavedPaymentFlow(resolvedSession, {
2703
2765
  attempt3DS: true,
2704
2766
  fallbackToFull: true,
2767
+ fromCreateSession: true,
2768
+ initialAutoProcessingError: resolved.result.autoProcessingError,
2769
+ autoProcessingAttempted: resolved.result.autoProcessingAttempted,
2705
2770
  sessionId: resolved.sid || resolvedSession.id
2706
2771
  });
2707
2772
  return;
@@ -3522,7 +3587,11 @@ function CheckoutFormInner({
3522
3587
  onError?.(submitResult.error);
3523
3588
  return;
3524
3589
  }
3525
- const pmResult = await flopay.createPaymentMethod();
3590
+ const fullName = `${firstName ?? ""} ${lastName ?? ""}`.trim();
3591
+ const pmResult = await flopay.createPaymentMethod({
3592
+ ...email ? { email } : {},
3593
+ ...fullName ? { name: fullName } : {}
3594
+ });
3526
3595
  if (pmResult.error || !pmResult.paymentMethodId) {
3527
3596
  updateError(pmResult.error?.message ?? "Failed to create payment method.");
3528
3597
  return;
@@ -4025,7 +4094,7 @@ function FloPayAutomaticPaymentButton({
4025
4094
  setOverlayStatus("error");
4026
4095
  await sleep2(PROCESSING_OVERLAY_ERROR_DELAY_MS);
4027
4096
  }, [emitDecline]);
4028
- const processResolvedSession = useCallback5(async (apiResult, resolvedSessionId) => {
4097
+ const processResolvedSession = useCallback5(async (apiResult, resolvedSessionId, options) => {
4029
4098
  const session = apiResult.data.session ?? null;
4030
4099
  if (!session) {
4031
4100
  throw new FloPayError7("No session data returned", "api_error");
@@ -4040,19 +4109,15 @@ function FloPayAutomaticPaymentButton({
4040
4109
  return;
4041
4110
  }
4042
4111
  try {
4043
- const result = await processSavedPaymentForMode({
4044
- billingApiUrl: resolvedBillingUrl,
4045
- sessionId: resolvedSessionId ?? session.id,
4046
- session,
4047
- tokenizedData: automaticPaymentToken
4048
- });
4049
- let paymentResult = result.type === "success" ? result.result : null;
4050
- if (result.type !== "success") {
4112
+ let paymentResult = null;
4113
+ const redirectResult = getRedirectResultFromCheckoutProcessError(apiResult.autoProcessingError);
4114
+ const shouldTreatCreateSessionFlowAsServerAutoAttempt = options?.fromCreateSession && (apiResult.autoProcessingAttempted === true || !!apiResult.autoProcessingError || !!redirectResult);
4115
+ if (redirectResult) {
4051
4116
  const {
4052
4117
  publishableKey,
4053
4118
  paypalPublishableKey
4054
4119
  } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4055
- if (result.type === "paypal_redirect_required") {
4120
+ if (redirectResult.type === "paypal_redirect_required") {
4056
4121
  persistPayPalResumeState({
4057
4122
  sessionId: session.id || resolvedSessionId,
4058
4123
  publishableKey,
@@ -4068,7 +4133,7 @@ function FloPayAutomaticPaymentButton({
4068
4133
  billingApiUrl: resolvedBillingUrl,
4069
4134
  locale
4070
4135
  });
4071
- paymentResult = await handleSavedPaymentRedirectResult(result, {
4136
+ paymentResult = await handleSavedPaymentRedirectResult(redirectResult, {
4072
4137
  flopay,
4073
4138
  paypalFlopay,
4074
4139
  attempt3DS: true,
@@ -4076,9 +4141,66 @@ function FloPayAutomaticPaymentButton({
4076
4141
  sessionId: session.id || resolvedSessionId,
4077
4142
  session
4078
4143
  });
4079
- if (result.type === "paypal_redirect_required") {
4144
+ if (redirectResult.type === "paypal_redirect_required") {
4080
4145
  clearPayPalResumeState();
4081
4146
  }
4147
+ } else if (apiResult.autoProcessingError) {
4148
+ throw checkoutProcessErrorToFloPayError(
4149
+ apiResult.autoProcessingError,
4150
+ "Automatic payment failed. Please try again.",
4151
+ {
4152
+ checkoutMethod: apiResult.autoProcessingError.checkoutMethod
4153
+ }
4154
+ );
4155
+ } else if (shouldTreatCreateSessionFlowAsServerAutoAttempt) {
4156
+ throw checkoutProcessErrorToFloPayError(
4157
+ {
4158
+ type: "unknown",
4159
+ message: "Automatic payment failed. Please try again."
4160
+ },
4161
+ "Automatic payment failed. Please try again."
4162
+ );
4163
+ } else {
4164
+ const result = await processSavedPaymentForMode({
4165
+ billingApiUrl: resolvedBillingUrl,
4166
+ sessionId: resolvedSessionId ?? session.id,
4167
+ session,
4168
+ tokenizedData: automaticPaymentToken
4169
+ });
4170
+ paymentResult = result.type === "success" ? result.result : null;
4171
+ if (result.type !== "success") {
4172
+ const {
4173
+ publishableKey,
4174
+ paypalPublishableKey
4175
+ } = resolveSavedPaymentPublishableKeys(apiResult, fallbackPublishableKey);
4176
+ if (result.type === "paypal_redirect_required") {
4177
+ persistPayPalResumeState({
4178
+ sessionId: session.id || resolvedSessionId,
4179
+ publishableKey,
4180
+ paypalPublishableKey
4181
+ });
4182
+ }
4183
+ const {
4184
+ flopay,
4185
+ paypalFlopay
4186
+ } = await loadSavedPaymentProviders({
4187
+ publishableKey,
4188
+ paypalPublishableKey,
4189
+ billingApiUrl: resolvedBillingUrl,
4190
+ locale
4191
+ });
4192
+ paymentResult = await handleSavedPaymentRedirectResult(result, {
4193
+ flopay,
4194
+ paypalFlopay,
4195
+ attempt3DS: true,
4196
+ billingApiUrl: resolvedBillingUrl,
4197
+ sessionId: session.id || resolvedSessionId,
4198
+ session
4199
+ });
4200
+ if (result.type === "paypal_redirect_required") {
4201
+ clearPayPalResumeState();
4202
+ }
4203
+ }
4082
4204
  }
4083
4205
  await showSuccess({
4084
4206
  result: paymentResult ? {
@@ -4095,7 +4217,8 @@ function FloPayAutomaticPaymentButton({
4095
4217
  } catch (err) {
4096
4218
  let floPayErr = normalizeSavedPaymentError(err);
4097
4219
  const fallbackSessionId = session.id || resolvedSessionId;
4098
- if (floPayErr.code === "authentication_required" && fallbackSessionId && automaticPaymentToken?.id && automaticPaymentToken.id.startsWith("pm_")) {
4220
+ const shouldTreatCreateSessionFlowAsServerAutoAttempt = options?.fromCreateSession && apiResult.autoProcessingAttempted === true;
4221
+ if (!shouldTreatCreateSessionFlowAsServerAutoAttempt && floPayErr.code === "authentication_required" && fallbackSessionId && automaticPaymentToken?.id && automaticPaymentToken.id.startsWith("pm_")) {
4099
4222
  try {
4100
4223
  const {
4101
4224
  publishableKey,
@@ -4192,7 +4315,9 @@ function FloPayAutomaticPaymentButton({
4192
4315
  }
4193
4316
  try {
4194
4317
  const result = await api.createAndFetchSession(createSessionDraft);
4195
- await processResolvedSession(result, result.data.session?.id ?? null);
4318
+ await processResolvedSession(result, result.data.session?.id ?? null, {
4319
+ fromCreateSession: true
4320
+ });
4196
4321
  } catch (err) {
4197
4322
  if (err instanceof FloPayError7 && err.code === "session_auto_completed") {
4198
4323
  await showSuccess({
@@ -4408,7 +4533,7 @@ function FloPayAutomaticPaymentButton({
4408
4533
  padding: "1.5rem",
4409
4534
  zIndex: 1100
4410
4535
  },
4411
- children: /* @__PURE__ */ jsxs7(
4536
+ children: /* @__PURE__ */ jsx9(
4412
4537
  "div",
4413
4538
  {
4414
4539
  style: {
@@ -4424,48 +4549,21 @@ function FloPayAutomaticPaymentButton({
4424
4549
  flexDirection: "column",
4425
4550
  gap: "1rem"
4426
4551
  },
4427
- children: [
4428
- /* @__PURE__ */ jsx9(
4429
- "div",
4430
- {
4431
- style: {
4432
- display: "flex",
4433
- gap: "1rem",
4434
- alignItems: "flex-start"
4435
- },
4436
- children: /* @__PURE__ */ jsx9(
4437
- "div",
4438
- {
4439
- style: {
4440
- flex: 1,
4441
- background: "#fef2f2",
4442
- color: "#b91c1c",
4443
- border: "1px solid #fecaca",
4444
- borderRadius: "12px",
4445
- padding: "0.75rem 1rem",
4446
- fontSize: "0.9rem",
4447
- lineHeight: 1.5
4448
- },
4449
- children: fallbackSession.errorMessage
4450
- }
4451
- )
4452
- }
4453
- ),
4454
- /* @__PURE__ */ jsx9(
4455
- FloPayCheckout,
4456
- {
4457
- sessionId: fallbackSession.sessionId,
4458
- checkoutMode: "full",
4459
- billingApiUrl: resolvedBillingUrl,
4460
- fallbackPublishableKey,
4461
- cardTitleContent: null,
4462
- showSecurityFooter: false,
4463
- onComplete: handleFallbackComplete,
4464
- onError: handleFallbackError,
4465
- onDecline: handleFallbackDecline
4466
- }
4467
- )
4468
- ]
4552
+ children: /* @__PURE__ */ jsx9(
4553
+ FloPayCheckout,
4554
+ {
4555
+ sessionId: fallbackSession.sessionId,
4556
+ checkoutMode: "full",
4557
+ billingApiUrl: resolvedBillingUrl,
4558
+ fallbackPublishableKey,
4559
+ initialErrorMessage: fallbackSession.errorMessage,
4560
+ cardTitleContent: null,
4561
+ showSecurityFooter: false,
4562
+ onComplete: handleFallbackComplete,
4563
+ onError: handleFallbackError,
4564
+ onDecline: handleFallbackDecline
4565
+ }
4566
+ )
4469
4567
  }
4470
4568
  )
4471
4569
  }