@flopay/react 0.5.6 → 0.5.11

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.mjs CHANGED
@@ -588,6 +588,39 @@ function resolveTokenizedPaymentMethodId(tokenizedBody) {
588
588
  return void 0;
589
589
  }
590
590
 
591
+ // src/recent-completion.ts
592
+ var STORAGE_KEY_PREFIX = "flopay_recent_completion_";
593
+ var RECENT_COMPLETION_TTL_MS = 60 * 1e3;
594
+ function markSessionRecentlyCompleted(sessionId) {
595
+ if (!sessionId || typeof window === "undefined") return;
596
+ try {
597
+ const payload = {
598
+ expiresAt: Date.now() + RECENT_COMPLETION_TTL_MS
599
+ };
600
+ window.sessionStorage.setItem(STORAGE_KEY_PREFIX + sessionId, JSON.stringify(payload));
601
+ } catch {
602
+ }
603
+ }
604
+ function wasSessionRecentlyCompleted(sessionId) {
605
+ if (!sessionId || typeof window === "undefined") return false;
606
+ try {
607
+ const raw = window.sessionStorage.getItem(STORAGE_KEY_PREFIX + sessionId);
608
+ if (!raw) return false;
609
+ const parsed = JSON.parse(raw);
610
+ if (typeof parsed?.expiresAt !== "number") {
611
+ window.sessionStorage.removeItem(STORAGE_KEY_PREFIX + sessionId);
612
+ return false;
613
+ }
614
+ if (parsed.expiresAt <= Date.now()) {
615
+ window.sessionStorage.removeItem(STORAGE_KEY_PREFIX + sessionId);
616
+ return false;
617
+ }
618
+ return true;
619
+ } catch {
620
+ return false;
621
+ }
622
+ }
623
+
591
624
  // src/split-card-form.tsx
592
625
  import { FloPayError as FloPayError2 } from "@flopay/shared";
