@flopay/react 0.5.3 → 0.5.6

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
@@ -559,6 +559,27 @@ function buildDeclineEvent(method, input, overrides) {
559
559
  ...declineCode ? { declineCode } : {}
560
560
  };
561
561
  }
562
+ function isRecord(value) {
563
+ return typeof value === "object" && value !== null;
564
+ }
565
+ function readString(payload, key) {
566
+ const value = payload?.[key];
567
+ return typeof value === "string" && value.trim() ? value : void 0;
568
+ }
569
+ function buildFloPayApiError(payload, fallbackMessage) {
570
+ const nestedError = isRecord(payload?.error) ? payload.error : null;
571
+ const message = readString(payload, "message") ?? readString(nestedError, "message") ?? fallbackMessage;
572
+ const code = readString(payload, "code") ?? readString(payload, "gatewayErrorCode") ?? readString(nestedError, "code");
573
+ const declineCode = readString(payload, "declineCode") ?? readString(payload, "gatewayDeclineReason") ?? readString(payload, "decline_code") ?? readString(nestedError, "decline_code");
574
+ return new import_shared3.FloPayError(message, "api_error", {
575
+ ...code ? { code } : {},
576
+ ...declineCode ? { declineCode } : {}
577
+ });
578
+ }
579
+ async function buildFloPayApiErrorFromResponse(response, fallbackMessage) {
580
+ const payload = await response.json().catch(() => null);
581
+ return buildFloPayApiError(payload, fallbackMessage);
582
+ }
562
583
  function mapPayPalIntentStatusToPaymentResult(status) {
563
584
  if (status === "succeeded") {
564
585
  return "succeeded";
@@ -568,6 +589,29 @@ function mapPayPalIntentStatusToPaymentResult(status) {
568
589
  }
569
590
  return "failed";
570
591
  }
592
+ function resolvePaymentIntentPaymentMethodId(paymentIntent) {
593
+ const paymentMethod = paymentIntent?.payment_method;
594
+ if (typeof paymentMethod === "string" && paymentMethod.startsWith("pm_")) {
595
+ return paymentMethod;
596
+ }
597
+ if (paymentMethod && typeof paymentMethod === "object" && typeof paymentMethod.id === "string") {
598
+ return paymentMethod.id;
599
+ }
600
+ return void 0;
601
+ }
602
+ async function retrievePaymentIntentFromProvider(provider, clientSecret) {
603
+ const retriever = provider;
604
+ if (!retriever?.retrievePaymentIntent) {
605
+ return null;
606
+ }
607
+ const { paymentIntent, error } = await retriever.retrievePaymentIntent(clientSecret);
608
+ if (error) {
609
+ throw new import_shared3.FloPayError(error.message ?? "Failed to retrieve payment intent.", "api_error", {
610
+ ...error.code ? { code: error.code } : {}
611
+ });
612
+ }
613
+ return paymentIntent ?? null;
614
+ }
571
615
  function resolveTokenizedPaymentMethodId(tokenizedBody) {
572
616
  const candidates = [
573
617
  tokenizedBody?.id,
@@ -766,8 +810,9 @@ function PayPalButtonInner({
766
810
  accountPatch: beforeClick.accountPatch,
767
811
  sessionId: beforeClick.sessionId
768
812
  };
813
+ onButtonClick?.("paypal");
769
814
  event.resolve();
770
- }, [isProcessing, runBeforeButtonClick, submitting]);
815
+ }, [isProcessing, onButtonClick, runBeforeButtonClick, submitting]);
771
816
  const handlePayPalConfirm = (0, import_react7.useCallback)(async (event) => {
772
817
  if (!stripe || !elements) return;
773
818
  let prepared = beforeClickRef.current;
@@ -785,7 +830,6 @@ function PayPalButtonInner({
785
830
  }
786
831
  const effectiveSessionId = prepared?.sessionId ?? sessionId;
787
832
  const effectiveEmail = prepared?.accountPatch?.email ?? email;
788
- onButtonClick?.("paypal");
789
833
  try {
790
834
  setSubmitting(true);
791
835
  onErrorChange?.(null);
@@ -812,7 +856,16 @@ function PayPalButtonInner({
812
856
  setup_future_usage: "off_session"
813
857
  })
814
858
  });
815
- if (!intentResponse.ok) throw new Error("Failed to create payment intent");
859
+ if (!intentResponse.ok) {
860
+ const intentError = await buildFloPayApiErrorFromResponse(
861
+ intentResponse,
862
+ "Failed to create payment intent"
863
+ );
864
+ onErrorChange?.(intentError.message);
865
+ onDecline?.(buildDeclineEvent("paypal", intentError));
866
+ event.paymentFailed({ reason: "fail", message: intentError.message });
867
+ return;
868
+ }
816
869
  const intentJson = await intentResponse.json();
817
870
  const intentClientSecret = intentJson.data?.id;
818
871
  if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
@@ -851,7 +904,7 @@ function PayPalButtonInner({
851
904
  } finally {
852
905
  setSubmitting(false);
853
906
  }
854
- }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, onButtonClick, runBeforeButtonClick]);
907
+ }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]);
855
908
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
856
909
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-paypal-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
857
910
  import_react_stripe_js.ExpressCheckoutElement,
