@flopay/react 0.5.2 → 0.5.5

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
@@ -522,6 +522,27 @@ function buildDeclineEvent(method, input, overrides) {
522
522
  ...declineCode ? { declineCode } : {}
523
523
  };
524
524
  }
525
+ function isRecord(value) {
526
+ return typeof value === "object" && value !== null;
527
+ }
528
+ function readString(payload, key) {
529
+ const value = payload?.[key];
530
+ return typeof value === "string" && value.trim() ? value : void 0;
531
+ }
532
+ function buildFloPayApiError(payload, fallbackMessage) {
533
+ const nestedError = isRecord(payload?.error) ? payload.error : null;
534
+ const message = readString(payload, "message") ?? readString(nestedError, "message") ?? fallbackMessage;
535
+ const code = readString(payload, "code") ?? readString(payload, "gatewayErrorCode") ?? readString(nestedError, "code");
536
+ const declineCode = readString(payload, "declineCode") ?? readString(payload, "gatewayDeclineReason") ?? readString(payload, "decline_code") ?? readString(nestedError, "decline_code");
537
+ return new FloPayError(message, "api_error", {
538
+ ...code ? { code } : {},
539
+ ...declineCode ? { declineCode } : {}
540
+ });
541
+ }
542
+ async function buildFloPayApiErrorFromResponse(response, fallbackMessage) {
543
+ const payload = await response.json().catch(() => null);
544
+ return buildFloPayApiError(payload, fallbackMessage);
545
+ }
525
546
  function mapPayPalIntentStatusToPaymentResult(status) {
526
547
  if (status === "succeeded") {
527
548
  return "succeeded";
@@ -531,6 +552,29 @@ function mapPayPalIntentStatusToPaymentResult(status) {
531
552
  }
532
553
  return "failed";
533
554
  }
555
+ function resolvePaymentIntentPaymentMethodId(paymentIntent) {
556
+ const paymentMethod = paymentIntent?.payment_method;
557
+ if (typeof paymentMethod === "string" && paymentMethod.startsWith("pm_")) {
558
+ return paymentMethod;
559
+ }
560
+ if (paymentMethod && typeof paymentMethod === "object" && typeof paymentMethod.id === "string") {
561
+ return paymentMethod.id;
562
+ }
563
+ return void 0;
564
+ }
565
+ async function retrievePaymentIntentFromProvider(provider, clientSecret) {
566
+ const retriever = provider;
567
+ if (!retriever?.retrievePaymentIntent) {
568
+ return null;
569
+ }
570
+ const { paymentIntent, error } = await retriever.retrievePaymentIntent(clientSecret);
571
+ if (error) {
572
+ throw new FloPayError(error.message ?? "Failed to retrieve payment intent.", "api_error", {
573
+ ...error.code ? { code: error.code } : {}
574
+ });
575
+ }
576
+ return paymentIntent ?? null;
577
+ }
534
578
  function resolveTokenizedPaymentMethodId(tokenizedBody) {
535
579
  const candidates = [
536
580
  tokenizedBody?.id,
@@ -775,7 +819,16 @@ function PayPalButtonInner({
775
819
  setup_future_usage: "off_session"
776
820
  })
777
821
  });
778
- if (!intentResponse.ok) throw new Error("Failed to create payment intent");
822
+ if (!intentResponse.ok) {
823
+ const intentError = await buildFloPayApiErrorFromResponse(
824
+ intentResponse,
825
+ "Failed to create payment intent"
826
+ );
827
+ onErrorChange?.(intentError.message);
828
+ onDecline?.(buildDeclineEvent("paypal", intentError));
829
+ event.paymentFailed({ reason: "fail", message: intentError.message });
830
+ return;
831
+ }
779
832
  const intentJson = await intentResponse.json();
780
833
  const intentClientSecret = intentJson.data?.id;
781
834
  if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
@@ -912,7 +965,16 @@ function WalletButtonInner({
912
965
  isPaypal: false
913
966
  })
914
967
  });
