@flopay/react 0.3.15 → 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
@@ -131,14 +131,20 @@ Use `onButtonClick` to track button interactions, `onDecline` to track declines/
131
131
  createSession={{
132
132
  clientId: 'your-client-id',
133
133
  items: [{ providerItemId: 'product-1', providerItemName: 'Widget', totalAmount: 29.99, currency: 'EUR' }],
134
- account: { userId: 'user_1' },
134
+ account: { userId: 'user_1', email: 'test@email.com' },
135
135
  successUrl: '/success',
136
136
  cancelUrl: '/cancel',
137
137
  }}
138
- onBeforeButtonClick={async ({ method }) => {
139
- if (method !== 'card') return;
140
-
141
- const email = await openEmailCaptureModal();
138
+ onBeforeButtonClick={async ({ method, createSession }) => {
139
+ if (method !== 'card') {
140
+ return {
141
+ tagsData: { sessionId: `checkout_${method}_clicked` },
142
+ };
143
+ }
144
+
145
+ const email = await openEmailCaptureModal({
146
+ initialEmail: createSession?.account.email ?? '',
147
+ });
142
148
  if (!email) return false;
143
149
 
144
150
  return {
@@ -156,7 +162,7 @@ Use `onButtonClick` to track button interactions, `onDecline` to track declines/
156
162
  />
157
163
  ```
158
164
 
159
- `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.
160
166
 
161
167
  #### Inline Session Creation
162
168
 
@@ -178,7 +184,7 @@ Skip the backend API route — create the session directly in the component:
178
184
 
179
185
  The component POSTs to the billing API, gets the full session back, and renders the form — zero backend code needed.
180
186
 
181
- When you use `onBeforeButtonClick`, `createSession.account.email` can still be filled in later for the card-button flow. 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.
182
188
 
183
189
  ### Advanced: Manual Provider Setup
184
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
  }
@@ -405,6 +424,52 @@ function toCssWeight(value) {
405
424
  if (typeof value === "number" || typeof value === "string") return value;
406
425
  return void 0;
407
426
  }
427
+ function resolveExpressCheckoutLoadState(event, methods) {
428
+ const available = event.availablePaymentMethods;
429
+ if (!available) return "unavailable";
430
+ return methods.some((method) => available[method]) ? "ready" : "unavailable";
431
+ }
432
+ function ExpressCheckoutReadySwap({
433
+ state,
434
+ placeholderTestId,
435
+ children
436
+ }) {
437
+ if (state === "unavailable") return null;
438
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { position: "relative", minHeight: 44 }, children: [
439
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
440
+ "div",
441
+ {
442
+ "data-testid": placeholderTestId,
443
+ "aria-hidden": state === "ready",
444
+ style: {
445
+ position: "absolute",
446
+ inset: 0,
447
+ height: 44,
448
+ borderRadius: 8,
449
+ background: "#e5e7eb",
450
+ animation: state === "ready" ? void 0 : "flopay-pulse 1.5s ease-in-out infinite",
451
+ opacity: state === "ready" ? 0 : 1,
452
+ transform: state === "ready" ? "scale(0.985)" : "scale(1)",
453
+ transition: "opacity 140ms ease-out, transform 140ms ease-out",
454
+ pointerEvents: "none"
455
+ }
456
+ }
457
+ ),
458
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
459
+ "div",
460
+ {
461
+ style: {
462
+ minHeight: 44,
463
+ opacity: state === "ready" ? 1 : 0,
464
+ transform: state === "ready" ? "translateY(0)" : "translateY(2px)",
465
+ transition: "opacity 140ms ease-out, transform 140ms ease-out",
466
+ pointerEvents: state === "ready" ? "auto" : "none"
467
+ },
468
+ children
469
+ }
470
+ )
471
+ ] });
472
+ }
408
473
  function ProcessingOverlay({ status, errorMessage }) {
409
474
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
410
475
  position: "fixed",
@@ -518,13 +583,15 @@ function PayPalButtonInner({
518
583
  onErrorChange,
519
584
  isProcessing = false,
520
585
  onButtonClick,
521
- onDecline
586
+ onDecline,
587
+ runBeforeButtonClick
522
588
  }) {
523
589
  const stripe = (0, import_react_stripe_js.useStripe)();
524
590
  const elements = (0, import_react_stripe_js.useElements)();
525
- const [ready, setReady] = (0, import_react6.useState)(false);
591
+ const [loadState, setLoadState] = (0, import_react6.useState)("loading");
526
592
  const [submitting, setSubmitting] = (0, import_react6.useState)(false);
527
593
  const paypalResumeAttempted = (0, import_react6.useRef)(false);
594
+ const beforeClickRef = (0, import_react6.useRef)(null);
528
595
  const baseUrl = billingApiUrl.replace(/\/+$/, "");
529
596
  (0, import_react6.useEffect)(() => {
530
597
  if (!stripe || paypalResumeAttempted.current) return;
@@ -573,13 +640,50 @@ function PayPalButtonInner({
573
640
  }
574
641
  })();
575
642
  }, [stripe, onTokenizedBody, onErrorChange, onDecline]);
576
- 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) => {
577
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;
578
682
  onButtonClick?.("paypal");
579
683
  try {
580
684
  setSubmitting(true);
581
685
  onErrorChange?.(null);
582
- if (!sessionId || !email) {
686
+ if (!effectiveSessionId || !effectiveEmail) {
583
687
  throw new Error("Missing sessionId or email for PayPal payment");
584
688
  }
585
689
  const createPM = stripe.createPaymentMethod;
@@ -591,8 +695,8 @@ function PayPalButtonInner({
591
695
  method: "POST",
592
696
  headers: { "Content-Type": "application/json" },
593
697
  body: JSON.stringify({
594
- sessionId,
595
- email,
698
+ sessionId: effectiveSessionId,
699
+ email: effectiveEmail,
596
700
  paymentMethodType: paymentMethod?.id ?? "paypal",
597
701
  isPaypal: "true"
598
702
  })
@@ -626,22 +730,26 @@ function PayPalButtonInner({
626
730
  type: "card",
627
731
  threeDSecureActionResultTokenId: paymentIntent?.id,
628
732
  isPaypal: true
733
+ }, {
734
+ accountPatch: prepared?.accountPatch,
735
+ sessionId: effectiveSessionId
629
736
  });
630
737
  } catch (err) {
631
738
  onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
632
739
  } finally {
633
740
  setSubmitting(false);
634
741
  }
635
- }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]);
742
+ }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, onButtonClick, runBeforeButtonClick]);
636
743
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
637
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
744
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-paypal-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
638
745
  import_react_stripe_js.ExpressCheckoutElement,