@@ -922,7 +975,6 @@ function WalletButtonInner({
922
975
  const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
923
976
  const effectiveSessionId = prepared?.sessionId ?? sessionId;
924
977
  const effectiveEmail = prepared?.accountPatch?.email ?? email;
925
- onButtonClick?.(walletType === "apple_pay" ? "apple_pay" : "google_pay");
926
978
  try {
927
979
  setSubmitting(true);
928
980
  onErrorChange?.(null);
@@ -949,7 +1001,16 @@ function WalletButtonInner({
949
1001
  isPaypal: false
950
1002
  })
951
1003
  });
952
- if (!intentResponse.ok) throw new Error("Failed to create payment intent");
1004
+ if (!intentResponse.ok) {
1005
+ const intentError = await buildFloPayApiErrorFromResponse(
1006
+ intentResponse,
1007
+ "Failed to create payment intent"
1008
+ );
1009
+ onErrorChange?.(intentError.message);
1010
+ onDecline?.(buildDeclineEvent(method, intentError));
1011
+ event.paymentFailed({ reason: "fail", message: intentError.message });
1012
+ return;
1013
+ }
953
1014
  const intentJson = await intentResponse.json();
954
1015
  const intentClientSecret = intentJson.data?.id;
955
1016
  if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
@@ -979,7 +1040,7 @@ function WalletButtonInner({
979
1040
  setSubmitting(false);
980
1041
  }
981
1042
  },
982
- [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, onButtonClick, runBeforeButtonClick]
1043
+ [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick]
983
1044
  );
984
1045
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
985
1046
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-wallet-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
@@ -999,6 +1060,7 @@ function WalletButtonInner({
999
1060
  accountPatch: beforeClick.accountPatch,
1000
1061
  sessionId: beforeClick.sessionId
1001
1062
  };
1063
+ onButtonClick?.(lastWalletMethodRef.current);
1002
1064
  event.resolve();
1003
1065
  },
1004
1066
  onConfirm: handleWalletConfirm,
@@ -1564,7 +1626,18 @@ function SplitCardFormInner({
1564
1626
  isPaypal: false
1565
1627
  })
1566
1628
  });
1567
- if (!intentResponse.ok) throw new import_shared5.FloPayError("Failed to create payment intent", "api_error");
1629
+ if (!intentResponse.ok) {
1630
+ const intentError = await buildFloPayApiErrorFromResponse(
1631
+ intentResponse,
1632
+ "Failed to create payment intent"
1633
+ );
1634
+ setOverlayStatus("error");
1635
+ updateError(intentError.message);
1636
+ onError?.(intentError);
1637
+ emitDecline("card", intentError);
1638
+ await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
1639
+ return;
1640
+ }
1568
1641
  const intentJson = await intentResponse.json();