915
- if (!intentResponse.ok) throw new Error("Failed to create payment intent");
968
+ if (!intentResponse.ok) {
969
+ const intentError = await buildFloPayApiErrorFromResponse(
970
+ intentResponse,
971
+ "Failed to create payment intent"
972
+ );
973
+ onErrorChange?.(intentError.message);
974
+ onDecline?.(buildDeclineEvent(method, intentError));
975
+ event.paymentFailed({ reason: "fail", message: intentError.message });
976
+ return;
977
+ }
916
978
  const intentJson = await intentResponse.json();
917
979
  const intentClientSecret = intentJson.data?.id;
918
980
  if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
@@ -1527,7 +1589,18 @@ function SplitCardFormInner({
1527
1589
  isPaypal: false
1528
1590
  })
1529
1591
  });
1530
- if (!intentResponse.ok) throw new FloPayError2("Failed to create payment intent", "api_error");
1592
+ if (!intentResponse.ok) {
1593
+ const intentError = await buildFloPayApiErrorFromResponse(
1594
+ intentResponse,
1595
+ "Failed to create payment intent"
1596
+ );
1597
+ setOverlayStatus("error");
1598
+ updateError(intentError.message);
1599
+ onError?.(intentError);
1600
+ emitDecline("card", intentError);
1601
+ await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
1602
+ return;
1603
+ }
1531
1604
  const intentJson = await intentResponse.json();
1532
1605
  const intentClientSecret = intentJson.data?.id;
1533
1606
  if (!intentClientSecret) throw new FloPayError2("No client_secret in payment intent response", "api_error");
