@flopay/react 0.3.17 → 0.3.19

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/README.md CHANGED
@@ -123,7 +123,7 @@ Theme presets: `'default'`, `'minimal'`, `'rounded'`, `'dark'`. Custom styles vi
123
123
 
124
124
  #### Button Hooks
125
125
 
126
- Use `onButtonClick` to track button interactions, `onDecline` to track declines/cancellations, and `onBeforeButtonClick` to enrich the credit card flow before the card form opens:
126
+ Use `onButtonClick` to track button interactions, `onDecline` to track declines/cancellations, and `onBeforeButtonClick` to enrich any buttons-layout payment method before it continues:
127
127
 
128
128
  ```tsx
129
129
  <FloPayCheckout
@@ -136,7 +136,11 @@ Use `onButtonClick` to track button interactions, `onDecline` to track declines/
136
136
  cancelUrl: '/cancel',
137
137
  }}
138
138
  onBeforeButtonClick={async ({ method, createSession }) => {
139
- if (method !== 'card') return;
139
+ if (method !== 'card') {
140
+ return {
141
+ tagsData: { sessionId: `checkout_${method}_clicked` },
142
+ };
143
+ }
140
144
 
141
145
  const email = await openEmailCaptureModal({
142
146
  initialEmail: createSession?.account.email ?? '',
@@ -158,7 +162,7 @@ Use `onButtonClick` to track button interactions, `onDecline` to track declines/
158
162
  />
159
163
  ```
160
164
 
161
- `onBeforeButtonClick` is **credit card only**. It runs only for the "Credit / Debit Card" button in `layout="buttons"`. It does not run for PayPal, Apple Pay, or Google Pay. If you need required data before every payment method, collect it before rendering checkout.
165
+ `onBeforeButtonClick` runs for every payment button in `layout="buttons"`. Branch on `method` if only some flows need extra work. For PayPal, Apple Pay, and Google Pay, keep the hook fast because it runs during the provider button handshake before the wallet sheet or PayPal window opens.
162
166
 
163
167
  #### Inline Session Creation
164
168
 
@@ -180,7 +184,7 @@ Skip the backend API route — create the session directly in the component:
180
184
 
181
185
  The component POSTs to the billing API, gets the full session back, and renders the form — zero backend code needed.
182
186
 
183
- When you use `onBeforeButtonClick`, `createSession.account.email` should still be present when checkout renders. If you only have a temporary address such as `test@email.com`, pass it first and replace it in `onBeforeButtonClick` before the card flow continues. PayPal and wallet buttons still bootstrap normally, but this hook does not run for them, so collect any data those methods require before the user uses them.
187
+ When you use `onBeforeButtonClick` with `createSession`, any returned `InlineSessionPatch` is merged into the draft session params before the selected buttons-layout flow continues. That lets you add tracking data or update account fields just in time without pre-creating a separate backend session.
184
188
 
185
189
  ### Advanced: Manual Provider Setup
186
190
 
package/dist/index.cjs CHANGED
@@ -394,6 +394,25 @@ var FLOPAY_KEYFRAMES = `
394
394
  100% { opacity: 0; transform: translateX(16px) scale(0.97); }
395
395
  }
396
396
  `;
397
+ var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT = 44;
398
+ function getButtonMethodLabel(method) {
399
+ switch (method) {
400
+ case "paypal":
401
+ return "PayPal";
402
+ case "apple_pay":
403
+ return "Apple Pay";
404
+ case "google_pay":
405
+ return "Google Pay";
406
+ default:
407
+ return "Card";
408
+ }
409
+ }
410
+ function normalizeBeforeButtonClickError(method, err) {
411
+ return err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(
412
+ err instanceof Error ? err.message : `${getButtonMethodLabel(method)} before-click hook failed.`,
413
+ "validation_error"
414
+ );
415
+ }
397
416
  function FloPayKeyframes() {
398
417
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: FLOPAY_KEYFRAMES });
399
418
  }