593
626
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -719,6 +752,11 @@ function PayPalButtonInner({
719
752
  const redirectStatus = params.get("redirect_status");
720
753
  if (!paymentIntentId || !clientSecret) return;
721
754
  paypalResumeAttempted.current = true;
755
+ const cleanedUrl = new URL(window.location.href);
756
+ cleanedUrl.searchParams.delete("payment_intent");
757
+ cleanedUrl.searchParams.delete("payment_intent_client_secret");
758
+ cleanedUrl.searchParams.delete("redirect_status");
759
+ window.history.replaceState({}, "", cleanedUrl.toString());
722
760
  (async () => {
723
761
  try {
724
762
  setSubmitting(true);
@@ -741,11 +779,6 @@ function PayPalButtonInner({
741
779
  threeDSecureActionResultTokenId: paymentIntent.id,
742
780
  isPaypal: true
743
781
  });
744
- const url = new URL(window.location.href);
745
- url.searchParams.delete("payment_intent");
746
- url.searchParams.delete("payment_intent_client_secret");
747
- url.searchParams.delete("redirect_status");
748
- window.history.replaceState({}, "", url.toString());
749
782
  } else {
750
783
  const message = "PayPal payment was not completed. Please try again.";
751
784
  onErrorChange?.(message);
@@ -1328,6 +1361,7 @@ function SplitCardFormInner({
1328
1361
  })
1329
1362
  });
1330
1363
  if (response.ok) {
1364
+ markSessionRecentlyCompleted(effectiveSessionId);
1331
1365
  setOverlayStatus("success");
1332
1366
  await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_SUCCESS_DELAY_MS));
1333
1367
  onComplete?.({
@@ -2982,6 +3016,7 @@ function FloPayCheckout({
2982
3016
  }
2983
3017
  }
2984
3018
  }
3019
+ if (activeSessionId2) markSessionRecentlyCompleted(activeSessionId2);
2985
3020
  setModeOverlayStatus("success");
2986
3021
  await sleep(PROCESSING_OVERLAY_SUCCESS_DELAY_MS);
2987
3022
  onCompleteRef.current?.(paymentResult);
@@ -3078,6 +3113,31 @@ function FloPayCheckout({
3078
3113
  );
3079
3114
  }
3080
3115
  const paymentMethodId = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
3116
+ if (resumeState.sessionId) {
3117
+ const resumeApi = new PaymentAPI2(resolvedBillingUrl);
3118
+ const resumeSessionResult = await resumeApi.getUnifiedCheckoutSession(resumeState.sessionId);
3119
+ const resumeSession = resumeSessionResult.data.session;
3120
+ if (resumeSession && resumeSession.status !== "complete") {
3121
+ const processResult = await processSavedPaymentForMode({
3122
+ billingApiUrl: resolvedBillingUrl,
3123
+ sessionId: resumeState.sessionId,
3124
+ session: resumeSession,
3125
+ tokenizedData: {
3126
+ id: paymentMethodId ?? paymentIntent.id,
3127
+ type: "card",
3128
+ threeDSecureActionResultTokenId: paymentIntent.id,
3129
+ isPaypal: true
3130
+ }
3131
+ });
3132
+ if (processResult.type !== "success") {
3133
+ throw Object.assign(
3134
+ new FloPayError4("Failed to finalize PayPal payment.", "api_error"),
3135
+ { checkoutMethod: "paypal" }
3136
+ );
3137
+ }
3138
+ }
3139
+ }
3140
+ if (resumeState.sessionId) markSessionRecentlyCompleted(resumeState.sessionId);
3081
3141
  setModeOverlayStatus("success");
3082
3142
  await sleep(PROCESSING_OVERLAY_SUCCESS_DELAY_MS);
3083
3143
  onCompleteRef.current?.({
@@ -3112,7 +3172,6 @@ function FloPayCheckout({
3112
3172
  const key = JSON.stringify({
3113
3173
  c: params?.clientId,
3114
3174
  a: {
3115
- u: params?.account?.userId ?? "",
3116
3175
  e: params?.account?.email?.trim().toLowerCase() ?? "",
3117
3176
  country: params?.account?.country ?? ""
3118
3177
  },
@@ -3154,7 +3213,11 @@ function FloPayCheckout({
3154
3213
  if (sid) {
3155
3214
  try {
3156
3215
  realResult = await api.getUnifiedCheckoutSession(sid);
3157
- if (realResult.data.session?.status === "complete") {
3216
+ const status = realResult.data.session?.status;
3217
+ if (status === "complete") {
3218
+ if (wasSessionRecentlyCompleted(sid)) {
3219
+ return { sid, result: realResult };
3220
+ }
3158
3221
  if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
3159
3222
  sid = null;
3160
3223
  realResult = null;
@@ -4819,6 +4882,10 @@ function FloPayAutomaticPaymentButton({
4819
4882
  floPayErr = normalizeSavedPaymentError(intentRecoveryErr);
4820
4883
  }
4821
4884
  }
4885
+ const inResumeWindow = typeof window !== "undefined" && new URLSearchParams(window.location.search).has("payment_intent_client_secret") && !!readPayPalResumeState2();
4886
+ if (floPayErr.code === "payment_intent_unexpected_state" && inResumeWindow) {
4887
+ return;
4888
+ }
4822
4889
  await showError(floPayErr, {
4823
4890
  emitDecline: true,
4824
4891
  method: floPayErr.checkoutMethod ?? DEFAULT_SAVED_PAYMENT_DECLINE_METHOD
@@ -4990,25 +5057,58 @@ function FloPayAutomaticPaymentButton({
4990
5057
  );
4991
5058
  }
4992
5059
  const resultStatus = mapPayPalIntentStatusToPaymentResult(paymentIntent?.status);
4993
- if (paymentIntent && resultStatus !== "failed") {
4994
- const paymentMethodId2 = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
4995
- await showSuccess({
4996
- result: {
4997
- status: resultStatus,
4998
- paymentIntentId: paymentIntent.id,
4999
- paymentMethodId: paymentMethodId2,
5000
- checkoutMethod: "paypal"
5001
- },
5002
- session: null,
5003
- sessionId: resumeState.sessionId,
5004
- autoCompleted: false
5005
- });
5006
- } else {
5060
+ if (!paymentIntent || resultStatus === "failed") {
5007
5061
  throw Object.assign(
5008
5062
  new FloPayError7("PayPal payment was not completed. Please try again.", "api_error"),
5009
5063
  { checkoutMethod: "paypal" }
5010
5064
  );
5011
5065
  }
5066
+ const paymentMethodId2 = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
5067
+ if (!resumeState.sessionId) {
5068
+ throw Object.assign(
5069
+ new FloPayError7("Missing session id on PayPal resume.", "api_error"),
5070
+ { checkoutMethod: "paypal" }
5071
+ );
5072
+ }
5073
+ const api = new PaymentAPI3(resolvedBillingUrl);
5074
+ const sessionResult = await api.getUnifiedCheckoutSession(resumeState.sessionId);
5075
+ const resumeSession = sessionResult.data.session;
5076
+ if (!resumeSession) {
5077
+ throw Object.assign(
5078
+ new FloPayError7("Could not load session to capture PayPal payment.", "api_error"),
5079
+ { checkoutMethod: "paypal" }
5080
+ );
5081
+ }
5082
+ if (resumeSession.status !== "complete") {
5083
+ const processResult = await processSavedPaymentForMode({
5084
+ billingApiUrl: resolvedBillingUrl,
5085
+ sessionId: resumeState.sessionId,
5086
+ session: resumeSession,
5087
+ tokenizedData: {
5088
+ id: paymentMethodId2 ?? paymentIntent.id,
5089
+ type: "card",
5090
+ threeDSecureActionResultTokenId: paymentIntent.id,
5091
+ isPaypal: true
5092
+ }
5093
+ });
5094
+ if (processResult.type !== "success") {
5095
+ throw Object.assign(
5096
+ new FloPayError7("Failed to finalize PayPal payment.", "api_error"),
5097
+ { checkoutMethod: "paypal" }
5098
+ );
5099
+ }
5100
+ }
5101
+ await showSuccess({
5102
+ result: {
5103
+ status: resultStatus,
5104
+ paymentIntentId: paymentIntent.id,
5105
+ paymentMethodId: paymentMethodId2,
5106
+ checkoutMethod: "paypal"
5107
+ },
5108
+ session: resumeSession,
5109
+ sessionId: resumeState.sessionId,
5110
+ autoCompleted: false
5111
+ });
5012
5112
  } catch (err) {
5013
5113
  const floPayErr = normalizeSavedPaymentError(err);
5014
5114
  await showError(floPayErr, {