639
746
  {
640
- onReady: () => setReady(true),
641
- onLoadError: () => {
642
- },
747
+ onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["paypal"])),
748
+ onLoadError: () => setLoadState("unavailable"),
749
+ onClick: handlePayPalClick,
643
750
  onConfirm: handlePayPalConfirm,
644
751
  onCancel: () => {
752
+ beforeClickRef.current = null;
645
753
  onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
646
754
  },
647
755
  options: {
@@ -667,18 +775,43 @@ function WalletButtonInner({
667
775
  onTokenizedBody,
668
776
  onErrorChange,
669
777
  onButtonClick,
670
- onDecline
778
+ onDecline,
779
+ runBeforeButtonClick
671
780
  }) {
672
781
  const stripe = (0, import_react_stripe_js.useStripe)();
673
782
  const elements = (0, import_react_stripe_js.useElements)();
674
- const [ready, setReady] = (0, import_react6.useState)(false);
783
+ const [loadState, setLoadState] = (0, import_react6.useState)("loading");
675
784
  const [submitting, setSubmitting] = (0, import_react6.useState)(false);
676
785
  const baseUrl = billingApiUrl.replace(/\/+$/, "");
677
786
  const lastWalletMethodRef = (0, import_react6.useRef)("card");
787
+ const beforeClickRef = (0, import_react6.useRef)(null);
678
788
  const handleWalletConfirm = (0, import_react6.useCallback)(
679
- async (_event) => {
789
+ async (event) => {
680
790
  if (!stripe || !elements) return;
681
- 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;
682
815
  onButtonClick?.(walletType === "apple_pay" ? "apple_pay" : "google_pay");
683
816
  try {
684
817
  setSubmitting(true);
@@ -693,15 +826,15 @@ function WalletButtonInner({
693
826
  onErrorChange?.(pmError?.message ?? "Failed to create payment method.");
694
827
  return;
695
828
  }
696
- if (!sessionId || !email) {
829
+ if (!effectiveSessionId || !effectiveEmail) {
697
830
  throw new Error("Missing sessionId or email for wallet payment");
698
831
  }
699
832
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
700
833
  method: "POST",
701
834
  headers: { "Content-Type": "application/json" },
702
835
  body: JSON.stringify({
703
- sessionId,
704
- email,
836
+ sessionId: effectiveSessionId,
837
+ email: effectiveEmail,
705
838
  paymentMethodType: paymentMethod.id,
706
839
  isPaypal: false
707
840
  })
@@ -715,7 +848,6 @@ function WalletButtonInner({
715
848
  { payment_method: paymentMethod.id }
716
849
  );
717
850
  if (confirmError) {
718
- const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
719
851
  const message = confirmError.message ?? "Wallet payment failed.";
720
852
  onErrorChange?.(message);
721
853
  onDecline?.(buildDeclineEvent(method, message, {
@@ -727,6 +859,9 @@ function WalletButtonInner({
727
859
  id: paymentMethod.id,
728
860
  type: "card",
729
861
  threeDSecureActionResultTokenId: paymentIntent?.id
862
+ }, {
863
+ accountPatch: prepared?.accountPatch,
864
+ sessionId: effectiveSessionId
730
865
  });
731
866
  } catch (err) {
732
867
  onErrorChange?.(err instanceof Error ? err.message : "Wallet payment failed. Please try again.");
@@ -734,21 +869,31 @@ function WalletButtonInner({
734
869
  setSubmitting(false);
735
870
  }
736
871
  },
737
- [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]
872
+ [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, onButtonClick, runBeforeButtonClick]
738
873
  );
739
874
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
740
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
875
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-wallet-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
741
876
  import_react_stripe_js.ExpressCheckoutElement,
742
877
  {
743
- onReady: () => setReady(true),
744
- onLoadError: () => {
745
- },
746
- onClick: (event) => {
878
+ onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["applePay", "googlePay"])),
879
+ onLoadError: () => setLoadState("unavailable"),
880
+ onClick: async (event) => {
747
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
+ };
748
892
  event.resolve();
749
893
  },
750
894
  onConfirm: handleWalletConfirm,
751
895
  onCancel: () => {
896
+ beforeClickRef.current = null;
752
897
  onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
753
898
  },
754
899
  options: {
@@ -786,6 +931,7 @@ function SplitCardFormInner({
786
931
  isProcessing: externalProcessing,
787
932
  error: externalError,
788
933
  onErrorChange,
934
+ onFullNameChange,
789
935
  onFirstNameChange,
790
936
  onLastNameChange,
791
937
  showPayPal = true,
@@ -904,59 +1050,96 @@ function SplitCardFormInner({
904
1050
  const showWallets = showApplePay || showGooglePay;
905
1051
  const handleNameChange = (0, import_react6.useCallback)((value) => {
906
1052
  setFullName(value);
1053
+ onFullNameChange?.(value);
907
1054
  const parts = value.trim().split(/\s+/);
908
1055
  onFirstNameChange?.(parts[0] ?? "");
909
1056
  onLastNameChange?.(parts.length > 1 ? parts.slice(1).join(" ") : "");
910
- }, [onFirstNameChange, onLastNameChange]);
911
- const runBeforeCardButtonClick = (0, import_react6.useCallback)(async () => {
912
- if (!onBeforeButtonClick) return true;
1057
+ }, [onFullNameChange, onFirstNameChange, onLastNameChange]);
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 };
913
1077
  try {
914
1078
  const result = await onBeforeButtonClick({
915
- method: "card",
916
- sessionId: sessionId || void 0
1079
+ method,
1080
+ sessionId: sessionId || void 0,
1081
+ createSession: checkout.inlineSessionDraft
917
1082
  });
918
1083
  if (result === false) {
919
- return false;
1084
+ return { proceed: false };
920
1085
  }
921
1086
  if (result && typeof result === "object") {
922
- await checkout.applyInlineSessionPatch?.(result);
923
1087
  if (result.account) {
924
1088
  setAccountPatch((prev) => ({ ...prev ?? {}, ...result.account }));
925
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
+ };
926
1110
  }
927
- return true;
1111
+ return { proceed: true };
928
1112
  } catch (err) {
929
- const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(
930
- err instanceof Error ? err.message : "Card before-click hook failed.",
931
- "validation_error"
932
- );
1113
+ const floPayErr = normalizeBeforeButtonClickError(method, err);
933
1114
  updateError(floPayErr.message);
934
1115
  onError?.(floPayErr);
935
- return false;
1116
+ return { proceed: false };
936
1117
  }
937
- }, [checkout.applyInlineSessionPatch, onBeforeButtonClick, sessionId, updateError, onError]);
1118
+ }, [applyInlineSessionPatch, checkout.inlineSessionDraft, onBeforeButtonClick, onError, sessionId, updateError]);
938
1119
  const processPaymentInternal = (0, import_react6.useCallback)(
939
- async (tokenizedBody) => {
1120
+ async (tokenizedBody, overrides) => {
940
1121
  if (processingRef.current) return;
941
1122
  processingRef.current = true;
942
1123
  setProcessing(true);
943
1124
  setOverlayStatus("processing");
944
1125
  updateError(null);
1126
+ const effectiveSessionId = overrides?.sessionId ?? sessionId;
1127
+ const effectiveAccount = mergeAccountPatch(resolvedAccount, overrides?.accountPatch);
945
1128
  try {
946
1129
  const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
947
1130
  method: "POST",
948
1131
  headers: {
949
1132
  "Content-Type": "application/json",
950
- "x-user-id": resolvedAccount.userId ?? ""
1133
+ "x-user-id": effectiveAccount.userId ?? ""
951
1134
  },
952
1135
  body: JSON.stringify({
953
- sessionId,
1136
+ sessionId: effectiveSessionId,
954
1137
  tokenizedData: tokenizedBody,
955
1138
  accountData: {
956
- userId: resolvedAccount.userId ?? "",
957
- email: resolvedAccount.email ?? "",
958
- firstName: resolvedAccount.firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
959
- 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(" ") ?? "",
960
1143
  ...enableAVS ? { zip: zipCodeRef.current, country: selectedCountryRef.current } : {}
961
1144
  },
962
1145
  chv
@@ -1065,11 +1248,11 @@ function SplitCardFormInner({
1065
1248
  [baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, onComplete, onError, updateError, emitDecline]
1066
1249
  );
1067
1250
  const dispatchTokenizedBody = (0, import_react6.useCallback)(
1068
- (tokenizedBody) => {
1251
+ (tokenizedBody, overrides) => {
1069
1252
  if (onTokenizedBody) {
1070
1253
  onTokenizedBody(tokenizedBody);
1071
1254
  } else {
1072
- processPaymentInternal(tokenizedBody);
1255
+ processPaymentInternal(tokenizedBody, overrides);
1073
1256
  }
1074
1257
  },
1075
1258
  [onTokenizedBody, processPaymentInternal]
@@ -1496,6 +1679,7 @@ function SplitCardFormInner({
1496
1679
  if (layout === "buttons") {
1497
1680
  const isButtonsView = viewState === "buttons" || viewState === "expanding";
1498
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" };
1499
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;
1500
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;
1501
1685
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
@@ -1521,9 +1705,10 @@ function SplitCardFormInner({
1521
1705
  onErrorChange: updateError,
1522
1706
  isProcessing: isSubmitting,
1523
1707
  onButtonClick,
1524
- onDecline
1708
+ onDecline,
1709
+ runBeforeButtonClick
1525
1710
  }
1526
- ) }) : 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,
1527
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)(
1528
1713
  WalletButtonInner,
1529
1714
  {
@@ -1535,24 +1720,25 @@ function SplitCardFormInner({
1535
1720
  onTokenizedBody: dispatchTokenizedBody,
1536
1721
  onErrorChange: updateError,
1537
1722
  onButtonClick,
1538
- onDecline
1723
+ onDecline,
1724
+ runBeforeButtonClick
1539
1725
  }
1540
- ) }) : 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,
1541
1727
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1542
1728
  "button",
1543
1729
  {
1544
1730
  type: "button",
1545
1731
  onClick: async () => {
1546
1732
  if (isSubmitting) return;
1547
- const shouldContinue = await runBeforeCardButtonClick();
1548
- if (!shouldContinue) return;
1733
+ const beforeClick = await runBeforeButtonClick("card");
1734
+ if (!beforeClick.proceed) return;
1549
1735
  onButtonClick?.("card");
1550
1736
  expandToCard();
1551
1737
  },
1552
1738
  disabled: isSubmitting,
1553
1739
  style: {
1554
1740
  width: "100%",
1555
- padding: "0.9rem 1rem",
1741
+ ...cardButtonSizing,
1556
1742
  backgroundColor: "white",
1557
1743
  color: "#262833",
1558
1744
  border: "1px solid #d1d5db",
@@ -1651,6 +1837,7 @@ function SplitCardFormInner({
1651
1837
 
1652
1838
  // src/flopay-checkout.tsx
1653
1839
  var import_jsx_runtime5 = require("react/jsx-runtime");
1840
+ var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2 = 44;
1654
1841
  function hasInlineSessionPatchData(patch) {
1655
1842
  if (!patch) return false;
1656
1843
  return Boolean(
@@ -1669,6 +1856,9 @@ function FloPayCheckout({
1669
1856
  onComplete,
1670
1857
  onError,
1671
1858
  onDecline,
1859
+ onFullNameChange,
1860
+ onCountryChange,
1861
+ onZipChange,
1672
1862
  showPayPal = true,
1673
1863
  showApplePay = true,
1674
1864
  showGooglePay = true,
@@ -1954,16 +2144,27 @@ function FloPayCheckout({
1954
2144
  [fallbackPublishableKey, locale, resolvedBillingUrl]
1955
2145
  );
1956
2146
  const handleInlineSessionPatch = (0, import_react7.useCallback)(async (patch) => {
1957
- if (!hasInlineSessionPatchData(patch) || cardBootstrapPending) return;
2147
+ if (!hasInlineSessionPatchData(patch) || cardBootstrapPending) {
2148
+ return {
2149
+ sessionId: resolvedSessionId,
2150
+ session
2151
+ };
2152
+ }
1958
2153
  setCardBootstrapPending(true);
1959
2154
  try {
1960
- await bootstrapInlineSession(patch);
2155
+ const resolved = await bootstrapInlineSession(patch);
2156
+ return {
2157
+ sessionId: resolved.sid,
2158
+ session: resolved.result.data.session ?? null
2159
+ };
1961
2160
  } finally {
1962
2161
  setCardBootstrapPending(false);
1963
2162
  }
1964
2163
  }, [
1965
2164
  bootstrapInlineSession,
1966
- cardBootstrapPending
2165
+ cardBootstrapPending,
2166
+ resolvedSessionId,
2167
+ session
1967
2168
  ]);
1968
2169
  (0, import_react7.useEffect)(() => {
1969
2170
  let cancelled = false;
@@ -2116,6 +2317,7 @@ function FloPayCheckout({
2116
2317
  loading: isLoading,
2117
2318
  error: loadError,
2118
2319
  checkoutMode: currentMode,
2320
+ inlineSessionDraft: effectiveCreateSession,
2119
2321
  applyInlineSessionPatch: shouldHandleInlineSessionPatch ? handleInlineSessionPatch : void 0,
2120
2322
  inlineSessionPatchProcessing: cardBootstrapPending
2121
2323
  }),
@@ -2124,6 +2326,7 @@ function FloPayCheckout({
2124
2326
  isLoading,
2125
2327
  loadError,
2126
2328
  currentMode,
2329
+ effectiveCreateSession,
2127
2330
  shouldHandleInlineSessionPatch,
2128
2331
  handleInlineSessionPatch,
2129
2332
  cardBootstrapPending
@@ -2140,9 +2343,9 @@ function FloPayCheckout({
2140
2343
  animation: "flopay-loading-pulse 1.5s ease-in-out infinite"
2141
2344
  } });
2142
2345
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
2143
- showPayPal && skeletonBar(44),
2144
- (showApplePay || showGooglePay) && skeletonBar(44),
2145
- 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),
2146
2349
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes flopay-loading-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
2147
2350
  ] });
2148
2351
  }
@@ -2267,6 +2470,7 @@ function FloPayCheckout({
2267
2470
  onComplete,
2268
2471
  onError,
2269
2472
  onDecline,
2473
+ onFullNameChange,
2270
2474
  showPayPal,
2271
2475
  showApplePay,
2272
2476
  showGooglePay,
@@ -2281,6 +2485,8 @@ function FloPayCheckout({
2281
2485
  enableAVS,
2282
2486
  avsLayout,
2283
2487
  country: session?.customer?.country,
2488
+ onCountryChange,
2489
+ onZipChange,
2284
2490
  submitLabel,
2285
2491
  className
2286
2492
  }
@@ -2353,6 +2559,7 @@ function InterimButtonsView({
2353
2559
  background: "#e5e7eb",
2354
2560
  animation: "flopay-interim-pulse 1.5s ease-in-out infinite"
2355
2561
  } });
2562
+ const cardButtonSizing = cardButtonContent === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
2356
2563
  if (showCardForm) {
2357
2564
  const inputBorder = bStyles.cardInputBorder ?? "#e5e7eb";
2358
2565
  const inputBg = bStyles.cardInputBackground ?? "white";
@@ -2443,8 +2650,8 @@ function InterimButtonsView({
2443
2650
  ] });
2444
2651
  }
2445
2652
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
2446
- showPayPal && skeleton(44),
2447
- (showApplePay || showGooglePay) && skeleton(44),
2653
+ showPayPal && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
2654
+ (showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
2448
2655
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2449
2656
  "button",
2450
2657
  {
@@ -2461,7 +2668,7 @@ function InterimButtonsView({
2461
2668
  disabled: cardLoading,
2462
2669
  style: {
2463
2670
  width: "100%",
2464
- padding: "0.9rem 1rem",
2671
+ ...cardButtonSizing,
2465
2672
  backgroundColor: "white",
2466
2673
  color: "#262833",
2467
2674
  border: "1px solid #d1d5db",