@@ -564,13 +583,15 @@ function PayPalButtonInner({
564
583
  onErrorChange,
565
584
  isProcessing = false,
566
585
  onButtonClick,
567
- onDecline
586
+ onDecline,
587
+ runBeforeButtonClick
568
588
  }) {
569
589
  const stripe = (0, import_react_stripe_js.useStripe)();
570
590
  const elements = (0, import_react_stripe_js.useElements)();
571
591
  const [loadState, setLoadState] = (0, import_react6.useState)("loading");
572
592
  const [submitting, setSubmitting] = (0, import_react6.useState)(false);
573
593
  const paypalResumeAttempted = (0, import_react6.useRef)(false);
594
+ const beforeClickRef = (0, import_react6.useRef)(null);
574
595
  const baseUrl = billingApiUrl.replace(/\/+$/, "");
575
596
  (0, import_react6.useEffect)(() => {
576
597
  if (!stripe || paypalResumeAttempted.current) return;
@@ -619,13 +640,50 @@ function PayPalButtonInner({
619
640
  }
620
641
  })();
621
642
  }, [stripe, onTokenizedBody, onErrorChange, onDecline]);
622
- const handlePayPalConfirm = (0, import_react6.useCallback)(async (_event) => {
643
+ const handlePayPalClick = (0, import_react6.useCallback)(async (event) => {
644
+ if (isProcessing || submitting) {
645
+ event.reject();
646
+ return;
647
+ }
648
+ const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick("paypal", { deferInlineSessionPatch: true }) : { proceed: true };
649
+ if (!beforeClick.proceed) {
650
+ beforeClickRef.current = null;
651
+ event.reject();
652
+ return;
653
+ }
654
+ beforeClickRef.current = {
655
+ accountPatch: beforeClick.accountPatch,
656
+ pendingInlinePatch: beforeClick.pendingInlinePatch
657
+ };
658
+ event.resolve();
659
+ }, [isProcessing, runBeforeButtonClick, submitting]);
660
+ const handlePayPalConfirm = (0, import_react6.useCallback)(async (event) => {
623
661
  if (!stripe || !elements) return;
662
+ let prepared = beforeClickRef.current;
663
+ beforeClickRef.current = null;
664
+ if (!prepared && runBeforeButtonClick) {
665
+ const beforeClick = await runBeforeButtonClick("paypal");
666
+ if (!beforeClick.proceed) {
667
+ event.paymentFailed({ reason: "fail", message: "PayPal checkout was cancelled." });
668
+ return;
669
+ }
670
+ prepared = {
671
+ accountPatch: beforeClick.accountPatch,
672
+ pendingInlinePatch: beforeClick.pendingInlinePatch ?? Promise.resolve({ error: null, sessionId: beforeClick.sessionId ?? sessionId })
673
+ };
674
+ }
675
+ const patchResult = prepared?.pendingInlinePatch ? await prepared.pendingInlinePatch : { error: null, sessionId };
676
+ if (patchResult.error) {
677
+ event.paymentFailed({ reason: "fail", message: patchResult.error.message });
678
+ return;
679
+ }
680
+ const effectiveSessionId = patchResult.sessionId ?? sessionId;
681
+ const effectiveEmail = prepared?.accountPatch?.email ?? email;
624
682
  onButtonClick?.("paypal");
625
683
  try {
626
684
  setSubmitting(true);
627
685
  onErrorChange?.(null);
628
- if (!sessionId || !email) {
686
+ if (!effectiveSessionId || !effectiveEmail) {
629
687
  throw new Error("Missing sessionId or email for PayPal payment");
630
688
  }
631
689
  const createPM = stripe.createPaymentMethod;
@@ -637,8 +695,8 @@ function PayPalButtonInner({
637
695
  method: "POST",
638
696
  headers: { "Content-Type": "application/json" },
639
697
  body: JSON.stringify({
640
- sessionId,
641
- email,
698
+ sessionId: effectiveSessionId,
699
+ email: effectiveEmail,
642
700
  paymentMethodType: paymentMethod?.id ?? "paypal",
643
701
  isPaypal: "true"
644
702
  })
@@ -672,21 +730,26 @@ function PayPalButtonInner({
672
730
  type: "card",
673
731
  threeDSecureActionResultTokenId: paymentIntent?.id,
674
732
  isPaypal: true
733
+ }, {
734
+ accountPatch: prepared?.accountPatch,
735
+ sessionId: effectiveSessionId
675
736
  });
676
737
  } catch (err) {
677
738
  onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
678
739
  } finally {
679
740
  setSubmitting(false);
680
741
  }
681
- }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]);
742
+ }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, onButtonClick, runBeforeButtonClick]);
682
743
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
683
744
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-paypal-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
684
745
  import_react_stripe_js.ExpressCheckoutElement,
685
746
  {
686
747
  onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["paypal"])),
