@flopay/react 0.1.6 → 0.3.1

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
@@ -69,7 +69,7 @@ function FloPayProvider({
69
69
  // src/flopay-checkout.tsx
70
70
  import React4, { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
71
71
  import { loadFloPay, PaymentAPI } from "@flopay/js";
72
- import { FloPayError as FloPayError2, resolveBillingApiUrl as resolveBillingApiUrl3, buildCheckoutDisplayData } from "@flopay/shared";
72
+ import { FloPayError as FloPayError2, resolveBillingApiUrl as resolveBillingApiUrl3, buildCheckoutDisplayData, resolveButtonsLayoutTheme as resolveButtonsLayoutTheme2 } from "@flopay/shared";
73
73
 
74
74
  // src/elements.tsx
75
75
  import { useEffect as useEffect2, useRef, useContext } from "react";
@@ -138,6 +138,7 @@ import {
138
138
  useElements as useStripeElements,
139
139
  useStripe as useStripeRaw
140
140
  } from "@stripe/react-stripe-js";
141
+ import { resolveButtonsLayoutTheme, getPostalCodeLabel, COUNTRY_OPTIONS } from "@flopay/shared";
141
142
  import { forwardRef, useCallback, useEffect as useEffect3, useImperativeHandle, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
142
143
 
143
144
  // src/hooks.ts
@@ -163,6 +164,29 @@ function useBillingApiUrl() {
163
164
  import { FloPayError } from "@flopay/shared";
164
165
  import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
165
166
  var WALLET_RESUME_KEY = "flopay_wallet_resume";
167
+ var FLOPAY_KEYFRAMES = `
168
+ @keyframes flopay-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
169
+ @keyframes flopay-fade-in { 0% { opacity: 0; } 100% { opacity: 1; } }
170
+ @keyframes flopay-buttons-enter {
171
+ 0% { opacity: 0; transform: translateX(-16px) scale(0.97); }
172
+ 100% { opacity: 1; transform: translateX(0) scale(1); }
173
+ }
174
+ @keyframes flopay-buttons-exit {
175
+ 0% { opacity: 1; transform: translateX(0) scale(1); }
176
+ 100% { opacity: 0; transform: translateX(-16px) scale(0.97); }
177
+ }
178
+ @keyframes flopay-card-enter {
179
+ 0% { opacity: 0; transform: translateX(16px) scale(0.97); }
180
+ 100% { opacity: 1; transform: translateX(0) scale(1); }
181
+ }
182
+ @keyframes flopay-card-exit {
183
+ 0% { opacity: 1; transform: translateX(0) scale(1); }
184
+ 100% { opacity: 0; transform: translateX(16px) scale(0.97); }
185
+ }
186
+ `;
187
+ function FloPayKeyframes() {
188
+ return /* @__PURE__ */ jsx3("style", { children: FLOPAY_KEYFRAMES });
189
+ }
166
190
  function ProcessingOverlay({ status, errorMessage }) {
167
191
  return /* @__PURE__ */ jsx3("div", { style: {
168
192
  position: "fixed",
@@ -252,8 +276,6 @@ function ProcessingOverlay({ status, errorMessage }) {
252
276
  @keyframes flopay-pop { 0% { transform: scale(0); } 100% { transform: scale(1); } }
253
277
  @keyframes flopay-draw { to { stroke-dashoffset: 0; } }
254
278
  @keyframes flopay-shake { 0%,100% { transform: translateX(0); } 20%,60% { transform: translateX(-4px); } 40%,80% { transform: translateX(4px); } }
255
- @keyframes flopay-expand { 0% { opacity: 0; clip-path: inset(0 0 100% 0); transform: translateY(-8px); } 100% { opacity: 1; clip-path: inset(0 0 0 0); transform: translateY(0); } }
256
- @keyframes flopay-fade-in { 0% { opacity: 0; } 100% { opacity: 1; } }
257
279
  ` })
258
280
  ] }) });
259
281
  }
@@ -268,7 +290,8 @@ function PayPalButtonInner({
268
290
  billingApiUrl,
269
291
  onTokenizedBody,
270
292
  onErrorChange,
271
- isProcessing = false
293
+ isProcessing = false,
294
+ onButtonClick
272
295
  }) {
273
296
  const stripe = useStripeRaw();
274
297
  const elements = useStripeElements();
@@ -321,6 +344,7 @@ function PayPalButtonInner({
321
344
  }, [stripe, onTokenizedBody, onErrorChange]);
322
345
  const handlePayPalConfirm = useCallback(async (_event) => {
323
346
  if (!stripe || !elements) return;
347
+ onButtonClick?.("paypal");
324
348
  try {
325
349
  setSubmitting(true);
326
350
  onErrorChange?.(null);
@@ -375,7 +399,7 @@ function PayPalButtonInner({
375
399
  }
376
400
  }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]);
377
401
  return /* @__PURE__ */ jsxs(Fragment, { children: [
378
- /* @__PURE__ */ jsx3("div", { style: { marginBottom: ready ? "0.5rem" : 0 }, children: /* @__PURE__ */ jsx3(
402
+ /* @__PURE__ */ jsx3("div", { children: /* @__PURE__ */ jsx3(
379
403
  ExpressCheckoutElement,
380
404
  {
381
405
  onReady: () => setReady(true),
@@ -403,7 +427,8 @@ function WalletButtonInner({
403
427
  showApplePay = true,
404
428
  showGooglePay = true,
405
429
  onTokenizedBody,
406
- onErrorChange
430
+ onErrorChange,
431
+ onButtonClick
407
432
  }) {
408
433
  const stripe = useStripeRaw();
409
434
  const elements = useStripeElements();
@@ -413,6 +438,8 @@ function WalletButtonInner({
413
438
  const handleWalletConfirm = useCallback(
414
439
  async (_event) => {
415
440
  if (!stripe || !elements) return;
441
+ const walletType = _event.expressPaymentType;
442
+ onButtonClick?.(walletType === "apple_pay" ? "apple_pay" : "google_pay");
416
443
  try {
417
444
  setSubmitting(true);
418
445
  onErrorChange?.(null);
@@ -465,7 +492,7 @@ function WalletButtonInner({
465
492
  [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]
466
493
  );
467
494
  return /* @__PURE__ */ jsxs(Fragment, { children: [
468
- /* @__PURE__ */ jsx3("div", { style: { marginBottom: ready ? "0.5rem" : 0 }, children: /* @__PURE__ */ jsx3(
495
+ /* @__PURE__ */ jsx3("div", { children: /* @__PURE__ */ jsx3(
469
496
  ExpressCheckoutElement,
470
497
  {
471
498
  onReady: () => setReady(true),
@@ -512,6 +539,15 @@ function SplitCardFormInner({
512
539
  showApplePay = true,
513
540
  showGooglePay = true,
514
541
  layout = "default",
542
+ buttonsTheme,
543
+ buttonsStyles: buttonsStylesOverride,
544
+ onButtonClick,
545
+ enableAVS = false,
546
+ avsLayout: avsLayoutProp = "row",
547
+ country: countryProp,
548
+ zip: zipProp,
549
+ onCountryChange,
550
+ onZipChange,
515
551
  totalAmount = 0,
516
552
  currency = "usd",
517
553
  innerRef
@@ -521,14 +557,42 @@ function SplitCardFormInner({
521
557
  const contextBillingUrl = useBillingApiUrl();
522
558
  const [processing, setProcessing] = useState2(false);
523
559
  const [error, setError] = useState2(null);
524
- const [showCardForm, setShowCardForm] = useState2(false);
525
560
  const [is3DSActive, setIs3DSActive] = useState2(false);
561
+ const [selectedCountry, setSelectedCountry] = useState2(countryProp ?? "US");
562
+ const [zipCode, setZipCode] = useState2(zipProp ?? "");
563
+ const zipCodeRef = useRef2(zipProp ?? "");
564
+ const selectedCountryRef = useRef2(countryProp ?? "US");
565
+ const [viewState, setViewState] = useState2("buttons");
566
+ const showCardForm = viewState === "expanding" || viewState === "card";
567
+ const TRANSITION_MS = 280;
568
+ const expandToCard = useCallback(() => {
569
+ setViewState("expanding");
570
+ setTimeout(() => setViewState("card"), TRANSITION_MS);
571
+ }, []);
572
+ const collapseToButtons = useCallback(() => {
573
+ setViewState("collapsing");
574
+ setTimeout(() => setViewState("buttons"), TRANSITION_MS);
575
+ }, []);
526
576
  const [fullName, setFullName] = useState2("");
527
577
  const [formReady, setFormReady] = useState2(false);
528
578
  const [overlayStatus, setOverlayStatus] = useState2(null);
529
579
  const processingRef = useRef2(false);
530
580
  const resolvedBillingApiUrl = billingApiUrl || contextBillingUrl;
531
581
  const displayError = externalError ?? error;
582
+ const bStyles = useMemo2(() => {
583
+ const base = resolveButtonsLayoutTheme(buttonsTheme);
584
+ if (!buttonsStylesOverride) return base;
585
+ return {
586
+ ...base,
587
+ ...buttonsStylesOverride,
588
+ cardButton: { ...base.cardButton, ...buttonsStylesOverride.cardButton },
589
+ cardFormContainer: { ...base.cardFormContainer, ...buttonsStylesOverride.cardFormContainer },
590
+ backButton: { ...base.backButton, ...buttonsStylesOverride.backButton },
591
+ backButtonIcon: { ...base.backButtonIcon, ...buttonsStylesOverride.backButtonIcon },
592
+ submitButton: { ...base.submitButton, ...buttonsStylesOverride.submitButton },
593
+ title: { ...base.title, ...buttonsStylesOverride.title }
594
+ };
595
+ }, [buttonsTheme, buttonsStylesOverride]);
532
596
  const isSubmitting = externalProcessing ?? processing;
533
597
  const isSelfContained = !onTokenizedBody;
534
598
  const baseUrl = resolvedBillingApiUrl.replace(/\/+$/, "");
@@ -585,7 +649,8 @@ function SplitCardFormInner({
585
649
  userId: userId ?? "",
586
650
  email: email ?? "",
587
651
  firstName: firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
588
- lastName: lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? ""
652
+ lastName: lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
653
+ ...enableAVS ? { zip: zipCodeRef.current, country: selectedCountryRef.current } : {}
589
654
  },
590
655
  chv
591
656
  })
@@ -740,18 +805,30 @@ function SplitCardFormInner({
740
805
  async (e) => {
741
806
  e.preventDefault();
742
807
  if (!flopay || !elements || isSubmitting || processingRef.current) return;
808
+ onButtonClick?.("card");
743
809
  setProcessing(true);
744
810
  setOverlayStatus("processing");
745
811
  updateError(null);
746
812
  let handedOff = false;
747
813
  try {
814
+ if (enableAVS && !zipCodeRef.current.trim()) {
815
+ updateError(getPostalCodeLabel(selectedCountryRef.current) + " is required");
816
+ return;
817
+ }
748
818
  const submitResult = await flopay.submitElements();
749
819
  if (submitResult.error) {
750
820
  updateError(submitResult.error.message);
751
821
  onError?.(submitResult.error);
752
822
  return;
753
823
  }
754
- const pmResult = await flopay.createPaymentMethod();
824
+ const billingDetails = enableAVS ? {
825
+ name: fullName,
826
+ address: {
827
+ country: selectedCountryRef.current,
828
+ postal_code: zipCodeRef.current
829
+ }
830
+ } : void 0;
831
+ const pmResult = await flopay.createPaymentMethod(billingDetails);
755
832
  if (pmResult.error || !pmResult.paymentMethodId) {
756
833
  updateError(pmResult.error?.message ?? "Failed to create payment method.");
757
834
  return;
@@ -807,15 +884,25 @@ function SplitCardFormInner({
807
884
  return /* @__PURE__ */ jsx3("div", { "data-testid": "flopay-loading", "aria-busy": "true", children: "Loading payment form..." });
808
885
  }
809
886
  const isButtons = layout === "buttons";
810
- const cardBorderColor = isButtons ? "#e5e7eb" : "#A4A4FF";
811
- const cardBg = isButtons ? "white" : "#EDEDFF";
887
+ const resolvedBorder = isButtons ? bStyles.cardInputBorder ?? "#e5e7eb" : "#A4A4FF";
888
+ const cardBg = isButtons ? bStyles.cardFormContainer?.backgroundColor ?? "white" : "#EDEDFF";
889
+ const cardInputBg = isButtons ? bStyles.cardInputBackground ?? "white" : "white";
890
+ const stripeElementStyle = isButtons && (bStyles.cardInputColor || bStyles.cardInputPlaceholderColor || bStyles.cardInputFontSize) ? {
891
+ style: {
892
+ base: {
893
+ ...bStyles.cardInputColor ? { color: bStyles.cardInputColor } : {},
894
+ ...bStyles.cardInputFontSize ? { fontSize: bStyles.cardInputFontSize } : {},
895
+ ...bStyles.cardInputPlaceholderColor ? { "::placeholder": { color: bStyles.cardInputPlaceholderColor } } : {}
896
+ },
897
+ invalid: { color: "#ef4444" }
898
+ }
899
+ } : {};
812
900
  const cardFormBlock = /* @__PURE__ */ jsxs("div", { style: {
813
901
  backgroundColor: cardBg,
814
902
  borderRadius: "8px",
815
903
  padding: isButtons ? "0" : "1rem",
816
- ...isButtons && showCardForm ? {
817
- animation: "flopay-expand 0.4s cubic-bezier(0.16,1,0.3,1) both"
818
- } : {}
904
+ ...isButtons ? bStyles.cardFormContainer : {},
905
+ ...isButtons ? { padding: bStyles.cardFormContainer?.padding ?? "0" } : {}
819
906
  }, children: [
820
907
  isButtons && showCardForm && /* @__PURE__ */ jsxs("div", { style: {
821
908
  display: "flex",
@@ -826,7 +913,7 @@ function SplitCardFormInner({
826
913
  "button",
827
914
  {
828
915
  type: "button",
829
- onClick: () => setShowCardForm(false),
916
+ onClick: collapseToButtons,
830
917
  style: {
831
918
  display: "inline-flex",
832
919
  alignItems: "center",
@@ -835,17 +922,12 @@ function SplitCardFormInner({
835
922
  border: "none",
836
923
  cursor: "pointer",
837
924
  color: "#4b5563",
838
- fontSize: "0.85rem",
925
+ fontSize: bStyles.backButtonFontSize ?? "0.85rem",
839
926
  fontWeight: 500,
840
927
  padding: 0,
841
928
  transition: "color 0.15s",
842
- flexShrink: 0
843
- },
844
- onMouseOver: (e) => {
845
- e.currentTarget.style.color = "#1f2937";
846
- },
847
- onMouseOut: (e) => {
848
- e.currentTarget.style.color = "#4b5563";
929
+ flexShrink: 0,
930
+ ...bStyles.backButton
849
931
  },
850
932
  "aria-label": "Back to payment methods",
851
933
  children: [
@@ -857,44 +939,53 @@ function SplitCardFormInner({
857
939
  height: 28,
858
940
  borderRadius: "50%",
859
941
  backgroundColor: "#f3f4f6",
860
- transition: "background-color 0.15s"
942
+ transition: "background-color 0.15s",
943
+ ...bStyles.backButtonIcon
861
944
  }, children: /* @__PURE__ */ jsx3("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx3("path", { d: "M15 18l-6-6 6-6" }) }) }),
862
945
  "Go back"
863
946
  ]
864
947
  }
865
948
  ),
866
- /* @__PURE__ */ jsx3("div", { style: { flex: 1, textAlign: "center", fontWeight: 600, fontSize: "1.05rem", color: "#262833", paddingRight: 80 }, children: "Secure card checkout" })
949
+ /* @__PURE__ */ jsx3("div", { style: {
950
+ flex: 1,
951
+ textAlign: "center",
952
+ fontWeight: 600,
953
+ fontSize: bStyles.titleFontSize ?? "1.05rem",
954
+ color: "#262833",
955
+ paddingRight: 80,
956
+ ...bStyles.title
957
+ }, children: "Secure card checkout" })
867
958
  ] }),
868
959
  !isButtons && /* @__PURE__ */ jsx3("div", { style: { textAlign: "center", fontWeight: 600, fontSize: "1.1rem", padding: "0.5rem 0", color: "#262833" }, children: "Secure card checkout" }),
869
960
  /* @__PURE__ */ jsx3("div", { style: {
870
- backgroundColor: "white",
871
- border: `1px solid ${cardBorderColor}`,
961
+ backgroundColor: cardInputBg,
962
+ border: `1px solid ${resolvedBorder}`,
872
963
  borderTopLeftRadius: "8px",
873
964
  borderTopRightRadius: "8px",
874
965
  padding: "10px"
875
- }, children: /* @__PURE__ */ jsx3(CardNumberElement, { onReady: () => setFormReady(true) }) }),
966
+ }, children: /* @__PURE__ */ jsx3(CardNumberElement, { onReady: () => setFormReady(true), options: stripeElementStyle }) }),
876
967
  /* @__PURE__ */ jsxs("div", { style: { display: "flex" }, children: [
877
968
  /* @__PURE__ */ jsx3("div", { style: {
878
969
  flex: 1,
879
- backgroundColor: "white",
880
- border: `1px solid ${cardBorderColor}`,
970
+ backgroundColor: cardInputBg,
971
+ border: `1px solid ${resolvedBorder}`,
881
972
  borderTop: "none",
882
973
  borderRight: "none",
883
974
  borderBottomLeftRadius: "8px",
884
975
  padding: "10px"
885
- }, children: /* @__PURE__ */ jsx3(CardExpiryElement, {}) }),
976
+ }, children: /* @__PURE__ */ jsx3(CardExpiryElement, { options: stripeElementStyle }) }),
886
977
  /* @__PURE__ */ jsx3("div", { style: {
887
978
  flex: 1,
888
- backgroundColor: "white",
889
- border: `1px solid ${cardBorderColor}`,
979
+ backgroundColor: cardInputBg,
980
+ border: `1px solid ${resolvedBorder}`,
890
981
  borderTop: "none",
891
982
  borderBottomRightRadius: "8px",
892
983
  padding: "10px"
893
- }, children: /* @__PURE__ */ jsx3(CardCvcElement, {}) })
984
+ }, children: /* @__PURE__ */ jsx3(CardCvcElement, { options: stripeElementStyle }) })
894
985
  ] }),
895
986
  /* @__PURE__ */ jsx3("div", { style: {
896
- backgroundColor: "white",
897
- border: `1px solid ${cardBorderColor}`,
987
+ backgroundColor: cardInputBg,
988
+ border: `1px solid ${resolvedBorder}`,
898
989
  borderRadius: "8px",
899
990
  marginTop: "0.5rem",
900
991
  padding: "10px"
@@ -911,12 +1002,91 @@ function SplitCardFormInner({
911
1002
  width: "100%",
912
1003
  border: "none",
913
1004
  outline: "none",
914
- fontSize: "16px",
1005
+ background: "transparent",
1006
+ fontSize: bStyles.cardInputFontSize ?? "16px",
915
1007
  fontFamily: "Poppins, sans-serif",
916
- color: "#262833"
1008
+ color: bStyles.cardInputColor ?? "#262833",
1009
+ ...isButtons && bStyles.nameInput ? bStyles.nameInput : {}
917
1010
  }
918
1011
  }
919
1012
  ) }),
1013
+ enableAVS && /* @__PURE__ */ jsxs("div", { style: {
1014
+ display: "flex",
1015
+ flexDirection: avsLayoutProp === "column" ? "column" : "row",
1016
+ gap: avsLayoutProp === "column" ? "0.5rem" : "0",
1017
+ marginTop: "0.5rem"
1018
+ }, children: [
1019
+ /* @__PURE__ */ jsx3("div", { style: {
1020
+ flex: avsLayoutProp === "row" ? 1 : void 0,
1021
+ backgroundColor: cardInputBg,
1022
+ border: `1px solid ${resolvedBorder}`,
1023
+ padding: "10px",
1024
+ ...avsLayoutProp === "row" ? { borderRadius: "0", borderTopLeftRadius: "8px", borderBottomLeftRadius: "8px", borderRight: "none" } : { borderRadius: "8px" },
1025
+ ...isButtons && bStyles.countrySelect ? bStyles.countrySelect : {}
1026
+ }, children: /* @__PURE__ */ jsx3(
1027
+ "select",
1028
+ {
1029
+ value: selectedCountry,
1030
+ onChange: (e) => {
1031
+ selectedCountryRef.current = e.target.value;
1032
+ setSelectedCountry(e.target.value);
1033
+ onCountryChange?.(e.target.value);
1034
+ },
1035
+ disabled: isSubmitting,
1036
+ autoComplete: "country",
1037
+ "data-testid": "flopay-country",
1038
+ style: {
1039
+ width: "100%",
1040
+ border: "none",
1041
+ outline: "none",
1042
+ background: "transparent",
1043
+ fontSize: bStyles.cardInputFontSize ?? "16px",
1044
+ fontFamily: "Poppins, sans-serif",
1045
+ color: bStyles.cardInputColor ?? "#262833",
1046
+ cursor: "pointer",
1047
+ ...isButtons && bStyles.nameInput ? bStyles.nameInput : {}
1048
+ },
1049
+ children: COUNTRY_OPTIONS.map((c) => /* @__PURE__ */ jsxs("option", { value: c.code, children: [
1050
+ c.flag,
1051
+ " ",
1052
+ c.name
1053
+ ] }, c.code))
1054
+ }
1055
+ ) }),
1056
+ /* @__PURE__ */ jsx3("div", { style: {
1057
+ flex: avsLayoutProp === "row" ? 1 : void 0,
1058
+ backgroundColor: cardInputBg,
1059
+ border: `1px solid ${resolvedBorder}`,
1060
+ padding: "10px",
1061
+ ...avsLayoutProp === "row" ? { borderRadius: "0", borderTopRightRadius: "8px", borderBottomRightRadius: "8px" } : { borderRadius: "8px" },
1062
+ ...isButtons && bStyles.zipInput ? bStyles.zipInput : {}
1063
+ }, children: /* @__PURE__ */ jsx3(
1064
+ "input",
1065
+ {
1066
+ placeholder: getPostalCodeLabel(selectedCountry),
1067
+ autoComplete: "postal-code",
1068
+ value: zipCode,
1069
+ onChange: (e) => {
1070
+ zipCodeRef.current = e.target.value;
1071
+ setZipCode(e.target.value);
1072
+ onZipChange?.(e.target.value);
1073
+ },
1074
+ disabled: isSubmitting,
1075
+ required: true,
1076
+ "data-testid": "flopay-zip",
1077
+ style: {
1078
+ width: "100%",
1079
+ border: "none",
1080
+ outline: "none",
1081
+ background: "transparent",
1082
+ fontSize: bStyles.cardInputFontSize ?? "16px",
1083
+ fontFamily: "Poppins, sans-serif",
1084
+ color: bStyles.cardInputColor ?? "#262833",
1085
+ ...isButtons && bStyles.nameInput ? bStyles.nameInput : {}
1086
+ }
1087
+ }
1088
+ ) })
1089
+ ] }),
920
1090
  displayError && /* @__PURE__ */ jsxs("div", { role: "alert", "data-testid": "flopay-error", style: {
921
1091
  margin: "0.75rem 0",
922
1092
  padding: "0.625rem 0.875rem",
@@ -928,7 +1098,8 @@ function SplitCardFormInner({
928
1098
  fontWeight: 600,
929
1099
  display: "flex",
930
1100
  alignItems: "center",
931
- gap: "0.5rem"
1101
+ gap: "0.5rem",
1102
+ ...isButtons && bStyles.errorBanner ? bStyles.errorBanner : {}
932
1103
  }, children: [
933
1104
  /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx3("path", { d: "M12 9v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", stroke: "#DC2626", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }),
934
1105
  displayError
@@ -947,10 +1118,11 @@ function SplitCardFormInner({
947
1118
  color: "white",
948
1119
  border: "none",
949
1120
  borderRadius: "8px",
950
- fontSize: "1rem",
1121
+ fontSize: isButtons && bStyles.submitButtonFontSize ? bStyles.submitButtonFontSize : "1rem",
951
1122
  fontWeight: 600,
952
1123
  cursor: !formReady || isSubmitting ? "not-allowed" : "pointer",
953
- opacity: !formReady || isSubmitting ? 0.5 : 1
1124
+ opacity: !formReady || isSubmitting ? 0.5 : 1,
1125
+ ...isButtons ? bStyles.submitButton : {}
954
1126
  },
955
1127
  children: isSubmitting ? "PROCESSING..." : submitLabel
956
1128
  }
@@ -967,103 +1139,119 @@ function SplitCardFormInner({
967
1139
  }, children: "Secure Card Checkout" })
968
1140
  ] });
969
1141
  if (layout === "buttons") {
1142
+ const isButtonsView = viewState === "buttons" || viewState === "expanding";
1143
+ const isCardView = viewState === "expanding" || viewState === "card" || viewState === "collapsing";
1144
+ 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;
1145
+ 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;
970
1146
  return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
1147
+ /* @__PURE__ */ jsx3(FloPayKeyframes, {}),
971
1148
  overlayStatus && /* @__PURE__ */ jsx3(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
972
- /* @__PURE__ */ jsxs("div", { style: {
973
- display: showCardForm ? "none" : "flex",
974
- flexDirection: "column",
975
- gap: "0.5rem"
976
- }, children: [
977
- showPayPal && stripeInstance && /* @__PURE__ */ jsx3(StripeElements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ jsx3(
978
- PayPalButtonInner,
979
- {
980
- sessionId,
981
- email,
982
- billingApiUrl: resolvedBillingApiUrl,
983
- onTokenizedBody: dispatchTokenizedBody,
984
- onErrorChange: updateError,
985
- isProcessing: isSubmitting
986
- }
987
- ) }),
988
- showWallets && stripeInstance && /* @__PURE__ */ jsx3(StripeElements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ jsx3(
989
- WalletButtonInner,
990
- {
991
- sessionId,
992
- email,
993
- billingApiUrl: resolvedBillingApiUrl,
994
- showApplePay,
995
- showGooglePay,
996
- onTokenizedBody: dispatchTokenizedBody,
997
- onErrorChange: updateError
998
- }
999
- ) }),
1000
- /* @__PURE__ */ jsxs(
1001
- "button",
1002
- {
1003
- type: "button",
1004
- onClick: () => setShowCardForm(true),
1005
- style: {
1006
- width: "100%",
1007
- padding: "0.9rem 1rem",
1008
- backgroundColor: "white",
1009
- color: "#262833",
1010
- border: "1px solid #d1d5db",
1011
- borderRadius: "8px",
1012
- fontSize: "0.95rem",
1013
- fontWeight: 600,
1014
- cursor: "pointer",
1015
- display: "flex",
1016
- alignItems: "center",
1017
- justifyContent: "center",
1018
- gap: "0.625rem",
1019
- transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
1020
- boxShadow: "0 1px 2px rgba(0,0,0,0.04)"
1021
- },
1022
- onMouseOver: (e) => {
1023
- e.currentTarget.style.borderColor = "#A4A4FF";
1024
- e.currentTarget.style.boxShadow = "0 0 0 1px #A4A4FF, 0 2px 8px rgba(74,73,255,0.08)";
1025
- },
1026
- onMouseOut: (e) => {
1027
- e.currentTarget.style.borderColor = "#d1d5db";
1028
- e.currentTarget.style.boxShadow = "0 1px 2px rgba(0,0,0,0.04)";
1029
- },
1030
- onMouseDown: (e) => {
1031
- e.currentTarget.style.transform = "scale(0.985)";
1032
- },
1033
- onMouseUp: (e) => {
1034
- e.currentTarget.style.transform = "scale(1)";
1035
- },
1036
- children: [
1037
- /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1038
- /* @__PURE__ */ jsx3("rect", { width: "20", height: "14", x: "2", y: "5", rx: "2" }),
1039
- /* @__PURE__ */ jsx3("line", { x1: "2", x2: "22", y1: "10", y2: "10" })
1040
- ] }),
1041
- "Credit / Debit Card",
1042
- /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#9ca3af", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { marginLeft: "auto" }, children: /* @__PURE__ */ jsx3("path", { d: "M9 18l6-6-6-6" }) })
1043
- ]
1044
- }
1045
- ),
1046
- displayError && !showCardForm && /* @__PURE__ */ jsxs("div", { role: "alert", "data-testid": "flopay-error", style: {
1047
- margin: "0.25rem 0",
1048
- padding: "0.625rem 0.875rem",
1049
- background: "#FEF2F2",
1050
- border: "1px solid #FECACA",
1051
- borderRadius: "8px",
1052
- color: "#991B1B",
1053
- fontSize: "0.85rem",
1054
- fontWeight: 600,
1149
+ /* @__PURE__ */ jsxs("div", { style: { display: "grid" }, children: [
1150
+ /* @__PURE__ */ jsxs("div", { style: {
1151
+ gridArea: "1 / 1",
1055
1152
  display: "flex",
1056
- alignItems: "center",
1057
- gap: "0.5rem"
1153
+ flexDirection: "column",
1154
+ gap: "0.5rem",
1155
+ // When card form is showing (and not transitioning), hide but keep mounted
1156
+ ...!isButtonsView && !buttonsAnim ? { visibility: "hidden", position: "absolute", pointerEvents: "none", width: "100%" } : {},
1157
+ ...buttonsAnim ? { animation: buttonsAnim, pointerEvents: "none" } : {}
1058
1158
  }, children: [
1059
- /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx3("path", { d: "M12 9v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", stroke: "#DC2626", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }),
1060
- displayError
1061
- ] })
1062
- ] }),
1063
- showCardForm && cardFormBlock
1159
+ showPayPal && stripeInstance ? /* @__PURE__ */ jsx3(StripeElements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ jsx3(
1160
+ PayPalButtonInner,
1161
+ {
1162
+ sessionId,
1163
+ email,
1164
+ billingApiUrl: resolvedBillingApiUrl,
1165
+ onTokenizedBody: dispatchTokenizedBody,
1166
+ onErrorChange: updateError,
1167
+ isProcessing: isSubmitting,
1168
+ onButtonClick
1169
+ }
1170
+ ) }) : showPayPal ? /* @__PURE__ */ jsx3("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1171
+ showWallets && stripeInstance ? /* @__PURE__ */ jsx3(StripeElements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ jsx3(
1172
+ WalletButtonInner,
1173
+ {
1174
+ sessionId,
1175
+ email,
1176
+ billingApiUrl: resolvedBillingApiUrl,
1177
+ showApplePay,
1178
+ showGooglePay,
1179
+ onTokenizedBody: dispatchTokenizedBody,
1180
+ onErrorChange: updateError,
1181
+ onButtonClick
1182
+ }
1183
+ ) }) : showWallets ? /* @__PURE__ */ jsx3("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1184
+ /* @__PURE__ */ jsxs(
1185
+ "button",
1186
+ {
1187
+ type: "button",
1188
+ onClick: () => {
1189
+ onButtonClick?.("card");
1190
+ expandToCard();
1191
+ },
1192
+ style: {
1193
+ width: "100%",
1194
+ padding: "0.9rem 1rem",
1195
+ backgroundColor: "white",
1196
+ color: "#262833",
1197
+ border: "1px solid #d1d5db",
1198
+ borderRadius: "8px",
1199
+ fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
1200
+ fontWeight: 600,
1201
+ cursor: "pointer",
1202
+ display: "flex",
1203
+ alignItems: "center",
1204
+ justifyContent: "center",
1205
+ gap: "0.625rem",
1206
+ transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
1207
+ boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
1208
+ position: "relative",
1209
+ ...bStyles.cardButton
1210
+ },
1211
+ onMouseDown: (e) => {
1212
+ e.currentTarget.style.transform = "scale(0.985)";
1213
+ },
1214
+ onMouseUp: (e) => {
1215
+ e.currentTarget.style.transform = "scale(1)";
1216
+ },
1217
+ children: [
1218
+ /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1219
+ /* @__PURE__ */ jsx3("rect", { width: "20", height: "14", x: "2", y: "5", rx: "2" }),
1220
+ /* @__PURE__ */ jsx3("line", { x1: "2", x2: "22", y1: "10", y2: "10" })
1221
+ ] }),
1222
+ "Credit / Debit Card",
1223
+ /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#9ca3af", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { position: "absolute", right: "1rem" }, children: /* @__PURE__ */ jsx3("path", { d: "M9 18l6-6-6-6" }) })
1224
+ ]
1225
+ }
1226
+ ),
1227
+ displayError && viewState === "buttons" && /* @__PURE__ */ jsxs("div", { role: "alert", "data-testid": "flopay-error", style: {
1228
+ margin: "0.25rem 0",
1229
+ padding: "0.625rem 0.875rem",
1230
+ background: "#FEF2F2",
1231
+ border: "1px solid #FECACA",
1232
+ borderRadius: "8px",
1233
+ color: "#991B1B",
1234
+ fontSize: "0.85rem",
1235
+ fontWeight: 600,
1236
+ display: "flex",
1237
+ alignItems: "center",
1238
+ gap: "0.5rem",
1239
+ ...bStyles.errorBanner ? bStyles.errorBanner : {}
1240
+ }, children: [
1241
+ /* @__PURE__ */ jsx3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx3("path", { d: "M12 9v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", stroke: "#DC2626", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }),
1242
+ displayError
1243
+ ] })
1244
+ ] }),
1245
+ isCardView && /* @__PURE__ */ jsx3("div", { style: {
1246
+ gridArea: "1 / 1",
1247
+ ...cardAnim ? { animation: cardAnim } : {},
1248
+ ...viewState === "collapsing" ? { pointerEvents: "none" } : {}
1249
+ }, children: cardFormBlock })
1250
+ ] })
1064
1251
  ] });
1065
1252
  }
1066
1253
  return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
1254
+ /* @__PURE__ */ jsx3(FloPayKeyframes, {}),
1067
1255
  overlayStatus && /* @__PURE__ */ jsx3(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
1068
1256
  showWallets && stripeInstance && /* @__PURE__ */ jsx3(StripeElements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ jsx3(
1069
1257
  WalletButtonInner,
@@ -1107,7 +1295,8 @@ function SplitCardFormInner({
1107
1295
  // src/flopay-checkout.tsx
1108
1296
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1109
1297
  function FloPayCheckout({
1110
- sessionId,
1298
+ sessionId: sessionIdProp,
1299
+ createSession: createSessionParams,
1111
1300
  billingApiUrl,
1112
1301
  appearance,
1113
1302
  locale,
@@ -1120,6 +1309,11 @@ function FloPayCheckout({
1120
1309
  showApplePay = true,
1121
1310
  showGooglePay = true,
1122
1311
  layout,
1312
+ buttonsTheme,
1313
+ buttonsStyles,
1314
+ onButtonClick,
1315
+ enableAVS,
1316
+ avsLayout,
1123
1317
  submitLabel,
1124
1318
  className,
1125
1319
  children,
@@ -1133,6 +1327,7 @@ function FloPayCheckout({
1133
1327
  const [flopay, setFloPay] = useState3(null);
1134
1328
  const flopayRef = useRef3(null);
1135
1329
  const [session, setSession] = useState3(null);
1330
+ const [resolvedSessionId, setResolvedSessionId] = useState3(sessionIdProp ?? "");
1136
1331
  const [isLoading, setIsLoading] = useState3(true);
1137
1332
  const [loadError, setLoadError] = useState3(null);
1138
1333
  const [currentMode, setCurrentMode] = useState3("full");
@@ -1155,7 +1350,7 @@ function FloPayCheckout({
1155
1350
  "x-user-id": sess.customer?.id ?? ""
1156
1351
  },
1157
1352
  body: JSON.stringify({
1158
- sessionId,
1353
+ sessionId: resolvedSessionId,
1159
1354
  tokenizedData: { id: void 0 },
1160
1355
  accountData: {
1161
1356
  userId: sess.customer?.id ?? "",
@@ -1189,7 +1384,7 @@ function FloPayCheckout({
1189
1384
  "api_error"
1190
1385
  );
1191
1386
  },
1192
- [resolvedBillingUrl, sessionId]
1387
+ [resolvedBillingUrl, resolvedSessionId]
1193
1388
  );
1194
1389
  const handleRedirectResult = useCallback2(
1195
1390
  async (redirectResult, sess, options) => {
@@ -1234,16 +1429,169 @@ function FloPayCheckout({
1234
1429
  }
1235
1430
  return false;
1236
1431
  },
1237
- [resolvedBillingUrl, sessionId]
1432
+ [resolvedBillingUrl, resolvedSessionId]
1433
+ );
1434
+ const inflightRef = useRef3(/* @__PURE__ */ new Map());
1435
+ const initializedHashRef = useRef3(null);
1436
+ function hashCreateParams(params) {
1437
+ const key = JSON.stringify({
1438
+ c: params?.clientId,
1439
+ i: params?.items?.map((x) => `${x.providerItemId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
1440
+ s: params?.subscriptions?.map((x) => `${x.providerPlanId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
1441
+ e: params?.account.email,
1442
+ m: params?.checkoutMode ?? "full"
1443
+ });
1444
+ let h = 0;
1445
+ for (let i = 0; i < key.length; i++) {
1446
+ h = (h << 5) - h + key.charCodeAt(i) | 0;
1447
+ }
1448
+ return `flopay_session_${Math.abs(h).toString(36)}`;
1449
+ }
1450
+ const createSessionHash = useMemo3(
1451
+ () => createSessionParams ? hashCreateParams(createSessionParams) : "",
1452
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1453
+ [
1454
+ createSessionParams?.clientId,
1455
+ createSessionParams?.account?.email,
1456
+ createSessionParams?.checkoutMode,
1457
+ JSON.stringify(createSessionParams?.items),
1458
+ JSON.stringify(createSessionParams?.subscriptions)
1459
+ ]
1238
1460
  );
1461
+ const createSessionParamsRef = useRef3(createSessionParams);
1462
+ createSessionParamsRef.current = createSessionParams;
1239
1463
  useEffect4(() => {
1240
1464
  let cancelled = false;
1241
- setIsLoading(true);
1242
1465
  setLoadError(null);
1466
+ if (createSessionHash) {
1467
+ if (initializedHashRef.current === createSessionHash) return;
1468
+ const params = createSessionParamsRef.current;
1469
+ const totalAmount = [
1470
+ ...(params.items ?? []).map((i) => i.overrideAmount ?? i.totalAmount ?? 0),
1471
+ ...(params.subscriptions ?? []).map((s) => s.overrideAmount ?? s.totalAmount ?? 0)
1472
+ ].reduce((sum, v) => sum + v, 0);
1473
+ const currency = params.items?.[0]?.currency ?? params.subscriptions?.[0]?.currency ?? "USD";
1474
+ const syntheticSession = {
1475
+ id: "",
1476
+ clientSecret: "",
1477
+ mode: "payment",
1478
+ amount: Math.round(totalAmount * 100),
1479
+ currency,
1480
+ status: "open",
1481
+ customer: {
1482
+ id: params.account.userId,
1483
+ email: params.account.email,
1484
+ firstName: params.account.firstName,
1485
+ lastName: params.account.lastName
1486
+ },
1487
+ successUrl: params.successUrl,
1488
+ cancelUrl: params.cancelUrl,
1489
+ checkoutMode: params.checkoutMode ?? "full",
1490
+ items: (params.items ?? []).map((i, idx) => ({
1491
+ uuid: `synthetic-item-${idx}`,
1492
+ checkoutSessionId: "",
1493
+ providerItemId: i.providerItemId,
1494
+ providerItemName: i.providerItemName ?? i.providerItemId,
1495
+ quantity: i.quantity ?? 1,
1496
+ totalAmount: i.totalAmount,
1497
+ overrideAmount: i.overrideAmount ?? null,
1498
+ currency: i.currency ?? currency
1499
+ })),
1500
+ subscriptions: (params.subscriptions ?? []).map((s, idx) => ({
1501
+ uuid: `synthetic-sub-${idx}`,
1502
+ checkoutSessionId: "",
1503
+ providerPlanId: s.providerPlanId,
1504
+ providerPlanName: s.providerPlanName ?? s.providerPlanId,
1505
+ quantity: s.quantity ?? 1,
1506
+ totalAmount: s.totalAmount,
1507
+ overrideAmount: s.overrideAmount ?? null,
1508
+ currency: s.currency ?? currency
1509
+ }))
1510
+ };
1511
+ setSession(syntheticSession);
1512
+ setCurrentMode(params.checkoutMode ?? checkoutModeProp ?? "full");
1513
+ setIsLoading(false);
1514
+ const cacheKey = createSessionHash;
1515
+ async function resolveSession() {
1516
+ const api = new PaymentAPI(resolvedBillingUrl);
1517
+ let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
1518
+ let realResult = null;
1519
+ if (sid) {
1520
+ try {
1521
+ realResult = await api.getUnifiedCheckoutSession(sid);
1522
+ if (realResult.data.session?.status === "complete") {
1523
+ if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
1524
+ sid = null;
1525
+ realResult = null;
1526
+ }
1527
+ } catch {
1528
+ if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
1529
+ sid = null;
1530
+ }
1531
+ }
1532
+ if (!sid) {
1533
+ realResult = await api.createAndFetchSession(createSessionParamsRef.current);
1534
+ sid = realResult.data.session?.id ?? "";
1535
+ if (sid && typeof window !== "undefined") {
1536
+ window.sessionStorage.setItem(cacheKey, sid);
1537
+ }
1538
+ }
1539
+ return { sid: sid ?? "", result: realResult };
1540
+ }
1541
+ (async () => {
1542
+ try {
1543
+ let promise = inflightRef.current.get(cacheKey);
1544
+ if (!promise) {
1545
+ promise = resolveSession();
1546
+ inflightRef.current.set(cacheKey, promise);
1547
+ }
1548
+ let resolved;
1549
+ try {
1550
+ resolved = await promise;
1551
+ } finally {
1552
+ inflightRef.current.delete(cacheKey);
1553
+ }
1554
+ if (cancelled) return;
1555
+ const { sid, result: realResult } = resolved;
1556
+ if (realResult) {
1557
+ setUnified(realResult);
1558
+ if (realResult.data.session) setSession(realResult.data.session);
1559
+ }
1560
+ if (sid) {
1561
+ setResolvedSessionId(sid);
1562
+ }
1563
+ let pk;
1564
+ if (realResult?.provider === "stripe") {
1565
+ pk = realResult.data.stripe?.publishableKey;
1566
+ }
1567
+ if (!pk) pk = fallbackPublishableKey;
1568
+ if (!pk) {
1569
+ throw new FloPayError2(
1570
+ "No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
1571
+ "validation_error"
1572
+ );
1573
+ }
1574
+ const instance = await loadFloPay(pk, { billingApiUrl: resolvedBillingUrl, locale });
1575
+ if (!cancelled) {
1576
+ flopayRef.current = instance;
1577
+ setFloPay(instance);
1578
+ initializedHashRef.current = cacheKey;
1579
+ }
1580
+ } catch (err) {
1581
+ if (cancelled) return;
1582
+ const floPayErr = err instanceof FloPayError2 ? err : new FloPayError2(err instanceof Error ? err.message : "Failed to create session", "api_error");
1583
+ setLoadError(floPayErr);
1584
+ }
1585
+ })();
1586
+ return () => {
1587
+ cancelled = true;
1588
+ };
1589
+ }
1590
+ setIsLoading(true);
1243
1591
  async function init() {
1244
1592
  try {
1245
1593
  const api = new PaymentAPI(resolvedBillingUrl);
1246
- const result = await api.getUnifiedCheckoutSession(sessionId);
1594
+ const result = await api.getUnifiedCheckoutSession(resolvedSessionId);
1247
1595
  if (cancelled) return;
1248
1596
  setUnified(result);
1249
1597
  const sess = result.data.session ?? null;
@@ -1261,7 +1609,7 @@ function FloPayCheckout({
1261
1609
  const hasPayPalRedirectParams = typeof window !== "undefined" && new URLSearchParams(window.location.search).has("payment_intent");
1262
1610
  if (effectiveMode === "auto" && !autoCheckoutAttempted.current && !hasPayPalRedirectParams) {
1263
1611
  autoCheckoutAttempted.current = true;
1264
- const stripeInitPromise = initStripe(result, sess);
1612
+ const stripeInitPromise = initStripe(result);
1265
1613
  try {
1266
1614
  const redirectResult = await processPaymentForMode(sess);
1267
1615
  if (!redirectResult) {
@@ -1272,9 +1620,7 @@ function FloPayCheckout({
1272
1620
  await stripeInitPromise;
1273
1621
  const handled = await handleRedirectResult(redirectResult, sess, { attempt3DS: true });
1274
1622
  if (!cancelled) {
1275
- if (!handled) {
1276
- setCurrentMode("full");
1277
- }
1623
+ if (!handled) setCurrentMode("full");
1278
1624
  setIsLoading(false);
1279
1625
  }
1280
1626
  return;
@@ -1286,19 +1632,16 @@ function FloPayCheckout({
1286
1632
  return;
1287
1633
  }
1288
1634
  }
1289
- await initStripe(result, sess);
1635
+ await initStripe(result);
1290
1636
  if (!cancelled) setIsLoading(false);
1291
1637
  } catch (err) {
1292
1638
  if (cancelled) return;
1293
- const floPayErr = err instanceof FloPayError2 ? err : new FloPayError2(
1294
- err instanceof Error ? err.message : "Failed to initialize checkout",
1295
- "api_error"
1296
- );
1639
+ const floPayErr = err instanceof FloPayError2 ? err : new FloPayError2(err instanceof Error ? err.message : "Failed to initialize checkout", "api_error");
1297
1640
  setLoadError(floPayErr);
1298
1641
  setIsLoading(false);
1299
1642
  }
1300
1643
  }
1301
- async function initStripe(result, _sess) {
1644
+ async function initStripe(result) {
1302
1645
  let publishableKey;
1303
1646
  if (result.provider === "stripe") {
1304
1647
  publishableKey = result.data.stripe?.publishableKey;
@@ -1321,7 +1664,7 @@ function FloPayCheckout({
1321
1664
  return () => {
1322
1665
  cancelled = true;
1323
1666
  };
1324
- }, [sessionId, resolvedBillingUrl, fallbackPublishableKey, locale, checkoutModeProp]);
1667
+ }, [resolvedSessionId, createSessionHash, resolvedBillingUrl, fallbackPublishableKey, locale, checkoutModeProp]);
1325
1668
  const handleConfirmCheckout = useCallback2(async () => {
1326
1669
  if (confirmProcessing || !session) return;
1327
1670
  setConfirmProcessing(true);
@@ -1373,32 +1716,32 @@ function FloPayCheckout({
1373
1716
  [session, isLoading, loadError, currentMode]
1374
1717
  );
1375
1718
  if (isLoading) {
1376
- return /* @__PURE__ */ jsx4(Fragment2, { children: loadingNode ?? /* @__PURE__ */ jsxs2(
1377
- "div",
1378
- {
1379
- style: {
1380
- display: "flex",
1381
- justifyContent: "center",
1382
- padding: 32
1383
- },
1384
- children: [
1385
- /* @__PURE__ */ jsx4(
1386
- "div",
1387
- {
1388
- style: {
1389
- width: 24,
1390
- height: 24,
1391
- border: "2px solid #e5e7eb",
1392
- borderTopColor: "#6b7280",
1393
- borderRadius: "50%",
1394
- animation: "spin 0.6s linear infinite"
1395
- }
1396
- }
1397
- ),
1398
- /* @__PURE__ */ jsx4("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
1399
- ]
1400
- }
1401
- ) });
1719
+ if (loadingNode) return /* @__PURE__ */ jsx4(Fragment2, { children: loadingNode });
1720
+ if (layout === "buttons") {
1721
+ const skeletonBar = (h) => /* @__PURE__ */ jsx4("div", { style: {
1722
+ height: h,
1723
+ borderRadius: 8,
1724
+ background: "#e5e7eb",
1725
+ animation: "flopay-loading-pulse 1.5s ease-in-out infinite"
1726
+ } });
1727
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
1728
+ showPayPal && skeletonBar(44),
1729
+ (showApplePay || showGooglePay) && skeletonBar(44),
1730
+ skeletonBar(48),
1731
+ /* @__PURE__ */ jsx4("style", { children: `@keyframes flopay-loading-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
1732
+ ] });
1733
+ }
1734
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "center", padding: 32 }, children: [
1735
+ /* @__PURE__ */ jsx4("div", { style: {
1736
+ width: 24,
1737
+ height: 24,
1738
+ border: "2px solid #e5e7eb",
1739
+ borderTopColor: "#6b7280",
1740
+ borderRadius: "50%",
1741
+ animation: "spin 0.6s linear infinite"
1742
+ } }),
1743
+ /* @__PURE__ */ jsx4("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
1744
+ ] });
1402
1745
  }
1403
1746
  if (loadError) {
1404
1747
  if (errorNode) return /* @__PURE__ */ jsx4(Fragment2, { children: errorNode(loadError) });
@@ -1415,6 +1758,19 @@ function FloPayCheckout({
1415
1758
  }
1416
1759
  );
1417
1760
  }
1761
+ if ((!flopay || !providerOptions) && createSessionParams && layout === "buttons") {
1762
+ return /* @__PURE__ */ jsx4(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx4(
1763
+ InterimButtonsView,
1764
+ {
1765
+ onButtonClick,
1766
+ showPayPal,
1767
+ showApplePay,
1768
+ showGooglePay,
1769
+ buttonsTheme,
1770
+ buttonsStyles
1771
+ }
1772
+ ) });
1773
+ }
1418
1774
  if (!flopay || !providerOptions) return /* @__PURE__ */ jsx4(Fragment2, {});
1419
1775
  if (currentMode === "confirm") {
1420
1776
  return /* @__PURE__ */ jsx4(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx4(FloPayProvider, { flopay, options: providerOptions, children: /* @__PURE__ */ jsxs2("div", { className, children: [
@@ -1475,7 +1831,7 @@ function FloPayCheckout({
1475
1831
  children ? /* @__PURE__ */ jsx4(
1476
1832
  SessionInjector,
1477
1833
  {
1478
- sessionId,
1834
+ sessionId: resolvedSessionId,
1479
1835
  billingApiUrl: resolvedBillingUrl,
1480
1836
  session,
1481
1837
  children
@@ -1483,7 +1839,7 @@ function FloPayCheckout({
1483
1839
  ) : /* @__PURE__ */ jsx4(
1484
1840
  SplitCardForm,
1485
1841
  {
1486
- sessionId,
1842
+ sessionId: resolvedSessionId,
1487
1843
  email: session?.customer?.email,
1488
1844
  userId: session?.customer?.id,
1489
1845
  firstName: session?.customer?.firstName,
@@ -1496,6 +1852,13 @@ function FloPayCheckout({
1496
1852
  showApplePay,
1497
1853
  showGooglePay,
1498
1854
  layout,
1855
+ buttonsTheme,
1856
+ buttonsStyles,
1857
+ onButtonClick,
1858
+ enableAVS,
1859
+ avsLayout,
1860
+ country: session?.customer?.country,
1861
+ zip: session?.customer?.zip,
1499
1862
  submitLabel,
1500
1863
  className
1501
1864
  }
@@ -1526,6 +1889,162 @@ function SessionInjector({
1526
1889
  return React4.cloneElement(child, injected);
1527
1890
  }) });
1528
1891
  }
1892
+ function InterimButtonsView({
1893
+ onButtonClick,
1894
+ showPayPal,
1895
+ showApplePay,
1896
+ showGooglePay,
1897
+ buttonsTheme,
1898
+ buttonsStyles: stylesOverride
1899
+ }) {
1900
+ const [showCardForm, setShowCardForm] = useState3(false);
1901
+ const bStyles = useMemo3(() => {
1902
+ const base = resolveButtonsLayoutTheme2(buttonsTheme);
1903
+ if (!stylesOverride) return base;
1904
+ return {
1905
+ ...base,
1906
+ ...stylesOverride,
1907
+ cardButton: { ...base.cardButton, ...stylesOverride.cardButton },
1908
+ cardFormContainer: { ...base.cardFormContainer, ...stylesOverride.cardFormContainer },
1909
+ submitButton: { ...base.submitButton, ...stylesOverride.submitButton },
1910
+ title: { ...base.title, ...stylesOverride.title }
1911
+ };
1912
+ }, [buttonsTheme, stylesOverride]);
1913
+ const skeleton = (h) => /* @__PURE__ */ jsx4("div", { style: {
1914
+ height: h,
1915
+ borderRadius: 8,
1916
+ background: "#e5e7eb",
1917
+ animation: "flopay-interim-pulse 1.5s ease-in-out infinite"
1918
+ } });
1919
+ if (showCardForm) {
1920
+ const inputBorder = bStyles.cardInputBorder ?? "#e5e7eb";
1921
+ const inputBg = bStyles.cardInputBackground ?? "white";
1922
+ return /* @__PURE__ */ jsxs2("div", { style: {
1923
+ backgroundColor: bStyles.cardFormContainer?.backgroundColor ?? "white",
1924
+ borderRadius: "8px",
1925
+ animation: "flopay-interim-expand 0.35s cubic-bezier(0.4, 0, 0.2, 1) both",
1926
+ overflow: "hidden",
1927
+ ...bStyles.cardFormContainer
1928
+ }, children: [
1929
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", padding: "0.75rem 0 0.625rem" }, children: [
1930
+ /* @__PURE__ */ jsxs2(
1931
+ "button",
1932
+ {
1933
+ type: "button",
1934
+ onClick: () => setShowCardForm(false),
1935
+ style: {
1936
+ display: "inline-flex",
1937
+ alignItems: "center",
1938
+ gap: "0.5rem",
1939
+ background: "none",
1940
+ border: "none",
1941
+ cursor: "pointer",
1942
+ color: "#4b5563",
1943
+ fontSize: "0.85rem",
1944
+ fontWeight: 500,
1945
+ padding: 0,
1946
+ flexShrink: 0,
1947
+ ...bStyles.backButton
1948
+ },
1949
+ children: [
1950
+ /* @__PURE__ */ jsx4("span", { style: {
1951
+ display: "inline-flex",
1952
+ alignItems: "center",
1953
+ justifyContent: "center",
1954
+ width: 28,
1955
+ height: 28,
1956
+ borderRadius: "50%",
1957
+ backgroundColor: "#f3f4f6",
1958
+ ...bStyles.backButtonIcon
1959
+ }, children: /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M15 18l-6-6 6-6" }) }) }),
1960
+ "Go back"
1961
+ ]
1962
+ }
1963
+ ),
1964
+ /* @__PURE__ */ jsx4("div", { style: {
1965
+ flex: 1,
1966
+ textAlign: "center",
1967
+ fontWeight: 600,
1968
+ fontSize: "1.05rem",
1969
+ color: "#262833",
1970
+ paddingRight: 80,
1971
+ ...bStyles.title
1972
+ }, children: "Secure card checkout" })
1973
+ ] }),
1974
+ /* @__PURE__ */ jsx4("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTopLeftRadius: 8, borderTopRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx4("div", { style: { width: "60%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
1975
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex" }, children: [
1976
+ /* @__PURE__ */ jsx4("div", { style: { flex: 1, backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTop: "none", borderRight: "none", borderBottomLeftRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx4("div", { style: { width: "50%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
1977
+ /* @__PURE__ */ jsx4("div", { style: { flex: 1, backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTop: "none", borderBottomRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx4("div", { style: { width: "40%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) })
1978
+ ] }),
1979
+ /* @__PURE__ */ jsx4("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderRadius: 8, marginTop: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx4("div", { style: { width: "45%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
1980
+ /* @__PURE__ */ jsx4("div", { style: {
1981
+ height: 50,
1982
+ borderRadius: 8,
1983
+ marginTop: 16,
1984
+ background: "#c8c8ff",
1985
+ animation: "flopay-interim-pulse 1.5s ease-in-out infinite",
1986
+ ...bStyles.submitButton,
1987
+ opacity: 0.5
1988
+ } }),
1989
+ /* @__PURE__ */ jsx4("style", { children: `
1990
+ @keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
1991
+ @keyframes flopay-interim-expand {
1992
+ 0% { opacity: 0; max-height: 0; transform: translateY(-12px); }
1993
+ 40% { opacity: 1; }
1994
+ 100% { opacity: 1; max-height: 600px; transform: translateY(0); }
1995
+ }
1996
+ ` })
1997
+ ] });
1998
+ }
1999
+ return /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
2000
+ showPayPal && skeleton(44),
2001
+ (showApplePay || showGooglePay) && skeleton(44),
2002
+ /* @__PURE__ */ jsxs2(
2003
+ "button",
2004
+ {
2005
+ type: "button",
2006
+ onClick: () => {
2007
+ onButtonClick?.("card");
2008
+ setShowCardForm(true);
2009
+ },
2010
+ style: {
2011
+ width: "100%",
2012
+ padding: "0.9rem 1rem",
2013
+ backgroundColor: "white",
2014
+ color: "#262833",
2015
+ border: "1px solid #d1d5db",
2016
+ borderRadius: "8px",
2017
+ fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
2018
+ fontWeight: 600,
2019
+ cursor: "pointer",
2020
+ display: "flex",
2021
+ alignItems: "center",
2022
+ justifyContent: "center",
2023
+ gap: "0.625rem",
2024
+ boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
2025
+ transition: "transform 0.1s",
2026
+ position: "relative",
2027
+ ...bStyles.cardButton
2028
+ },
2029
+ onMouseDown: (e) => {
2030
+ e.currentTarget.style.transform = "scale(0.985)";
2031
+ },
2032
+ onMouseUp: (e) => {
2033
+ e.currentTarget.style.transform = "scale(1)";
2034
+ },
2035
+ children: [
2036
+ /* @__PURE__ */ jsxs2("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
2037
+ /* @__PURE__ */ jsx4("rect", { width: "20", height: "14", x: "2", y: "5", rx: "2" }),
2038
+ /* @__PURE__ */ jsx4("line", { x1: "2", x2: "22", y1: "10", y2: "10" })
2039
+ ] }),
2040
+ "Credit / Debit Card",
2041
+ /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "#9ca3af", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", style: { position: "absolute", right: "1rem" }, children: /* @__PURE__ */ jsx4("path", { d: "M9 18l6-6-6-6" }) })
2042
+ ]
2043
+ }
2044
+ ),
2045
+ /* @__PURE__ */ jsx4("style", { children: `@keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
2046
+ ] });
2047
+ }
1529
2048
 
1530
2049
  // src/checkout-form.tsx
1531
2050
  import { FloPayError as FloPayError3 } from "@flopay/shared";