1569
1642
  const intentClientSecret = intentJson.data?.id;
1570
1643
  if (!intentClientSecret) throw new import_shared5.FloPayError("No client_secret in payment intent response", "api_error");
@@ -1580,11 +1653,26 @@ function SplitCardFormInner({
1580
1653
  await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
1581
1654
  return;
1582
1655
  }
1656
+ const rawProvider = typeof flopay.getRawProvider === "function" ? flopay.getRawProvider() : null;
1657
+ const confirmedPaymentIntent = await retrievePaymentIntentFromProvider(
1658
+ rawProvider,
1659
+ intentClientSecret
1660
+ );
1661
+ const paymentIntentId = confirmResult.paymentIntentId ?? confirmedPaymentIntent?.id;
1662
+ const paymentMethodId = confirmResult.paymentMethodId ?? resolvePaymentIntentPaymentMethodId(confirmedPaymentIntent) ?? pmResult.paymentMethodId;
1663
+ if (!paymentIntentId) {
1664
+ const error2 = new import_shared5.FloPayError("No payment intent returned after confirmation.", "api_error");
1665
+ setOverlayStatus("error");
1666
+ updateError(error2.message);
1667
+ onError?.(error2);
1668
+ await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
1669
+ return;
1670
+ }
1583
1671
  handedOff = isSelfContained;
1584
1672
  dispatchTokenizedBody({
1585
- id: pmResult.paymentMethodId,
1673
+ id: paymentMethodId,
1586
1674
  type: "card",
1587
- threeDSecureActionResultTokenId: confirmResult.paymentIntentId,
1675
+ threeDSecureActionResultTokenId: paymentIntentId,
1588
1676
  originalPaymentMethodId: pmResult.paymentMethodId
1589
1677
  });
1590
1678
  } catch (err) {
@@ -2405,12 +2493,12 @@ async function processSavedPaymentWithIntent({
2405
2493
  ));
2406
2494
  const intentJson = await intentResponse.json().catch(() => null);
2407
2495
  if (!intentResponse.ok) {
2496
+ const intentError = buildFloPayApiError(
2497
+ intentJson,
2498
+ "Failed to create payment intent."
2499
+ );
2408
2500
  throw Object.assign(
2409
- new import_shared6.FloPayError(
2410
- intentJson?.message ?? "Failed to create payment intent.",
2411
- "api_error",
2412
- { code: intentJson?.code ?? intentJson?.gatewayErrorCode }
2413
- ),
2501
+ intentError,
2414
2502
  { checkoutMethod: "card" }
2415
2503
  );
2416
2504
  }
@@ -2433,7 +2521,14 @@ async function processSavedPaymentWithIntent({
2433
2521
  { checkoutMethod: "card" }
2434
2522
  );
2435
2523
  }
2436
- if (!confirmResult.paymentIntentId || confirmResult.status !== "succeeded" && confirmResult.status !== "processing" && confirmResult.status !== "requires_capture") {
2524
+ const rawProvider = typeof flopay.getRawProvider === "function" ? flopay.getRawProvider() : null;
2525
+ const confirmedPaymentIntent = await retrievePaymentIntentFromProvider(
2526
+ rawProvider,
2527
+ intentClientSecret
2528
+ );
2529
+ const confirmedPaymentIntentId = confirmResult.paymentIntentId ?? confirmedPaymentIntent?.id;
2530
+ const confirmedPaymentMethodId = confirmResult.paymentMethodId ?? resolvePaymentIntentPaymentMethodId(confirmedPaymentIntent) ?? paymentMethodId;
2531
+ if (!confirmedPaymentIntentId || confirmResult.status !== "succeeded" && confirmResult.status !== "processing" && confirmResult.status !== "requires_capture") {
2437
2532
  throw Object.assign(
2438
2533
  new import_shared6.FloPayError(
2439
2534
  `Unfortunately, your payment could not be processed. Please try again using a different payment method or contact your bank for assistance. If the issue persists, feel free to reach out to us for support.`,
@@ -2450,9 +2545,9 @@ async function processSavedPaymentWithIntent({
2450
2545
  sessionId,
2451
2546
  session,
2452
2547
  tokenizedData: {
2453
- id: paymentMethodId,
2548
+ id: confirmedPaymentMethodId,
2454
2549
  type: "card",
2455
- threeDSecureActionResultTokenId: confirmResult.paymentIntentId
2550
+ threeDSecureActionResultTokenId: confirmedPaymentIntentId
2456
2551
  },
2457
2552
  returnUrl
2458
2553
  });
@@ -2467,8 +2562,8 @@ async function processSavedPaymentWithIntent({
2467
2562
  });
2468
2563
  return {
2469
2564
  ...finalResult,
2470
- paymentIntentId: finalResult.paymentIntentId ?? confirmResult.paymentIntentId,
2471
- paymentMethodId: finalResult.paymentMethodId ?? paymentMethodId,
2565
+ paymentIntentId: finalResult.paymentIntentId ?? confirmedPaymentIntentId,
2566
+ paymentMethodId: finalResult.paymentMethodId ?? confirmedPaymentMethodId,
2472
2567
  checkoutMethod: finalResult.checkoutMethod ?? "card"
2473
2568
  };
2474
2569
  }
@@ -2682,6 +2777,7 @@ async function loadSavedPaymentProviders({
2682
2777
  var import_jsx_runtime6 = require("react/jsx-runtime");
2683
2778
  var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2 = 44;
2684
2779
  var PAYPAL_RESUME_STORAGE_KEY = "flopay_checkout_saved_payment_resume";
2780
+ var sessionInflightMap = /* @__PURE__ */ new Map();
2685
2781
  function sleep(ms) {
2686
2782
  return new Promise((resolve) => setTimeout(resolve, ms));
2687
2783
  }
@@ -3048,11 +3144,15 @@ function FloPayCheckout({
3048
3144
  }
3049
3145
  })();
3050
3146
  }, [emitDecline, locale, normalizeSavedPaymentError, resolvedBillingUrl]);
3051
- const inflightRef = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
3052
3147
  const initializedHashRef = (0, import_react8.useRef)(null);
3053
3148
  function hashCreateParams(params) {
3054
3149
  const key = JSON.stringify({
3055
3150
  c: params?.clientId,
3151
+ a: {
3152
+ u: params?.account?.userId ?? "",
3153
+ e: params?.account?.email?.trim().toLowerCase() ?? "",
3154
+ country: params?.account?.country ?? ""
3155
+ },
3056
3156
  successUrl: params?.successUrl,
3057
3157
  cancelUrl: params?.cancelUrl,
3058
3158
  i: params?.items?.map((x) => `${x.providerItemId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
@@ -3060,6 +3160,7 @@ function FloPayCheckout({
3060
3160
  couponCodes: params?.couponCodes,
3061
3161
  tagsData: params?.tagsData,
3062
3162
  utmMetadata: params?.utmMetadata,
3163
+ tokenizedData: params?.tokenizedData,
3063
3164
  m: params?.checkoutMode ?? "full"
3064
3165
  });
3065
3166
  let h = 0;
@@ -3124,16 +3225,16 @@ function FloPayCheckout({
3124
3225
  }
3125
3226
  const mergedParams = mergeInlineSessionPatch(baseParams, patch);
3126
3227
  const cacheKey = hashCreateParams(mergedParams);
3127
- let promise = inflightRef.current.get(cacheKey);
3228
+ let promise = sessionInflightMap.get(cacheKey);
3128
3229
  if (!promise) {
3129
3230
  promise = resolveInlineSession(mergedParams, cacheKey);
3130
- inflightRef.current.set(cacheKey, promise);
3231
+ sessionInflightMap.set(cacheKey, promise);
3131
3232
  }
3132
3233
  let resolved;
3133
3234
  try {
3134
3235
  resolved = await promise;
3135
3236
  } finally {
3136
- inflightRef.current.delete(cacheKey);
3237
+ sessionInflightMap.delete(cacheKey);
3137
3238
  }
3138
3239
  initializedHashRef.current = cacheKey;
3139
3240
  try {
@@ -4062,6 +4163,7 @@ function CheckoutFormInner({
4062
4163
  if (!flopay || !elements || isSubmitting) return;
4063
4164
  setProcessing(true);
4064
4165
  updateError(null);
4166
+ let handedOff = false;
4065
4167
  try {
4066
4168
  const submitResult = await flopay.submitElements();
4067
4169
  if (submitResult.error) {
@@ -4091,7 +4193,16 @@ function CheckoutFormInner({
4091
4193
  isPaypal: false
4092
4194
  })
4093
4195
  });
4094
- if (!intentResponse.ok) throw new import_shared8.FloPayError("Failed to create payment intent", "api_error");
4196
+ if (!intentResponse.ok) {
4197
+ const intentError = await buildFloPayApiErrorFromResponse(
4198
+ intentResponse,
4199
+ "Failed to create payment intent"
4200
+ );
4201
+ updateError(intentError.message);
4202
+ onError?.(intentError);
4203
+ emitDecline(intentError);
4204
+ return;
4205
+ }
4095
4206
  const intentJson = await intentResponse.json();
4096
4207
  const intentClientSecret = intentJson.data?.id;
4097
4208
  if (!intentClientSecret) throw new import_shared8.FloPayError("No client_secret in payment intent response", "api_error");
@@ -4105,17 +4216,30 @@ function CheckoutFormInner({
4105
4216
  emitDecline(confirmResult.error);
4106
4217
  return;
4107
4218
  }
4219
+ const rawProvider = typeof flopay.getRawProvider === "function" ? flopay.getRawProvider() : null;
4220
+ const confirmedPaymentIntent = await retrievePaymentIntentFromProvider(
4221
+ rawProvider,
4222
+ intentClientSecret
4223
+ );
4224
+ const paymentIntentId = confirmResult.paymentIntentId ?? confirmedPaymentIntent?.id;
4225
+ const paymentMethodId = confirmResult.paymentMethodId ?? resolvePaymentIntentPaymentMethodId(confirmedPaymentIntent) ?? pmResult.paymentMethodId;
4226
+ if (!paymentIntentId) {
4227
+ const error2 = new import_shared8.FloPayError("No payment intent returned after confirmation.", "api_error");
4228
+ updateError(error2.message);
4229
+ onError?.(error2);
4230
+ return;
4231
+ }
4232
+ handedOff = isSelfContained;
4108
4233
  dispatchTokenizedBody({
4109
- id: pmResult.paymentMethodId,
4234
+ id: paymentMethodId,
4110
4235
  type: "card",
4111
- threeDSecureActionResultTokenId: confirmResult.paymentIntentId,
4236
+ threeDSecureActionResultTokenId: paymentIntentId,
4112
4237
  originalPaymentMethodId: pmResult.paymentMethodId
4113
4238
  });
4114
4239
  } catch (err) {
4115
4240
  updateError(err instanceof Error ? err.message : "An unexpected error occurred");
4116
4241
  } finally {
4117
- if (isSelfContained) {
4118
- } else {
4242
+ if (!handedOff) {
4119
4243
  setProcessing(false);
4120
4244
  }
4121
4245
  }
@@ -4793,7 +4917,10 @@ function FloPayAutomaticPaymentButton({
4793
4917
  return;
4794
4918
  }
4795
4919
  try {
4796
- const result = await api.createAndFetchSession(createSessionDraft);
4920
+ const result = await api.createAndFetchSession({
4921
+ ...createSessionDraft,
4922
+ ...automaticPaymentToken ? { tokenizedData: automaticPaymentToken } : {}
4923
+ });
4797
4924
  await processResolvedSession(result, result.data.session?.id ?? null, {
4798
4925
  fromCreateSession: true
4799
4926
  });