@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.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,
@@ -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");
@@ -949,7 +1002,16 @@ function WalletButtonInner({
949
1002
  isPaypal: false
950
1003
  })
951
1004
  });
952
- if (!intentResponse.ok) throw new Error("Failed to create payment intent");
1005
+ if (!intentResponse.ok) {
1006
+ const intentError = await buildFloPayApiErrorFromResponse(
1007
+ intentResponse,
1008
+ "Failed to create payment intent"
1009
+ );
1010
+ onErrorChange?.(intentError.message);
1011
+ onDecline?.(buildDeclineEvent(method, intentError));
1012
+ event.paymentFailed({ reason: "fail", message: intentError.message });
1013
+ return;
1014
+ }
953
1015
  const intentJson = await intentResponse.json();
954
1016
  const intentClientSecret = intentJson.data?.id;
955
1017
  if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
@@ -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
  }
@@ -3053,14 +3148,19 @@ function FloPayCheckout({
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(),
3059
3159
  s: params?.subscriptions?.map((x) => `${x.providerPlanId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
3060
- account: params?.account,
3061
3160
  couponCodes: params?.couponCodes,
3062
3161
  tagsData: params?.tagsData,
3063
3162
  utmMetadata: params?.utmMetadata,
3163
+ tokenizedData: params?.tokenizedData,
3064
3164
  m: params?.checkoutMode ?? "full"
3065
3165
  });
3066
3166
  let h = 0;
@@ -3215,7 +3315,8 @@ function FloPayCheckout({
3215
3315
  };
3216
3316
  }
3217
3317
  (async () => {
3218
- const shouldAutoProcessInlineSession = effectiveCreateSessionMode === "auto" && !autoCheckoutAttempted.current;
3318
+ const hasPayPalRedirectParams = typeof window !== "undefined" && new URLSearchParams(window.location.search).has("payment_intent");
3319
+ const shouldAutoProcessInlineSession = effectiveCreateSessionMode === "auto" && !autoCheckoutAttempted.current && !hasPayPalRedirectParams;
3219
3320
  if (shouldAutoProcessInlineSession) {
3220
3321
  setModeError(null);
3221
3322
  setModeOverlayError(null);
@@ -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
  });