687
748
  onLoadError: () => setLoadState("unavailable"),
749
+ onClick: handlePayPalClick,
688
750
  onConfirm: handlePayPalConfirm,
689
751
  onCancel: () => {
752
+ beforeClickRef.current = null;
690
753
  onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
691
754
  },
692
755
  options: {
@@ -712,7 +775,8 @@ function WalletButtonInner({
712
775
  onTokenizedBody,
713
776
  onErrorChange,
714
777
  onButtonClick,
715
- onDecline
778
+ onDecline,
779
+ runBeforeButtonClick
716
780
  }) {
717
781
  const stripe = (0, import_react_stripe_js.useStripe)();
718
782
  const elements = (0, import_react_stripe_js.useElements)();
@@ -720,10 +784,34 @@ function WalletButtonInner({
720
784
  const [submitting, setSubmitting] = (0, import_react6.useState)(false);
721
785
  const baseUrl = billingApiUrl.replace(/\/+$/, "");
722
786
  const lastWalletMethodRef = (0, import_react6.useRef)("card");
787
+ const beforeClickRef = (0, import_react6.useRef)(null);
723
788
  const handleWalletConfirm = (0, import_react6.useCallback)(
724
- async (_event) => {
789
+ async (event) => {
725
790
  if (!stripe || !elements) return;
726
- const walletType = _event.expressPaymentType;
791
+ const walletType = event.expressPaymentType;
792
+ let prepared = beforeClickRef.current;
793
+ beforeClickRef.current = null;
794
+ if (!prepared && runBeforeButtonClick) {
795
+ const beforeClick = await runBeforeButtonClick(
796
+ walletType === "apple_pay" ? "apple_pay" : "google_pay"
797
+ );
798
+ if (!beforeClick.proceed) {
799
+ event.paymentFailed({ reason: "fail", message: "Wallet checkout was cancelled." });
800
+ return;
801
+ }
802
+ prepared = {
803
+ accountPatch: beforeClick.accountPatch,
804
+ pendingInlinePatch: beforeClick.pendingInlinePatch ?? Promise.resolve({ error: null, sessionId: beforeClick.sessionId ?? sessionId })
805
+ };
806
+ }
807
+ const patchResult = prepared?.pendingInlinePatch ? await prepared.pendingInlinePatch : { error: null, sessionId };
808
+ if (patchResult.error) {
809
+ event.paymentFailed({ reason: "fail", message: patchResult.error.message });
810
+ return;
811
+ }
812
+ const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
813
+ const effectiveSessionId = patchResult.sessionId ?? sessionId;
814
+ const effectiveEmail = prepared?.accountPatch?.email ?? email;
727
815
  onButtonClick?.(walletType === "apple_pay" ? "apple_pay" : "google_pay");
728
816
  try {
729
817
  setSubmitting(true);
@@ -738,15 +826,15 @@ function WalletButtonInner({
738
826
  onErrorChange?.(pmError?.message ?? "Failed to create payment method.");
739
827
  return;
740
828
  }
741
- if (!sessionId || !email) {
829
+ if (!effectiveSessionId || !effectiveEmail) {
742
830
  throw new Error("Missing sessionId or email for wallet payment");
743
831
  }
744
832
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
745
833
  method: "POST",
746
834
  headers: { "Content-Type": "application/json" },
747
835
  body: JSON.stringify({
748
- sessionId,
749
- email,
836
+ sessionId: effectiveSessionId,
837
+ email: effectiveEmail,
750
838
  paymentMethodType: paymentMethod.id,
751
839
  isPaypal: false
752
840
  })
@@ -760,7 +848,6 @@ function WalletButtonInner({
760
848
  { payment_method: paymentMethod.id }
761
849
  );
762
850
  if (confirmError) {
763
- const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
764
851
  const message = confirmError.message ?? "Wallet payment failed.";
765
852
  onErrorChange?.(message);
766
853
  onDecline?.(buildDeclineEvent(method, message, {
@@ -772,6 +859,9 @@ function WalletButtonInner({
772
859
  id: paymentMethod.id,
773
860
  type: "card",
774
861
  threeDSecureActionResultTokenId: paymentIntent?.id
862
+ }, {
863
+ accountPatch: prepared?.accountPatch,
864
+ sessionId: effectiveSessionId
775
865
  });
776
866
  } catch (err) {
777
867
  onErrorChange?.(err instanceof Error ? err.message : "Wallet payment failed. Please try again.");
@@ -779,7 +869,7 @@ function WalletButtonInner({
779
869
  setSubmitting(false);
780
870
  }
781
871
  },
782
- [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]
872
+ [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, onButtonClick, runBeforeButtonClick]
783
873
  );
784
874
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
785
875
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-wallet-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -787,12 +877,23 @@ function WalletButtonInner({
787
877
  {
788
878
  onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["applePay", "googlePay"])),
789
879
  onLoadError: () => setLoadState("unavailable"),
790
- onClick: (event) => {
880
+ onClick: async (event) => {
791
881
  lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
882
+ const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick(lastWalletMethodRef.current, { deferInlineSessionPatch: true }) : { proceed: true };
883
+ if (!beforeClick.proceed) {
884
+ beforeClickRef.current = null;
885
+ event.reject();
886
+ return;
887
+ }
888
+ beforeClickRef.current = {
889
+ accountPatch: beforeClick.accountPatch,
890
+ pendingInlinePatch: beforeClick.pendingInlinePatch
891
+ };
792
892
  event.resolve();
793
893
  },
794
894
  onConfirm: handleWalletConfirm,
795
895
  onCancel: () => {
896
+ beforeClickRef.current = null;
796
897
  onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
797
898
  },
798
899
  options: {
@@ -954,55 +1055,91 @@ function SplitCardFormInner({
954
1055
  onFirstNameChange?.(parts[0] ?? "");
955
1056
  onLastNameChange?.(parts.length > 1 ? parts.slice(1).join(" ") : "");
956
1057
  }, [onFullNameChange, onFirstNameChange, onLastNameChange]);
957
- const runBeforeCardButtonClick = (0, import_react6.useCallback)(async () => {
958
- if (!onBeforeButtonClick) return true;
1058
+ const applyInlineSessionPatch = (0, import_react6.useCallback)(
1059
+ (patch, method) => {
1060
+ if (!checkout.applyInlineSessionPatch) {
1061
+ return Promise.resolve({ error: null, sessionId });
1062
+ }
1063
+ return checkout.applyInlineSessionPatch(patch).then((result) => ({
1064
+ error: null,
1065
+ sessionId: result.sessionId || sessionId
1066
+ })).catch((err) => {
1067
+ const floPayErr = normalizeBeforeButtonClickError(method, err);
1068
+ updateError(floPayErr.message);
1069
+ onError?.(floPayErr);
1070
+ return { error: floPayErr, sessionId };
1071
+ });
1072
+ },
1073
+ [checkout.applyInlineSessionPatch, onError, sessionId, updateError]
1074
+ );
1075
+ const runBeforeButtonClick = (0, import_react6.useCallback)(async (method, options) => {
1076
+ if (!onBeforeButtonClick) return { proceed: true };
959
1077
  try {
960
1078
  const result = await onBeforeButtonClick({
961
- method: "card",
962
- sessionId: sessionId || void 0
1079
+ method,
1080
+ sessionId: sessionId || void 0,
1081
+ createSession: checkout.inlineSessionDraft
963
1082
  });
964
1083
  if (result === false) {
965
- return false;
1084
+ return { proceed: false };
966
1085
  }
967
1086
  if (result && typeof result === "object") {
968
- await checkout.applyInlineSessionPatch?.(result);
969
1087
  if (result.account) {
970
1088
  setAccountPatch((prev) => ({ ...prev ?? {}, ...result.account }));
971
1089
  }
1090
+ const pendingInlinePatch = applyInlineSessionPatch(result, method);
1091
+ if (options?.deferInlineSessionPatch) {
1092
+ return {
1093
+ proceed: true,
1094
+ accountPatch: result.account,
1095
+ pendingInlinePatch
1096
+ };
1097
+ }
1098
+ const patchResult = await pendingInlinePatch;
1099
+ if (patchResult.error) {
1100
+ return {
1101
+ proceed: false,
1102
+ accountPatch: result.account
1103
+ };
1104
+ }
1105
+ return {
1106
+ proceed: true,
1107
+ accountPatch: result.account,
1108
+ sessionId: patchResult.sessionId
1109
+ };
972
1110
  }
973
- return true;
1111
+ return { proceed: true };
974
1112
  } catch (err) {
975
- const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(
976
- err instanceof Error ? err.message : "Card before-click hook failed.",
977
- "validation_error"
978
- );
1113
+ const floPayErr = normalizeBeforeButtonClickError(method, err);
979
1114
  updateError(floPayErr.message);
980
1115
  onError?.(floPayErr);
981
- return false;
1116
+ return { proceed: false };
982
1117
  }
983
- }, [checkout.applyInlineSessionPatch, onBeforeButtonClick, sessionId, updateError, onError]);
1118
+ }, [applyInlineSessionPatch, checkout.inlineSessionDraft, onBeforeButtonClick, onError, sessionId, updateError]);
984
1119
  const processPaymentInternal = (0, import_react6.useCallback)(
985
- async (tokenizedBody) => {
1120
+ async (tokenizedBody, overrides) => {
986
1121
  if (processingRef.current) return;
987
1122
  processingRef.current = true;
988
1123
  setProcessing(true);
989
1124
  setOverlayStatus("processing");
990
1125
  updateError(null);
1126
+ const effectiveSessionId = overrides?.sessionId ?? sessionId;
1127
+ const effectiveAccount = mergeAccountPatch(resolvedAccount, overrides?.accountPatch);
991
1128
  try {
992
1129
  const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
993
1130
  method: "POST",
994
1131
  headers: {
995
1132
  "Content-Type": "application/json",
996
- "x-user-id": resolvedAccount.userId ?? ""
1133
+ "x-user-id": effectiveAccount.userId ?? ""
997
1134
  },
998
1135
  body: JSON.stringify({
999
- sessionId,
1136
+ sessionId: effectiveSessionId,
1000
1137
  tokenizedData: tokenizedBody,
1001
1138
  accountData: {
1002
- userId: resolvedAccount.userId ?? "",
1003
- email: resolvedAccount.email ?? "",
1004
- firstName: resolvedAccount.firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
1005
- lastName: resolvedAccount.lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
1139
+ userId: effectiveAccount.userId ?? "",
1140
+ email: effectiveAccount.email ?? "",
1141
+ firstName: effectiveAccount.firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
1142
+ lastName: effectiveAccount.lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
1006
1143
  ...enableAVS ? { zip: zipCodeRef.current, country: selectedCountryRef.current } : {}
1007
1144
  },
1008
1145
  chv
@@ -1111,11 +1248,11 @@ function SplitCardFormInner({
1111
1248
  [baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, onComplete, onError, updateError, emitDecline]
1112
1249
  );
1113
1250
  const dispatchTokenizedBody = (0, import_react6.useCallback)(
1114
- (tokenizedBody) => {
1251
+ (tokenizedBody, overrides) => {
1115
1252
  if (onTokenizedBody) {
1116
1253
  onTokenizedBody(tokenizedBody);
1117
1254
  } else {
1118
- processPaymentInternal(tokenizedBody);
1255
+ processPaymentInternal(tokenizedBody, overrides);
1119
1256
  }
1120
1257
  },
1121
1258
  [onTokenizedBody, processPaymentInternal]
@@ -1542,6 +1679,7 @@ function SplitCardFormInner({
1542
1679
  if (layout === "buttons") {
1543
1680
  const isButtonsView = viewState === "buttons" || viewState === "expanding";
1544
1681
  const isCardView = viewState === "expanding" || viewState === "card" || viewState === "collapsing";
1682
+ const cardButtonSizing = cardButtonContent === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
1545
1683
  const buttonsAnim = viewState === "expanding" ? `flopay-buttons-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : viewState === "collapsing" ? `flopay-buttons-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : void 0;
1546
1684
  const cardAnim = viewState === "expanding" ? `flopay-card-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : viewState === "collapsing" ? `flopay-card-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : void 0;
1547
1685
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
@@ -1567,9 +1705,10 @@ function SplitCardFormInner({
1567
1705
  onErrorChange: updateError,
1568
1706
  isProcessing: isSubmitting,
1569
1707
  onButtonClick,
1570
- onDecline
1708
+ onDecline,
1709
+ runBeforeButtonClick
1571
1710
  }
1572
- ) }) : showPayPal ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1711
+ ) }) : showPayPal ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1573
1712
  showWallets && stripeInstance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1574
1713
  WalletButtonInner,
1575
1714
  {
@@ -1581,24 +1720,25 @@ function SplitCardFormInner({
1581
1720
  onTokenizedBody: dispatchTokenizedBody,
1582
1721
  onErrorChange: updateError,
1583
1722
  onButtonClick,
1584
- onDecline
1723
+ onDecline,
1724
+ runBeforeButtonClick
1585
1725
  }
1586
- ) }) : showWallets ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1726
+ ) }) : showWallets ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1587
1727
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1588
1728
  "button",
1589
1729
  {
1590
1730
  type: "button",
1591
1731
  onClick: async () => {
1592
1732
  if (isSubmitting) return;
1593
- const shouldContinue = await runBeforeCardButtonClick();
1594
- if (!shouldContinue) return;
1733
+ const beforeClick = await runBeforeButtonClick("card");
1734
+ if (!beforeClick.proceed) return;
1595
1735
  onButtonClick?.("card");
1596
1736
  expandToCard();
1597
1737
  },
1598
1738
  disabled: isSubmitting,
1599
1739
  style: {
1600
1740
  width: "100%",
1601
- padding: "0.9rem 1rem",
1741
+ ...cardButtonSizing,
1602
1742
  backgroundColor: "white",
1603
1743
  color: "#262833",
1604
1744
  border: "1px solid #d1d5db",
@@ -1697,6 +1837,7 @@ function SplitCardFormInner({
1697
1837
 
1698
1838
  // src/flopay-checkout.tsx
1699
1839
  var import_jsx_runtime5 = require("react/jsx-runtime");
1840
+ var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2 = 44;
1700
1841
  function hasInlineSessionPatchData(patch) {
1701
1842
  if (!patch) return false;
1702
1843
  return Boolean(
@@ -2003,16 +2144,27 @@ function FloPayCheckout({
2003
2144
  [fallbackPublishableKey, locale, resolvedBillingUrl]
2004
2145
  );
2005
2146
  const handleInlineSessionPatch = (0, import_react7.useCallback)(async (patch) => {
2006
- if (!hasInlineSessionPatchData(patch) || cardBootstrapPending) return;
2147
+ if (!hasInlineSessionPatchData(patch) || cardBootstrapPending) {
2148
+ return {
2149
+ sessionId: resolvedSessionId,
2150
+ session
2151
+ };
2152
+ }
2007
2153
  setCardBootstrapPending(true);
2008
2154
  try {
2009
- await bootstrapInlineSession(patch);
2155
+ const resolved = await bootstrapInlineSession(patch);
2156
+ return {
2157
+ sessionId: resolved.sid,
2158
+ session: resolved.result.data.session ?? null
2159
+ };
2010
2160
  } finally {
2011
2161
  setCardBootstrapPending(false);
2012
2162
  }
2013
2163
  }, [
2014
2164
  bootstrapInlineSession,
2015
- cardBootstrapPending
2165
+ cardBootstrapPending,
2166
+ resolvedSessionId,
2167
+ session
2016
2168
  ]);
2017
2169
  (0, import_react7.useEffect)(() => {
2018
2170
  let cancelled = false;
@@ -2165,6 +2317,7 @@ function FloPayCheckout({
2165
2317
  loading: isLoading,
2166
2318
  error: loadError,
2167
2319
  checkoutMode: currentMode,
2320
+ inlineSessionDraft: effectiveCreateSession,
2168
2321
  applyInlineSessionPatch: shouldHandleInlineSessionPatch ? handleInlineSessionPatch : void 0,
2169
2322
  inlineSessionPatchProcessing: cardBootstrapPending
2170
2323
  }),
@@ -2173,6 +2326,7 @@ function FloPayCheckout({
2173
2326
  isLoading,
2174
2327
  loadError,
2175
2328
  currentMode,
2329
+ effectiveCreateSession,
2176
2330
  shouldHandleInlineSessionPatch,
2177
2331
  handleInlineSessionPatch,
2178
2332
  cardBootstrapPending
@@ -2189,9 +2343,9 @@ function FloPayCheckout({
2189
2343
  animation: "flopay-loading-pulse 1.5s ease-in-out infinite"
2190
2344
  } });
2191
2345
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
2192
- showPayPal && skeletonBar(44),
2193
- (showApplePay || showGooglePay) && skeletonBar(44),
2194
- skeletonBar(48),
2346
+ showPayPal && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
2347
+ (showApplePay || showGooglePay) && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
2348
+ skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
2195
2349
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes flopay-loading-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
2196
2350
  ] });
2197
2351
  }
@@ -2405,6 +2559,7 @@ function InterimButtonsView({
2405
2559
  background: "#e5e7eb",
2406
2560
  animation: "flopay-interim-pulse 1.5s ease-in-out infinite"
2407
2561
  } });
2562
+ const cardButtonSizing = cardButtonContent === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
2408
2563
  if (showCardForm) {
2409
2564
  const inputBorder = bStyles.cardInputBorder ?? "#e5e7eb";
2410
2565
  const inputBg = bStyles.cardInputBackground ?? "white";
@@ -2495,8 +2650,8 @@ function InterimButtonsView({
2495
2650
  ] });
2496
2651
  }
2497
2652
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
2498
- showPayPal && skeleton(44),
2499
- (showApplePay || showGooglePay) && skeleton(44),
2653
+ showPayPal && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
2654
+ (showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
2500
2655
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2501
2656
  "button",
2502
2657
  {
@@ -2513,7 +2668,7 @@ function InterimButtonsView({
2513
2668
  disabled: cardLoading,
2514
2669
  style: {
2515
2670
  width: "100%",
2516
- padding: "0.9rem 1rem",
2671
+ ...cardButtonSizing,
2517
2672
  backgroundColor: "white",
2518
2673
  color: "#262833",
2519
2674
  border: "1px solid #d1d5db",