@@ -1543,11 +1616,26 @@ function SplitCardFormInner({
1543
1616
  await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
1544
1617
  return;
1545
1618
  }
1619
+ const rawProvider = typeof flopay.getRawProvider === "function" ? flopay.getRawProvider() : null;
1620
+ const confirmedPaymentIntent = await retrievePaymentIntentFromProvider(
1621
+ rawProvider,
1622
+ intentClientSecret
1623
+ );
1624
+ const paymentIntentId = confirmResult.paymentIntentId ?? confirmedPaymentIntent?.id;
1625
+ const paymentMethodId = confirmResult.paymentMethodId ?? resolvePaymentIntentPaymentMethodId(confirmedPaymentIntent) ?? pmResult.paymentMethodId;
1626
+ if (!paymentIntentId) {
1627
+ const error2 = new FloPayError2("No payment intent returned after confirmation.", "api_error");
1628
+ setOverlayStatus("error");
1629
+ updateError(error2.message);
1630
+ onError?.(error2);
1631
+ await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
1632
+ return;
1633
+ }
1546
1634
  handedOff = isSelfContained;
1547
1635
  dispatchTokenizedBody({
1548
- id: pmResult.paymentMethodId,
1636
+ id: paymentMethodId,
1549
1637
  type: "card",
1550
- threeDSecureActionResultTokenId: confirmResult.paymentIntentId,
1638
+ threeDSecureActionResultTokenId: paymentIntentId,
1551
1639
  originalPaymentMethodId: pmResult.paymentMethodId
1552
1640
  });
1553
1641
  } catch (err) {
@@ -2368,12 +2456,12 @@ async function processSavedPaymentWithIntent({
2368
2456
  ));
2369
2457
  const intentJson = await intentResponse.json().catch(() => null);
2370
2458
  if (!intentResponse.ok) {
2459
+ const intentError = buildFloPayApiError(
2460
+ intentJson,
2461
+ "Failed to create payment intent."
2462
+ );
2371
2463
  throw Object.assign(
2372
- new FloPayError3(
2373
- intentJson?.message ?? "Failed to create payment intent.",
2374
- "api_error",
2375
- { code: intentJson?.code ?? intentJson?.gatewayErrorCode }
2376
- ),
2464
+ intentError,
2377
2465
  { checkoutMethod: "card" }
2378
2466
  );
2379
2467
  }
@@ -2396,7 +2484,14 @@ async function processSavedPaymentWithIntent({
2396
2484
  { checkoutMethod: "card" }
2397
2485
  );
2398
2486
  }
2399
- if (!confirmResult.paymentIntentId || confirmResult.status !== "succeeded" && confirmResult.status !== "processing" && confirmResult.status !== "requires_capture") {
2487
+ const rawProvider = typeof flopay.getRawProvider === "function" ? flopay.getRawProvider() : null;
2488
+ const confirmedPaymentIntent = await retrievePaymentIntentFromProvider(
2489
+ rawProvider,
2490
+ intentClientSecret
2491
+ );
2492
+ const confirmedPaymentIntentId = confirmResult.paymentIntentId ?? confirmedPaymentIntent?.id;
2493
+ const confirmedPaymentMethodId = confirmResult.paymentMethodId ?? resolvePaymentIntentPaymentMethodId(confirmedPaymentIntent) ?? paymentMethodId;
2494
+ if (!confirmedPaymentIntentId || confirmResult.status !== "succeeded" && confirmResult.status !== "processing" && confirmResult.status !== "requires_capture") {
2400
2495
  throw Object.assign(
2401
2496
  new FloPayError3(
2402
2497
  `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.`,
@@ -2413,9 +2508,9 @@ async function processSavedPaymentWithIntent({
2413
2508
  sessionId,
2414
2509
  session,
2415
2510
  tokenizedData: {
2416
- id: paymentMethodId,
2511
+ id: confirmedPaymentMethodId,
2417
2512
  type: "card",
2418
- threeDSecureActionResultTokenId: confirmResult.paymentIntentId
2513
+ threeDSecureActionResultTokenId: confirmedPaymentIntentId
2419
2514
  },
2420
2515
  returnUrl
2421
2516
  });
@@ -2430,8 +2525,8 @@ async function processSavedPaymentWithIntent({
2430
2525
  });
2431
2526
  return {
2432
2527
  ...finalResult,
2433
- paymentIntentId: finalResult.paymentIntentId ?? confirmResult.paymentIntentId,
2434
- paymentMethodId: finalResult.paymentMethodId ?? paymentMethodId,
2528
+ paymentIntentId: finalResult.paymentIntentId ?? confirmedPaymentIntentId,
2529
+ paymentMethodId: finalResult.paymentMethodId ?? confirmedPaymentMethodId,
2435
2530
  checkoutMethod: finalResult.checkoutMethod ?? "card"
2436
2531
  };
2437
2532
  }
@@ -3016,14 +3111,19 @@ function FloPayCheckout({
3016
3111
  function hashCreateParams(params) {
3017
3112
  const key = JSON.stringify({
3018
3113
  c: params?.clientId,
3114
+ a: {
3115
+ u: params?.account?.userId ?? "",
3116
+ e: params?.account?.email?.trim().toLowerCase() ?? "",
3117
+ country: params?.account?.country ?? ""
3118
+ },
3019
3119
  successUrl: params?.successUrl,
3020
3120
  cancelUrl: params?.cancelUrl,
3021
3121
  i: params?.items?.map((x) => `${x.providerItemId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
3022
3122
  s: params?.subscriptions?.map((x) => `${x.providerPlanId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
3023
- account: params?.account,
3024
3123
  couponCodes: params?.couponCodes,
3025
3124
  tagsData: params?.tagsData,
3026
3125
  utmMetadata: params?.utmMetadata,
3126
+ tokenizedData: params?.tokenizedData,
3027
3127
  m: params?.checkoutMode ?? "full"
3028
3128
  });
3029
3129
  let h = 0;
@@ -3178,7 +3278,8 @@ function FloPayCheckout({
3178
3278
  };
3179
3279
  }
3180
3280
  (async () => {
3181
- const shouldAutoProcessInlineSession = effectiveCreateSessionMode === "auto" && !autoCheckoutAttempted.current;
3281
+ const hasPayPalRedirectParams = typeof window !== "undefined" && new URLSearchParams(window.location.search).has("payment_intent");
3282
+ const shouldAutoProcessInlineSession = effectiveCreateSessionMode === "auto" && !autoCheckoutAttempted.current && !hasPayPalRedirectParams;
3182
3283
  if (shouldAutoProcessInlineSession) {
3183
3284
  setModeError(null);
3184
3285
  setModeOverlayError(null);
@@ -4025,6 +4126,7 @@ function CheckoutFormInner({
4025
4126
  if (!flopay || !elements || isSubmitting) return;
4026
4127
  setProcessing(true);
4027
4128
  updateError(null);
4129
+ let handedOff = false;
4028
4130
  try {
4029
4131
  const submitResult = await flopay.submitElements();
4030
4132
  if (submitResult.error) {
@@ -4054,7 +4156,16 @@ function CheckoutFormInner({
4054
4156
  isPaypal: false
4055
4157
  })
4056
4158
  });
4057
- if (!intentResponse.ok) throw new FloPayError5("Failed to create payment intent", "api_error");
4159
+ if (!intentResponse.ok) {
4160
+ const intentError = await buildFloPayApiErrorFromResponse(
4161
+ intentResponse,
4162
+ "Failed to create payment intent"
4163
+ );
4164
+ updateError(intentError.message);
4165
+ onError?.(intentError);
4166
+ emitDecline(intentError);
4167
+ return;
4168
+ }
4058
4169
  const intentJson = await intentResponse.json();
4059
4170
  const intentClientSecret = intentJson.data?.id;
4060
4171
  if (!intentClientSecret) throw new FloPayError5("No client_secret in payment intent response", "api_error");
@@ -4068,17 +4179,30 @@ function CheckoutFormInner({
4068
4179
  emitDecline(confirmResult.error);
4069
4180
  return;
4070
4181
  }
4182
+ const rawProvider = typeof flopay.getRawProvider === "function" ? flopay.getRawProvider() : null;
4183
+ const confirmedPaymentIntent = await retrievePaymentIntentFromProvider(
4184
+ rawProvider,
4185
+ intentClientSecret
4186
+ );
4187
+ const paymentIntentId = confirmResult.paymentIntentId ?? confirmedPaymentIntent?.id;
4188
+ const paymentMethodId = confirmResult.paymentMethodId ?? resolvePaymentIntentPaymentMethodId(confirmedPaymentIntent) ?? pmResult.paymentMethodId;
4189
+ if (!paymentIntentId) {
4190
+ const error2 = new FloPayError5("No payment intent returned after confirmation.", "api_error");
4191
+ updateError(error2.message);
4192
+ onError?.(error2);
4193
+ return;
4194
+ }
4195
+ handedOff = isSelfContained;
4071
4196
  dispatchTokenizedBody({
4072
- id: pmResult.paymentMethodId,
4197
+ id: paymentMethodId,
4073
4198
  type: "card",
4074
- threeDSecureActionResultTokenId: confirmResult.paymentIntentId,
4199
+ threeDSecureActionResultTokenId: paymentIntentId,
4075
4200
  originalPaymentMethodId: pmResult.paymentMethodId
4076
4201
  });
4077
4202
  } catch (err) {
4078
4203
  updateError(err instanceof Error ? err.message : "An unexpected error occurred");
4079
4204
  } finally {
4080
- if (isSelfContained) {
4081
- } else {
4205
+ if (!handedOff) {
4082
4206
  setProcessing(false);
4083
4207
  }
4084
4208
  }
@@ -4756,7 +4880,10 @@ function FloPayAutomaticPaymentButton({
4756
4880
  return;
4757
4881
  }
4758
4882
  try {
4759
- const result = await api.createAndFetchSession(createSessionDraft);
4883
+ const result = await api.createAndFetchSession({
4884
+ ...createSessionDraft,
4885
+ ...automaticPaymentToken ? { tokenizedData: automaticPaymentToken } : {}
4886
+ });
4760
4887
  await processResolvedSession(result, result.data.session?.id ?? null, {
4761
4888
  fromCreateSession: true
4762
4889
  });