@flopay/react 0.1.6 → 0.2.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.cjs CHANGED
@@ -118,7 +118,7 @@ function FloPayProvider({
118
118
  // src/flopay-checkout.tsx
119
119
  var import_react6 = __toESM(require("react"), 1);
120
120
  var import_js = require("@flopay/js");
121
- var import_shared4 = require("@flopay/shared");
121
+ var import_shared5 = require("@flopay/shared");
122
122
 
123
123
  // src/elements.tsx
124
124
  var import_react3 = require("react");
@@ -182,6 +182,7 @@ var AddressElement = createElementComponent("address", "AddressElement");
182
182
 
183
183
  // src/split-card-form.tsx
184
184
  var import_react_stripe_js = require("@stripe/react-stripe-js");
185
+ var import_shared3 = require("@flopay/shared");
185
186
  var import_react5 = require("react");
186
187
 
187
188
  // src/hooks.ts
@@ -204,9 +205,32 @@ function useBillingApiUrl() {
204
205
  }
205
206
 
206
207
  // src/split-card-form.tsx
207
- var import_shared3 = require("@flopay/shared");
208
+ var import_shared4 = require("@flopay/shared");
208
209
  var import_jsx_runtime3 = require("react/jsx-runtime");
209
210
  var WALLET_RESUME_KEY = "flopay_wallet_resume";
211
+ var FLOPAY_KEYFRAMES = `
212
+ @keyframes flopay-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
213
+ @keyframes flopay-fade-in { 0% { opacity: 0; } 100% { opacity: 1; } }
214
+ @keyframes flopay-buttons-enter {
215
+ 0% { opacity: 0; transform: translateX(-16px) scale(0.97); }
216
+ 100% { opacity: 1; transform: translateX(0) scale(1); }
217
+ }
218
+ @keyframes flopay-buttons-exit {
219
+ 0% { opacity: 1; transform: translateX(0) scale(1); }
220
+ 100% { opacity: 0; transform: translateX(-16px) scale(0.97); }
221
+ }
222
+ @keyframes flopay-card-enter {
223
+ 0% { opacity: 0; transform: translateX(16px) scale(0.97); }
224
+ 100% { opacity: 1; transform: translateX(0) scale(1); }
225
+ }
226
+ @keyframes flopay-card-exit {
227
+ 0% { opacity: 1; transform: translateX(0) scale(1); }
228
+ 100% { opacity: 0; transform: translateX(16px) scale(0.97); }
229
+ }
230
+ `;
231
+ function FloPayKeyframes() {
232
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: FLOPAY_KEYFRAMES });
233
+ }
210
234
  function ProcessingOverlay({ status, errorMessage }) {
211
235
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
212
236
  position: "fixed",
@@ -296,8 +320,6 @@ function ProcessingOverlay({ status, errorMessage }) {
296
320
  @keyframes flopay-pop { 0% { transform: scale(0); } 100% { transform: scale(1); } }
297
321
  @keyframes flopay-draw { to { stroke-dashoffset: 0; } }
298
322
  @keyframes flopay-shake { 0%,100% { transform: translateX(0); } 20%,60% { transform: translateX(-4px); } 40%,80% { transform: translateX(4px); } }
299
- @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); } }
300
- @keyframes flopay-fade-in { 0% { opacity: 0; } 100% { opacity: 1; } }
301
323
  ` })
302
324
  ] }) });
303
325
  }
@@ -312,7 +334,8 @@ function PayPalButtonInner({
312
334
  billingApiUrl,
313
335
  onTokenizedBody,
314
336
  onErrorChange,
315
- isProcessing = false
337
+ isProcessing = false,
338
+ onButtonClick
316
339
  }) {
317
340
  const stripe = (0, import_react_stripe_js.useStripe)();
318
341
  const elements = (0, import_react_stripe_js.useElements)();
@@ -365,6 +388,7 @@ function PayPalButtonInner({
365
388
  }, [stripe, onTokenizedBody, onErrorChange]);
366
389
  const handlePayPalConfirm = (0, import_react5.useCallback)(async (_event) => {
367
390
  if (!stripe || !elements) return;
391
+ onButtonClick?.("paypal");
368
392
  try {
369
393
  setSubmitting(true);
370
394
  onErrorChange?.(null);
@@ -419,7 +443,7 @@ function PayPalButtonInner({
419
443
  }
420
444
  }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]);
421
445
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
422
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginBottom: ready ? "0.5rem" : 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
446
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
423
447
  import_react_stripe_js.ExpressCheckoutElement,
424
448
  {
425
449
  onReady: () => setReady(true),
@@ -447,7 +471,8 @@ function WalletButtonInner({
447
471
  showApplePay = true,
448
472
  showGooglePay = true,
449
473
  onTokenizedBody,
450
- onErrorChange
474
+ onErrorChange,
475
+ onButtonClick
451
476
  }) {
452
477
  const stripe = (0, import_react_stripe_js.useStripe)();
453
478
  const elements = (0, import_react_stripe_js.useElements)();
@@ -457,6 +482,8 @@ function WalletButtonInner({
457
482
  const handleWalletConfirm = (0, import_react5.useCallback)(
458
483
  async (_event) => {
459
484
  if (!stripe || !elements) return;
485
+ const walletType = _event.expressPaymentType;
486
+ onButtonClick?.(walletType === "apple_pay" ? "apple_pay" : "google_pay");
460
487
  try {
461
488
  setSubmitting(true);
462
489
  onErrorChange?.(null);
@@ -509,7 +536,7 @@ function WalletButtonInner({
509
536
  [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]
510
537
  );
511
538
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
512
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { marginBottom: ready ? "0.5rem" : 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
539
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
513
540
  import_react_stripe_js.ExpressCheckoutElement,
514
541
  {
515
542
  onReady: () => setReady(true),
@@ -556,6 +583,9 @@ function SplitCardFormInner({
556
583
  showApplePay = true,
557
584
  showGooglePay = true,
558
585
  layout = "default",
586
+ buttonsTheme,
587
+ buttonsStyles: buttonsStylesOverride,
588
+ onButtonClick,
559
589
  totalAmount = 0,
560
590
  currency = "usd",
561
591
  innerRef
@@ -565,14 +595,38 @@ function SplitCardFormInner({
565
595
  const contextBillingUrl = useBillingApiUrl();
566
596
  const [processing, setProcessing] = (0, import_react5.useState)(false);
567
597
  const [error, setError] = (0, import_react5.useState)(null);
568
- const [showCardForm, setShowCardForm] = (0, import_react5.useState)(false);
569
598
  const [is3DSActive, setIs3DSActive] = (0, import_react5.useState)(false);
599
+ const [viewState, setViewState] = (0, import_react5.useState)("buttons");
600
+ const showCardForm = viewState === "expanding" || viewState === "card";
601
+ const TRANSITION_MS = 280;
602
+ const expandToCard = (0, import_react5.useCallback)(() => {
603
+ setViewState("expanding");
604
+ setTimeout(() => setViewState("card"), TRANSITION_MS);
605
+ }, []);
606
+ const collapseToButtons = (0, import_react5.useCallback)(() => {
607
+ setViewState("collapsing");
608
+ setTimeout(() => setViewState("buttons"), TRANSITION_MS);
609
+ }, []);
570
610
  const [fullName, setFullName] = (0, import_react5.useState)("");
571
611
  const [formReady, setFormReady] = (0, import_react5.useState)(false);
572
612
  const [overlayStatus, setOverlayStatus] = (0, import_react5.useState)(null);
573
613
  const processingRef = (0, import_react5.useRef)(false);
574
614
  const resolvedBillingApiUrl = billingApiUrl || contextBillingUrl;
575
615
  const displayError = externalError ?? error;
616
+ const bStyles = (0, import_react5.useMemo)(() => {
617
+ const base = (0, import_shared3.resolveButtonsLayoutTheme)(buttonsTheme);
618
+ if (!buttonsStylesOverride) return base;
619
+ return {
620
+ ...base,
621
+ ...buttonsStylesOverride,
622
+ cardButton: { ...base.cardButton, ...buttonsStylesOverride.cardButton },
623
+ cardFormContainer: { ...base.cardFormContainer, ...buttonsStylesOverride.cardFormContainer },
624
+ backButton: { ...base.backButton, ...buttonsStylesOverride.backButton },
625
+ backButtonIcon: { ...base.backButtonIcon, ...buttonsStylesOverride.backButtonIcon },
626
+ submitButton: { ...base.submitButton, ...buttonsStylesOverride.submitButton },
627
+ title: { ...base.title, ...buttonsStylesOverride.title }
628
+ };
629
+ }, [buttonsTheme, buttonsStylesOverride]);
576
630
  const isSubmitting = externalProcessing ?? processing;
577
631
  const isSelfContained = !onTokenizedBody;
578
632
  const baseUrl = resolvedBillingApiUrl.replace(/\/+$/, "");
@@ -784,6 +838,7 @@ function SplitCardFormInner({
784
838
  async (e) => {
785
839
  e.preventDefault();
786
840
  if (!flopay || !elements || isSubmitting || processingRef.current) return;
841
+ onButtonClick?.("card");
787
842
  setProcessing(true);
788
843
  setOverlayStatus("processing");
789
844
  updateError(null);
@@ -801,7 +856,7 @@ function SplitCardFormInner({
801
856
  return;
802
857
  }
803
858
  if (!sessionId || !email) {
804
- throw new import_shared3.FloPayError("Missing sessionId or email", "validation_error");
859
+ throw new import_shared4.FloPayError("Missing sessionId or email", "validation_error");
805
860
  }
806
861
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
807
862
  method: "POST",
@@ -813,10 +868,10 @@ function SplitCardFormInner({
813
868
  isPaypal: false
814
869
  })
815
870
  });
816
- if (!intentResponse.ok) throw new import_shared3.FloPayError("Failed to create payment intent", "api_error");
871
+ if (!intentResponse.ok) throw new import_shared4.FloPayError("Failed to create payment intent", "api_error");
817
872
  const intentJson = await intentResponse.json();
818
873
  const intentClientSecret = intentJson.data?.id;
819
- if (!intentClientSecret) throw new import_shared3.FloPayError("No client_secret in payment intent response", "api_error");
874
+ if (!intentClientSecret) throw new import_shared4.FloPayError("No client_secret in payment intent response", "api_error");
820
875
  const confirmResult = await flopay.confirmCardPayment({
821
876
  clientSecret: intentClientSecret,
822
877
  paymentMethodId: pmResult.paymentMethodId
@@ -851,15 +906,25 @@ function SplitCardFormInner({
851
906
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { "data-testid": "flopay-loading", "aria-busy": "true", children: "Loading payment form..." });
852
907
  }
853
908
  const isButtons = layout === "buttons";
854
- const cardBorderColor = isButtons ? "#e5e7eb" : "#A4A4FF";
855
- const cardBg = isButtons ? "white" : "#EDEDFF";
909
+ const resolvedBorder = isButtons ? bStyles.cardInputBorder ?? "#e5e7eb" : "#A4A4FF";
910
+ const cardBg = isButtons ? bStyles.cardFormContainer?.backgroundColor ?? "white" : "#EDEDFF";
911
+ const cardInputBg = isButtons ? bStyles.cardInputBackground ?? "white" : "white";
912
+ const stripeElementStyle = isButtons && (bStyles.cardInputColor || bStyles.cardInputPlaceholderColor || bStyles.cardInputFontSize) ? {
913
+ style: {
914
+ base: {
915
+ ...bStyles.cardInputColor ? { color: bStyles.cardInputColor } : {},
916
+ ...bStyles.cardInputFontSize ? { fontSize: bStyles.cardInputFontSize } : {},
917
+ ...bStyles.cardInputPlaceholderColor ? { "::placeholder": { color: bStyles.cardInputPlaceholderColor } } : {}
918
+ },
919
+ invalid: { color: "#ef4444" }
920
+ }
921
+ } : {};
856
922
  const cardFormBlock = /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
857
923
  backgroundColor: cardBg,
858
924
  borderRadius: "8px",
859
925
  padding: isButtons ? "0" : "1rem",
860
- ...isButtons && showCardForm ? {
861
- animation: "flopay-expand 0.4s cubic-bezier(0.16,1,0.3,1) both"
862
- } : {}
926
+ ...isButtons ? bStyles.cardFormContainer : {},
927
+ ...isButtons ? { padding: bStyles.cardFormContainer?.padding ?? "0" } : {}
863
928
  }, children: [
864
929
  isButtons && showCardForm && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
865
930
  display: "flex",
@@ -870,7 +935,7 @@ function SplitCardFormInner({
870
935
  "button",
871
936
  {
872
937
  type: "button",
873
- onClick: () => setShowCardForm(false),
938
+ onClick: collapseToButtons,
874
939
  style: {
875
940
  display: "inline-flex",
876
941
  alignItems: "center",
@@ -879,17 +944,12 @@ function SplitCardFormInner({
879
944
  border: "none",
880
945
  cursor: "pointer",
881
946
  color: "#4b5563",
882
- fontSize: "0.85rem",
947
+ fontSize: bStyles.backButtonFontSize ?? "0.85rem",
883
948
  fontWeight: 500,
884
949
  padding: 0,
885
950
  transition: "color 0.15s",
886
- flexShrink: 0
887
- },
888
- onMouseOver: (e) => {
889
- e.currentTarget.style.color = "#1f2937";
890
- },
891
- onMouseOut: (e) => {
892
- e.currentTarget.style.color = "#4b5563";
951
+ flexShrink: 0,
952
+ ...bStyles.backButton
893
953
  },
894
954
  "aria-label": "Back to payment methods",
895
955
  children: [
@@ -901,44 +961,53 @@ function SplitCardFormInner({
901
961
  height: 28,
902
962
  borderRadius: "50%",
903
963
  backgroundColor: "#f3f4f6",
904
- transition: "background-color 0.15s"
964
+ transition: "background-color 0.15s",
965
+ ...bStyles.backButtonIcon
905
966
  }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M15 18l-6-6 6-6" }) }) }),
906
967
  "Go back"
907
968
  ]
908
969
  }
909
970
  ),
910
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { flex: 1, textAlign: "center", fontWeight: 600, fontSize: "1.05rem", color: "#262833", paddingRight: 80 }, children: "Secure card checkout" })
971
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
972
+ flex: 1,
973
+ textAlign: "center",
974
+ fontWeight: 600,
975
+ fontSize: bStyles.titleFontSize ?? "1.05rem",
976
+ color: "#262833",
977
+ paddingRight: 80,
978
+ ...bStyles.title
979
+ }, children: "Secure card checkout" })
911
980
  ] }),
912
981
  !isButtons && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { textAlign: "center", fontWeight: 600, fontSize: "1.1rem", padding: "0.5rem 0", color: "#262833" }, children: "Secure card checkout" }),
913
982
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
914
- backgroundColor: "white",
915
- border: `1px solid ${cardBorderColor}`,
983
+ backgroundColor: cardInputBg,
984
+ border: `1px solid ${resolvedBorder}`,
916
985
  borderTopLeftRadius: "8px",
917
986
  borderTopRightRadius: "8px",
918
987
  padding: "10px"
919
- }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CardNumberElement, { onReady: () => setFormReady(true) }) }),
988
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CardNumberElement, { onReady: () => setFormReady(true), options: stripeElementStyle }) }),
920
989
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "flex" }, children: [
921
990
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
922
991
  flex: 1,
923
- backgroundColor: "white",
924
- border: `1px solid ${cardBorderColor}`,
992
+ backgroundColor: cardInputBg,
993
+ border: `1px solid ${resolvedBorder}`,
925
994
  borderTop: "none",
926
995
  borderRight: "none",
927
996
  borderBottomLeftRadius: "8px",
928
997
  padding: "10px"
929
- }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CardExpiryElement, {}) }),
998
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CardExpiryElement, { options: stripeElementStyle }) }),
930
999
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
931
1000
  flex: 1,
932
- backgroundColor: "white",
933
- border: `1px solid ${cardBorderColor}`,
1001
+ backgroundColor: cardInputBg,
1002
+ border: `1px solid ${resolvedBorder}`,
934
1003
  borderTop: "none",
935
1004
  borderBottomRightRadius: "8px",
936
1005
  padding: "10px"
937
- }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CardCvcElement, {}) })
1006
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CardCvcElement, { options: stripeElementStyle }) })
938
1007
  ] }),
939
1008
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
940
- backgroundColor: "white",
941
- border: `1px solid ${cardBorderColor}`,
1009
+ backgroundColor: cardInputBg,
1010
+ border: `1px solid ${resolvedBorder}`,
942
1011
  borderRadius: "8px",
943
1012
  marginTop: "0.5rem",
944
1013
  padding: "10px"
@@ -955,9 +1024,11 @@ function SplitCardFormInner({
955
1024
  width: "100%",
956
1025
  border: "none",
957
1026
  outline: "none",
958
- fontSize: "16px",
1027
+ background: "transparent",
1028
+ fontSize: bStyles.cardInputFontSize ?? "16px",
959
1029
  fontFamily: "Poppins, sans-serif",
960
- color: "#262833"
1030
+ color: bStyles.cardInputColor ?? "#262833",
1031
+ ...isButtons && bStyles.nameInput ? bStyles.nameInput : {}
961
1032
  }
962
1033
  }
963
1034
  ) }),
@@ -972,7 +1043,8 @@ function SplitCardFormInner({
972
1043
  fontWeight: 600,
973
1044
  display: "flex",
974
1045
  alignItems: "center",
975
- gap: "0.5rem"
1046
+ gap: "0.5rem",
1047
+ ...isButtons && bStyles.errorBanner ? bStyles.errorBanner : {}
976
1048
  }, children: [
977
1049
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }) }),
978
1050
  displayError
@@ -991,10 +1063,11 @@ function SplitCardFormInner({
991
1063
  color: "white",
992
1064
  border: "none",
993
1065
  borderRadius: "8px",
994
- fontSize: "1rem",
1066
+ fontSize: isButtons && bStyles.submitButtonFontSize ? bStyles.submitButtonFontSize : "1rem",
995
1067
  fontWeight: 600,
996
1068
  cursor: !formReady || isSubmitting ? "not-allowed" : "pointer",
997
- opacity: !formReady || isSubmitting ? 0.5 : 1
1069
+ opacity: !formReady || isSubmitting ? 0.5 : 1,
1070
+ ...isButtons ? bStyles.submitButton : {}
998
1071
  },
999
1072
  children: isSubmitting ? "PROCESSING..." : submitLabel
1000
1073
  }
@@ -1011,103 +1084,119 @@ function SplitCardFormInner({
1011
1084
  }, children: "Secure Card Checkout" })
1012
1085
  ] });
1013
1086
  if (layout === "buttons") {
1087
+ const isButtonsView = viewState === "buttons" || viewState === "expanding";
1088
+ const isCardView = viewState === "expanding" || viewState === "card" || viewState === "collapsing";
1089
+ 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;
1090
+ 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;
1014
1091
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
1092
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FloPayKeyframes, {}),
1015
1093
  overlayStatus && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
1016
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
1017
- display: showCardForm ? "none" : "flex",
1018
- flexDirection: "column",
1019
- gap: "0.5rem"
1020
- }, children: [
1021
- showPayPal && stripeInstance && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1022
- PayPalButtonInner,
1023
- {
1024
- sessionId,
1025
- email,
1026
- billingApiUrl: resolvedBillingApiUrl,
1027
- onTokenizedBody: dispatchTokenizedBody,
1028
- onErrorChange: updateError,
1029
- isProcessing: isSubmitting
1030
- }
1031
- ) }),
1032
- showWallets && stripeInstance && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1033
- WalletButtonInner,
1034
- {
1035
- sessionId,
1036
- email,
1037
- billingApiUrl: resolvedBillingApiUrl,
1038
- showApplePay,
1039
- showGooglePay,
1040
- onTokenizedBody: dispatchTokenizedBody,
1041
- onErrorChange: updateError
1042
- }
1043
- ) }),
1044
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1045
- "button",
1046
- {
1047
- type: "button",
1048
- onClick: () => setShowCardForm(true),
1049
- style: {
1050
- width: "100%",
1051
- padding: "0.9rem 1rem",
1052
- backgroundColor: "white",
1053
- color: "#262833",
1054
- border: "1px solid #d1d5db",
1055
- borderRadius: "8px",
1056
- fontSize: "0.95rem",
1057
- fontWeight: 600,
1058
- cursor: "pointer",
1059
- display: "flex",
1060
- alignItems: "center",
1061
- justifyContent: "center",
1062
- gap: "0.625rem",
1063
- transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
1064
- boxShadow: "0 1px 2px rgba(0,0,0,0.04)"
1065
- },
1066
- onMouseOver: (e) => {
1067
- e.currentTarget.style.borderColor = "#A4A4FF";
1068
- e.currentTarget.style.boxShadow = "0 0 0 1px #A4A4FF, 0 2px 8px rgba(74,73,255,0.08)";
1069
- },
1070
- onMouseOut: (e) => {
1071
- e.currentTarget.style.borderColor = "#d1d5db";
1072
- e.currentTarget.style.boxShadow = "0 1px 2px rgba(0,0,0,0.04)";
1073
- },
1074
- onMouseDown: (e) => {
1075
- e.currentTarget.style.transform = "scale(0.985)";
1076
- },
1077
- onMouseUp: (e) => {
1078
- e.currentTarget.style.transform = "scale(1)";
1079
- },
1080
- children: [
1081
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1082
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "20", height: "14", x: "2", y: "5", rx: "2" }),
1083
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "2", x2: "22", y1: "10", y2: "10" })
1084
- ] }),
1085
- "Credit / Debit Card",
1086
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M9 18l6-6-6-6" }) })
1087
- ]
1088
- }
1089
- ),
1090
- displayError && !showCardForm && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { role: "alert", "data-testid": "flopay-error", style: {
1091
- margin: "0.25rem 0",
1092
- padding: "0.625rem 0.875rem",
1093
- background: "#FEF2F2",
1094
- border: "1px solid #FECACA",
1095
- borderRadius: "8px",
1096
- color: "#991B1B",
1097
- fontSize: "0.85rem",
1098
- fontWeight: 600,
1094
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: { display: "grid" }, children: [
1095
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: {
1096
+ gridArea: "1 / 1",
1099
1097
  display: "flex",
1100
- alignItems: "center",
1101
- gap: "0.5rem"
1098
+ flexDirection: "column",
1099
+ gap: "0.5rem",
1100
+ // When card form is showing (and not transitioning), hide but keep mounted
1101
+ ...!isButtonsView && !buttonsAnim ? { visibility: "hidden", position: "absolute", pointerEvents: "none", width: "100%" } : {},
1102
+ ...buttonsAnim ? { animation: buttonsAnim, pointerEvents: "none" } : {}
1102
1103
  }, children: [
1103
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }) }),
1104
- displayError
1105
- ] })
1106
- ] }),
1107
- showCardForm && cardFormBlock
1104
+ showPayPal && stripeInstance ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1105
+ PayPalButtonInner,
1106
+ {
1107
+ sessionId,
1108
+ email,
1109
+ billingApiUrl: resolvedBillingApiUrl,
1110
+ onTokenizedBody: dispatchTokenizedBody,
1111
+ onErrorChange: updateError,
1112
+ isProcessing: isSubmitting,
1113
+ onButtonClick
1114
+ }
1115
+ ) }) : showPayPal ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1116
+ showWallets && stripeInstance ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1117
+ WalletButtonInner,
1118
+ {
1119
+ sessionId,
1120
+ email,
1121
+ billingApiUrl: resolvedBillingApiUrl,
1122
+ showApplePay,
1123
+ showGooglePay,
1124
+ onTokenizedBody: dispatchTokenizedBody,
1125
+ onErrorChange: updateError,
1126
+ onButtonClick
1127
+ }
1128
+ ) }) : showWallets ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1129
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1130
+ "button",
1131
+ {
1132
+ type: "button",
1133
+ onClick: () => {
1134
+ onButtonClick?.("card");
1135
+ expandToCard();
1136
+ },
1137
+ style: {
1138
+ width: "100%",
1139
+ padding: "0.9rem 1rem",
1140
+ backgroundColor: "white",
1141
+ color: "#262833",
1142
+ border: "1px solid #d1d5db",
1143
+ borderRadius: "8px",
1144
+ fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
1145
+ fontWeight: 600,
1146
+ cursor: "pointer",
1147
+ display: "flex",
1148
+ alignItems: "center",
1149
+ justifyContent: "center",
1150
+ gap: "0.625rem",
1151
+ transition: "border-color 0.2s, box-shadow 0.2s, transform 0.1s",
1152
+ boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
1153
+ position: "relative",
1154
+ ...bStyles.cardButton
1155
+ },
1156
+ onMouseDown: (e) => {
1157
+ e.currentTarget.style.transform = "scale(0.985)";
1158
+ },
1159
+ onMouseUp: (e) => {
1160
+ e.currentTarget.style.transform = "scale(1)";
1161
+ },
1162
+ children: [
1163
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1164
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("rect", { width: "20", height: "14", x: "2", y: "5", rx: "2" }),
1165
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("line", { x1: "2", x2: "22", y1: "10", y2: "10" })
1166
+ ] }),
1167
+ "Credit / Debit Card",
1168
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M9 18l6-6-6-6" }) })
1169
+ ]
1170
+ }
1171
+ ),
1172
+ displayError && viewState === "buttons" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { role: "alert", "data-testid": "flopay-error", style: {
1173
+ margin: "0.25rem 0",
1174
+ padding: "0.625rem 0.875rem",
1175
+ background: "#FEF2F2",
1176
+ border: "1px solid #FECACA",
1177
+ borderRadius: "8px",
1178
+ color: "#991B1B",
1179
+ fontSize: "0.85rem",
1180
+ fontWeight: 600,
1181
+ display: "flex",
1182
+ alignItems: "center",
1183
+ gap: "0.5rem",
1184
+ ...bStyles.errorBanner ? bStyles.errorBanner : {}
1185
+ }, children: [
1186
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("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" }) }),
1187
+ displayError
1188
+ ] })
1189
+ ] }),
1190
+ isCardView && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: {
1191
+ gridArea: "1 / 1",
1192
+ ...cardAnim ? { animation: cardAnim } : {},
1193
+ ...viewState === "collapsing" ? { pointerEvents: "none" } : {}
1194
+ }, children: cardFormBlock })
1195
+ ] })
1108
1196
  ] });
1109
1197
  }
1110
1198
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
1199
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FloPayKeyframes, {}),
1111
1200
  overlayStatus && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
1112
1201
  showWallets && stripeInstance && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1113
1202
  WalletButtonInner,
@@ -1151,7 +1240,8 @@ function SplitCardFormInner({
1151
1240
  // src/flopay-checkout.tsx
1152
1241
  var import_jsx_runtime4 = require("react/jsx-runtime");
1153
1242
  function FloPayCheckout({
1154
- sessionId,
1243
+ sessionId: sessionIdProp,
1244
+ createSession: createSessionParams,
1155
1245
  billingApiUrl,
1156
1246
  appearance,
1157
1247
  locale,
@@ -1164,6 +1254,9 @@ function FloPayCheckout({
1164
1254
  showApplePay = true,
1165
1255
  showGooglePay = true,
1166
1256
  layout,
1257
+ buttonsTheme,
1258
+ buttonsStyles,
1259
+ onButtonClick,
1167
1260
  submitLabel,
1168
1261
  className,
1169
1262
  children,
@@ -1172,11 +1265,12 @@ function FloPayCheckout({
1172
1265
  renderConfirmButton,
1173
1266
  onSessionCompleted
1174
1267
  }) {
1175
- const resolvedBillingUrl = (0, import_shared4.resolveBillingApiUrl)(billingApiUrl);
1268
+ const resolvedBillingUrl = (0, import_shared5.resolveBillingApiUrl)(billingApiUrl);
1176
1269
  const [unified, setUnified] = (0, import_react6.useState)(null);
1177
1270
  const [flopay, setFloPay] = (0, import_react6.useState)(null);
1178
1271
  const flopayRef = (0, import_react6.useRef)(null);
1179
1272
  const [session, setSession] = (0, import_react6.useState)(null);
1273
+ const [resolvedSessionId, setResolvedSessionId] = (0, import_react6.useState)(sessionIdProp ?? "");
1180
1274
  const [isLoading, setIsLoading] = (0, import_react6.useState)(true);
1181
1275
  const [loadError, setLoadError] = (0, import_react6.useState)(null);
1182
1276
  const [currentMode, setCurrentMode] = (0, import_react6.useState)("full");
@@ -1199,7 +1293,7 @@ function FloPayCheckout({
1199
1293
  "x-user-id": sess.customer?.id ?? ""
1200
1294
  },
1201
1295
  body: JSON.stringify({
1202
- sessionId,
1296
+ sessionId: resolvedSessionId,
1203
1297
  tokenizedData: { id: void 0 },
1204
1298
  accountData: {
1205
1299
  userId: sess.customer?.id ?? "",
@@ -1228,12 +1322,12 @@ function FloPayCheckout({
1228
1322
  // No client secret available
1229
1323
  };
1230
1324
  }
1231
- throw new import_shared4.FloPayError(
1325
+ throw new import_shared5.FloPayError(
1232
1326
  json?.message ?? "Payment failed. Please try again.",
1233
1327
  "api_error"
1234
1328
  );
1235
1329
  },
1236
- [resolvedBillingUrl, sessionId]
1330
+ [resolvedBillingUrl, resolvedSessionId]
1237
1331
  );
1238
1332
  const handleRedirectResult = (0, import_react6.useCallback)(
1239
1333
  async (redirectResult, sess, options) => {
@@ -1278,22 +1372,175 @@ function FloPayCheckout({
1278
1372
  }
1279
1373
  return false;
1280
1374
  },
1281
- [resolvedBillingUrl, sessionId]
1375
+ [resolvedBillingUrl, resolvedSessionId]
1282
1376
  );
1377
+ const inflightRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
1378
+ const initializedHashRef = (0, import_react6.useRef)(null);
1379
+ function hashCreateParams(params) {
1380
+ const key = JSON.stringify({
1381
+ c: params?.clientId,
1382
+ i: params?.items?.map((x) => `${x.providerItemId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
1383
+ s: params?.subscriptions?.map((x) => `${x.providerPlanId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
1384
+ e: params?.account.email,
1385
+ m: params?.checkoutMode ?? "full"
1386
+ });
1387
+ let h = 0;
1388
+ for (let i = 0; i < key.length; i++) {
1389
+ h = (h << 5) - h + key.charCodeAt(i) | 0;
1390
+ }
1391
+ return `flopay_session_${Math.abs(h).toString(36)}`;
1392
+ }
1393
+ const createSessionHash = (0, import_react6.useMemo)(
1394
+ () => createSessionParams ? hashCreateParams(createSessionParams) : "",
1395
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1396
+ [
1397
+ createSessionParams?.clientId,
1398
+ createSessionParams?.account?.email,
1399
+ createSessionParams?.checkoutMode,
1400
+ JSON.stringify(createSessionParams?.items),
1401
+ JSON.stringify(createSessionParams?.subscriptions)
1402
+ ]
1403
+ );
1404
+ const createSessionParamsRef = (0, import_react6.useRef)(createSessionParams);
1405
+ createSessionParamsRef.current = createSessionParams;
1283
1406
  (0, import_react6.useEffect)(() => {
1284
1407
  let cancelled = false;
1285
- setIsLoading(true);
1286
1408
  setLoadError(null);
1409
+ if (createSessionHash) {
1410
+ if (initializedHashRef.current === createSessionHash) return;
1411
+ const params = createSessionParamsRef.current;
1412
+ const totalAmount = [
1413
+ ...(params.items ?? []).map((i) => i.overrideAmount ?? i.totalAmount ?? 0),
1414
+ ...(params.subscriptions ?? []).map((s) => s.overrideAmount ?? s.totalAmount ?? 0)
1415
+ ].reduce((sum, v) => sum + v, 0);
1416
+ const currency = params.items?.[0]?.currency ?? params.subscriptions?.[0]?.currency ?? "USD";
1417
+ const syntheticSession = {
1418
+ id: "",
1419
+ clientSecret: "",
1420
+ mode: "payment",
1421
+ amount: Math.round(totalAmount * 100),
1422
+ currency,
1423
+ status: "open",
1424
+ customer: {
1425
+ id: params.account.userId,
1426
+ email: params.account.email,
1427
+ firstName: params.account.firstName,
1428
+ lastName: params.account.lastName
1429
+ },
1430
+ successUrl: params.successUrl,
1431
+ cancelUrl: params.cancelUrl,
1432
+ checkoutMode: params.checkoutMode ?? "full",
1433
+ items: (params.items ?? []).map((i, idx) => ({
1434
+ uuid: `synthetic-item-${idx}`,
1435
+ checkoutSessionId: "",
1436
+ providerItemId: i.providerItemId,
1437
+ providerItemName: i.providerItemName ?? i.providerItemId,
1438
+ quantity: i.quantity ?? 1,
1439
+ totalAmount: i.totalAmount,
1440
+ overrideAmount: i.overrideAmount ?? null,
1441
+ currency: i.currency ?? currency
1442
+ })),
1443
+ subscriptions: (params.subscriptions ?? []).map((s, idx) => ({
1444
+ uuid: `synthetic-sub-${idx}`,
1445
+ checkoutSessionId: "",
1446
+ providerPlanId: s.providerPlanId,
1447
+ providerPlanName: s.providerPlanName ?? s.providerPlanId,
1448
+ quantity: s.quantity ?? 1,
1449
+ totalAmount: s.totalAmount,
1450
+ overrideAmount: s.overrideAmount ?? null,
1451
+ currency: s.currency ?? currency
1452
+ }))
1453
+ };
1454
+ setSession(syntheticSession);
1455
+ setCurrentMode(params.checkoutMode ?? checkoutModeProp ?? "full");
1456
+ setIsLoading(false);
1457
+ const cacheKey = createSessionHash;
1458
+ async function resolveSession() {
1459
+ const api = new import_js.PaymentAPI(resolvedBillingUrl);
1460
+ let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
1461
+ let realResult = null;
1462
+ if (sid) {
1463
+ try {
1464
+ realResult = await api.getUnifiedCheckoutSession(sid);
1465
+ if (realResult.data.session?.status === "complete") {
1466
+ if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
1467
+ sid = null;
1468
+ realResult = null;
1469
+ }
1470
+ } catch {
1471
+ if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
1472
+ sid = null;
1473
+ }
1474
+ }
1475
+ if (!sid) {
1476
+ realResult = await api.createAndFetchSession(createSessionParamsRef.current);
1477
+ sid = realResult.data.session?.id ?? "";
1478
+ if (sid && typeof window !== "undefined") {
1479
+ window.sessionStorage.setItem(cacheKey, sid);
1480
+ }
1481
+ }
1482
+ return { sid: sid ?? "", result: realResult };
1483
+ }
1484
+ (async () => {
1485
+ try {
1486
+ let promise = inflightRef.current.get(cacheKey);
1487
+ if (!promise) {
1488
+ promise = resolveSession();
1489
+ inflightRef.current.set(cacheKey, promise);
1490
+ }
1491
+ let resolved;
1492
+ try {
1493
+ resolved = await promise;
1494
+ } finally {
1495
+ inflightRef.current.delete(cacheKey);
1496
+ }
1497
+ if (cancelled) return;
1498
+ const { sid, result: realResult } = resolved;
1499
+ if (realResult) {
1500
+ setUnified(realResult);
1501
+ if (realResult.data.session) setSession(realResult.data.session);
1502
+ }
1503
+ if (sid) {
1504
+ setResolvedSessionId(sid);
1505
+ }
1506
+ let pk;
1507
+ if (realResult?.provider === "stripe") {
1508
+ pk = realResult.data.stripe?.publishableKey;
1509
+ }
1510
+ if (!pk) pk = fallbackPublishableKey;
1511
+ if (!pk) {
1512
+ throw new import_shared5.FloPayError(
1513
+ "No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
1514
+ "validation_error"
1515
+ );
1516
+ }
1517
+ const instance = await (0, import_js.loadFloPay)(pk, { billingApiUrl: resolvedBillingUrl, locale });
1518
+ if (!cancelled) {
1519
+ flopayRef.current = instance;
1520
+ setFloPay(instance);
1521
+ initializedHashRef.current = cacheKey;
1522
+ }
1523
+ } catch (err) {
1524
+ if (cancelled) return;
1525
+ const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(err instanceof Error ? err.message : "Failed to create session", "api_error");
1526
+ setLoadError(floPayErr);
1527
+ }
1528
+ })();
1529
+ return () => {
1530
+ cancelled = true;
1531
+ };
1532
+ }
1533
+ setIsLoading(true);
1287
1534
  async function init() {
1288
1535
  try {
1289
1536
  const api = new import_js.PaymentAPI(resolvedBillingUrl);
1290
- const result = await api.getUnifiedCheckoutSession(sessionId);
1537
+ const result = await api.getUnifiedCheckoutSession(resolvedSessionId);
1291
1538
  if (cancelled) return;
1292
1539
  setUnified(result);
1293
1540
  const sess = result.data.session ?? null;
1294
1541
  setSession(sess);
1295
1542
  if (!sess) {
1296
- throw new import_shared4.FloPayError("No session data returned", "api_error");
1543
+ throw new import_shared5.FloPayError("No session data returned", "api_error");
1297
1544
  }
1298
1545
  if (sess.status === "complete") {
1299
1546
  setIsLoading(false);
@@ -1305,7 +1552,7 @@ function FloPayCheckout({
1305
1552
  const hasPayPalRedirectParams = typeof window !== "undefined" && new URLSearchParams(window.location.search).has("payment_intent");
1306
1553
  if (effectiveMode === "auto" && !autoCheckoutAttempted.current && !hasPayPalRedirectParams) {
1307
1554
  autoCheckoutAttempted.current = true;
1308
- const stripeInitPromise = initStripe(result, sess);
1555
+ const stripeInitPromise = initStripe(result);
1309
1556
  try {
1310
1557
  const redirectResult = await processPaymentForMode(sess);
1311
1558
  if (!redirectResult) {
@@ -1316,9 +1563,7 @@ function FloPayCheckout({
1316
1563
  await stripeInitPromise;
1317
1564
  const handled = await handleRedirectResult(redirectResult, sess, { attempt3DS: true });
1318
1565
  if (!cancelled) {
1319
- if (!handled) {
1320
- setCurrentMode("full");
1321
- }
1566
+ if (!handled) setCurrentMode("full");
1322
1567
  setIsLoading(false);
1323
1568
  }
1324
1569
  return;
@@ -1330,26 +1575,23 @@ function FloPayCheckout({
1330
1575
  return;
1331
1576
  }
1332
1577
  }
1333
- await initStripe(result, sess);
1578
+ await initStripe(result);
1334
1579
  if (!cancelled) setIsLoading(false);
1335
1580
  } catch (err) {
1336
1581
  if (cancelled) return;
1337
- const floPayErr = err instanceof import_shared4.FloPayError ? err : new import_shared4.FloPayError(
1338
- err instanceof Error ? err.message : "Failed to initialize checkout",
1339
- "api_error"
1340
- );
1582
+ const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(err instanceof Error ? err.message : "Failed to initialize checkout", "api_error");
1341
1583
  setLoadError(floPayErr);
1342
1584
  setIsLoading(false);
1343
1585
  }
1344
1586
  }
1345
- async function initStripe(result, _sess) {
1587
+ async function initStripe(result) {
1346
1588
  let publishableKey;
1347
1589
  if (result.provider === "stripe") {
1348
1590
  publishableKey = result.data.stripe?.publishableKey;
1349
1591
  }
1350
1592
  if (!publishableKey) publishableKey = fallbackPublishableKey;
1351
1593
  if (!publishableKey) {
1352
- throw new import_shared4.FloPayError(
1594
+ throw new import_shared5.FloPayError(
1353
1595
  "No publishable key found in session response. Provide a fallbackPublishableKey prop or ensure the session includes gatewayData.publishableKey.",
1354
1596
  "validation_error"
1355
1597
  );
@@ -1365,7 +1607,7 @@ function FloPayCheckout({
1365
1607
  return () => {
1366
1608
  cancelled = true;
1367
1609
  };
1368
- }, [sessionId, resolvedBillingUrl, fallbackPublishableKey, locale, checkoutModeProp]);
1610
+ }, [resolvedSessionId, createSessionHash, resolvedBillingUrl, fallbackPublishableKey, locale, checkoutModeProp]);
1369
1611
  const handleConfirmCheckout = (0, import_react6.useCallback)(async () => {
1370
1612
  if (confirmProcessing || !session) return;
1371
1613
  setConfirmProcessing(true);
@@ -1380,7 +1622,7 @@ function FloPayCheckout({
1380
1622
  setCurrentMode("full");
1381
1623
  }
1382
1624
  } catch (err) {
1383
- const floPayErr = err instanceof import_shared4.FloPayError ? err : new import_shared4.FloPayError(
1625
+ const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(
1384
1626
  err instanceof Error ? err.message : "Payment failed",
1385
1627
  "api_error"
1386
1628
  );
@@ -1401,7 +1643,7 @@ function FloPayCheckout({
1401
1643
  if (unified.provider === "stripe" && unified.data.stripe?.clientSecret) {
1402
1644
  opts.clientSecret = unified.data.stripe.clientSecret;
1403
1645
  } else {
1404
- const displayTotal = (0, import_shared4.buildCheckoutDisplayData)(session).total;
1646
+ const displayTotal = (0, import_shared5.buildCheckoutDisplayData)(session).total;
1405
1647
  opts.amount = Math.round(displayTotal * 100) || session.amount;
1406
1648
  opts.currency = session.currency?.toLowerCase();
1407
1649
  }
@@ -1417,32 +1659,32 @@ function FloPayCheckout({
1417
1659
  [session, isLoading, loadError, currentMode]
1418
1660
  );
1419
1661
  if (isLoading) {
1420
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: loadingNode ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1421
- "div",
1422
- {
1423
- style: {
1424
- display: "flex",
1425
- justifyContent: "center",
1426
- padding: 32
1427
- },
1428
- children: [
1429
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1430
- "div",
1431
- {
1432
- style: {
1433
- width: 24,
1434
- height: 24,
1435
- border: "2px solid #e5e7eb",
1436
- borderTopColor: "#6b7280",
1437
- borderRadius: "50%",
1438
- animation: "spin 0.6s linear infinite"
1439
- }
1440
- }
1441
- ),
1442
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
1443
- ]
1444
- }
1445
- ) });
1662
+ if (loadingNode) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: loadingNode });
1663
+ if (layout === "buttons") {
1664
+ const skeletonBar = (h) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1665
+ height: h,
1666
+ borderRadius: 8,
1667
+ background: "#e5e7eb",
1668
+ animation: "flopay-loading-pulse 1.5s ease-in-out infinite"
1669
+ } });
1670
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
1671
+ showPayPal && skeletonBar(44),
1672
+ (showApplePay || showGooglePay) && skeletonBar(44),
1673
+ skeletonBar(48),
1674
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `@keyframes flopay-loading-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
1675
+ ] });
1676
+ }
1677
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", justifyContent: "center", padding: 32 }, children: [
1678
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1679
+ width: 24,
1680
+ height: 24,
1681
+ border: "2px solid #e5e7eb",
1682
+ borderTopColor: "#6b7280",
1683
+ borderRadius: "50%",
1684
+ animation: "spin 0.6s linear infinite"
1685
+ } }),
1686
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
1687
+ ] });
1446
1688
  }
1447
1689
  if (loadError) {
1448
1690
  if (errorNode) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: errorNode(loadError) });
@@ -1459,6 +1701,19 @@ function FloPayCheckout({
1459
1701
  }
1460
1702
  );
1461
1703
  }
1704
+ if ((!flopay || !providerOptions) && createSessionParams && layout === "buttons") {
1705
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1706
+ InterimButtonsView,
1707
+ {
1708
+ onButtonClick,
1709
+ showPayPal,
1710
+ showApplePay,
1711
+ showGooglePay,
1712
+ buttonsTheme,
1713
+ buttonsStyles
1714
+ }
1715
+ ) });
1716
+ }
1462
1717
  if (!flopay || !providerOptions) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, {});
1463
1718
  if (currentMode === "confirm") {
1464
1719
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(FloPayProvider, { flopay, options: providerOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className, children: [
@@ -1519,7 +1774,7 @@ function FloPayCheckout({
1519
1774
  children ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1520
1775
  SessionInjector,
1521
1776
  {
1522
- sessionId,
1777
+ sessionId: resolvedSessionId,
1523
1778
  billingApiUrl: resolvedBillingUrl,
1524
1779
  session,
1525
1780
  children
@@ -1527,12 +1782,12 @@ function FloPayCheckout({
1527
1782
  ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1528
1783
  SplitCardForm,
1529
1784
  {
1530
- sessionId,
1785
+ sessionId: resolvedSessionId,
1531
1786
  email: session?.customer?.email,
1532
1787
  userId: session?.customer?.id,
1533
1788
  firstName: session?.customer?.firstName,
1534
1789
  lastName: session?.customer?.lastName,
1535
- totalAmount: session ? Math.round((0, import_shared4.buildCheckoutDisplayData)(session).total * 100) : 0,
1790
+ totalAmount: session ? Math.round((0, import_shared5.buildCheckoutDisplayData)(session).total * 100) : 0,
1536
1791
  currency: session?.currency?.toLowerCase() ?? "usd",
1537
1792
  onComplete,
1538
1793
  onError,
@@ -1540,6 +1795,9 @@ function FloPayCheckout({
1540
1795
  showApplePay,
1541
1796
  showGooglePay,
1542
1797
  layout,
1798
+ buttonsTheme,
1799
+ buttonsStyles,
1800
+ onButtonClick,
1543
1801
  submitLabel,
1544
1802
  className
1545
1803
  }
@@ -1570,9 +1828,165 @@ function SessionInjector({
1570
1828
  return import_react6.default.cloneElement(child, injected);
1571
1829
  }) });
1572
1830
  }
1831
+ function InterimButtonsView({
1832
+ onButtonClick,
1833
+ showPayPal,
1834
+ showApplePay,
1835
+ showGooglePay,
1836
+ buttonsTheme,
1837
+ buttonsStyles: stylesOverride
1838
+ }) {
1839
+ const [showCardForm, setShowCardForm] = (0, import_react6.useState)(false);
1840
+ const bStyles = (0, import_react6.useMemo)(() => {
1841
+ const base = (0, import_shared5.resolveButtonsLayoutTheme)(buttonsTheme);
1842
+ if (!stylesOverride) return base;
1843
+ return {
1844
+ ...base,
1845
+ ...stylesOverride,
1846
+ cardButton: { ...base.cardButton, ...stylesOverride.cardButton },
1847
+ cardFormContainer: { ...base.cardFormContainer, ...stylesOverride.cardFormContainer },
1848
+ submitButton: { ...base.submitButton, ...stylesOverride.submitButton },
1849
+ title: { ...base.title, ...stylesOverride.title }
1850
+ };
1851
+ }, [buttonsTheme, stylesOverride]);
1852
+ const skeleton = (h) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1853
+ height: h,
1854
+ borderRadius: 8,
1855
+ background: "#e5e7eb",
1856
+ animation: "flopay-interim-pulse 1.5s ease-in-out infinite"
1857
+ } });
1858
+ if (showCardForm) {
1859
+ const inputBorder = bStyles.cardInputBorder ?? "#e5e7eb";
1860
+ const inputBg = bStyles.cardInputBackground ?? "white";
1861
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
1862
+ backgroundColor: bStyles.cardFormContainer?.backgroundColor ?? "white",
1863
+ borderRadius: "8px",
1864
+ animation: "flopay-interim-expand 0.35s cubic-bezier(0.4, 0, 0.2, 1) both",
1865
+ overflow: "hidden",
1866
+ ...bStyles.cardFormContainer
1867
+ }, children: [
1868
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", alignItems: "center", padding: "0.75rem 0 0.625rem" }, children: [
1869
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1870
+ "button",
1871
+ {
1872
+ type: "button",
1873
+ onClick: () => setShowCardForm(false),
1874
+ style: {
1875
+ display: "inline-flex",
1876
+ alignItems: "center",
1877
+ gap: "0.5rem",
1878
+ background: "none",
1879
+ border: "none",
1880
+ cursor: "pointer",
1881
+ color: "#4b5563",
1882
+ fontSize: "0.85rem",
1883
+ fontWeight: 500,
1884
+ padding: 0,
1885
+ flexShrink: 0,
1886
+ ...bStyles.backButton
1887
+ },
1888
+ children: [
1889
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: {
1890
+ display: "inline-flex",
1891
+ alignItems: "center",
1892
+ justifyContent: "center",
1893
+ width: 28,
1894
+ height: 28,
1895
+ borderRadius: "50%",
1896
+ backgroundColor: "#f3f4f6",
1897
+ ...bStyles.backButtonIcon
1898
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M15 18l-6-6 6-6" }) }) }),
1899
+ "Go back"
1900
+ ]
1901
+ }
1902
+ ),
1903
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1904
+ flex: 1,
1905
+ textAlign: "center",
1906
+ fontWeight: 600,
1907
+ fontSize: "1.05rem",
1908
+ color: "#262833",
1909
+ paddingRight: 80,
1910
+ ...bStyles.title
1911
+ }, children: "Secure card checkout" })
1912
+ ] }),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTopLeftRadius: 8, borderTopRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: "60%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
1914
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex" }, children: [
1915
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { flex: 1, backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTop: "none", borderRight: "none", borderBottomLeftRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: "50%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
1916
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { flex: 1, backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTop: "none", borderBottomRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: "40%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) })
1917
+ ] }),
1918
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderRadius: 8, marginTop: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: "45%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
1919
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1920
+ height: 50,
1921
+ borderRadius: 8,
1922
+ marginTop: 16,
1923
+ background: "#c8c8ff",
1924
+ animation: "flopay-interim-pulse 1.5s ease-in-out infinite",
1925
+ ...bStyles.submitButton,
1926
+ opacity: 0.5
1927
+ } }),
1928
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `
1929
+ @keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
1930
+ @keyframes flopay-interim-expand {
1931
+ 0% { opacity: 0; max-height: 0; transform: translateY(-12px); }
1932
+ 40% { opacity: 1; }
1933
+ 100% { opacity: 1; max-height: 600px; transform: translateY(0); }
1934
+ }
1935
+ ` })
1936
+ ] });
1937
+ }
1938
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
1939
+ showPayPal && skeleton(44),
1940
+ (showApplePay || showGooglePay) && skeleton(44),
1941
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1942
+ "button",
1943
+ {
1944
+ type: "button",
1945
+ onClick: () => {
1946
+ onButtonClick?.("card");
1947
+ setShowCardForm(true);
1948
+ },
1949
+ style: {
1950
+ width: "100%",
1951
+ padding: "0.9rem 1rem",
1952
+ backgroundColor: "white",
1953
+ color: "#262833",
1954
+ border: "1px solid #d1d5db",
1955
+ borderRadius: "8px",
1956
+ fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
1957
+ fontWeight: 600,
1958
+ cursor: "pointer",
1959
+ display: "flex",
1960
+ alignItems: "center",
1961
+ justifyContent: "center",
1962
+ gap: "0.625rem",
1963
+ boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
1964
+ transition: "transform 0.1s",
1965
+ position: "relative",
1966
+ ...bStyles.cardButton
1967
+ },
1968
+ onMouseDown: (e) => {
1969
+ e.currentTarget.style.transform = "scale(0.985)";
1970
+ },
1971
+ onMouseUp: (e) => {
1972
+ e.currentTarget.style.transform = "scale(1)";
1973
+ },
1974
+ children: [
1975
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
1976
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("rect", { width: "20", height: "14", x: "2", y: "5", rx: "2" }),
1977
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("line", { x1: "2", x2: "22", y1: "10", y2: "10" })
1978
+ ] }),
1979
+ "Credit / Debit Card",
1980
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M9 18l6-6-6-6" }) })
1981
+ ]
1982
+ }
1983
+ ),
1984
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `@keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
1985
+ ] });
1986
+ }
1573
1987
 
1574
1988
  // src/checkout-form.tsx
1575
- var import_shared5 = require("@flopay/shared");
1989
+ var import_shared6 = require("@flopay/shared");
1576
1990
  var import_react7 = require("react");
1577
1991
  var import_jsx_runtime5 = require("react/jsx-runtime");
1578
1992
  var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
@@ -1780,7 +2194,7 @@ function CheckoutFormInner({
1780
2194
  return;
1781
2195
  }
1782
2196
  if (!sessionId || !email) {
1783
- throw new import_shared5.FloPayError("Missing sessionId or email", "validation_error");
2197
+ throw new import_shared6.FloPayError("Missing sessionId or email", "validation_error");
1784
2198
  }
1785
2199
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
1786
2200
  method: "POST",
@@ -1792,10 +2206,10 @@ function CheckoutFormInner({
1792
2206
  isPaypal: false
1793
2207
  })
1794
2208
  });
1795
- if (!intentResponse.ok) throw new import_shared5.FloPayError("Failed to create payment intent", "api_error");
2209
+ if (!intentResponse.ok) throw new import_shared6.FloPayError("Failed to create payment intent", "api_error");
1796
2210
  const intentJson = await intentResponse.json();
1797
2211
  const intentClientSecret = intentJson.data?.id;
1798
- if (!intentClientSecret) throw new import_shared5.FloPayError("No client_secret in payment intent response", "api_error");
2212
+ if (!intentClientSecret) throw new import_shared6.FloPayError("No client_secret in payment intent response", "api_error");
1799
2213
  const confirmResult = await flopay.confirmCardPayment({
1800
2214
  clientSecret: intentClientSecret,
1801
2215
  paymentMethodId: pmResult.paymentMethodId
@@ -1850,7 +2264,7 @@ function CheckoutFormInner({
1850
2264
  }
1851
2265
 
1852
2266
  // src/paypal-button.tsx
1853
- var import_shared6 = require("@flopay/shared");
2267
+ var import_shared7 = require("@flopay/shared");
1854
2268
  var import_react8 = require("react");
1855
2269
  var import_jsx_runtime6 = require("react/jsx-runtime");
1856
2270
  function PayPalButton({
@@ -1970,7 +2384,7 @@ function PayPalButton({
1970
2384
  setSubmitting(true);
1971
2385
  onErrorChange?.(null);
1972
2386
  if (!sessionId || !email) {
1973
- throw new import_shared6.FloPayError("Missing sessionId or email for PayPal payment", "validation_error");
2387
+ throw new import_shared7.FloPayError("Missing sessionId or email for PayPal payment", "validation_error");
1974
2388
  }
1975
2389
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
1976
2390
  method: "POST",
@@ -1982,10 +2396,10 @@ function PayPalButton({
1982
2396
  isPaypal: "true"
1983
2397
  })
1984
2398
  });
1985
- if (!intentResponse.ok) throw new import_shared6.FloPayError("Failed to create payment intent", "api_error");
2399
+ if (!intentResponse.ok) throw new import_shared7.FloPayError("Failed to create payment intent", "api_error");
1986
2400
  const intentJson = await intentResponse.json();
1987
2401
  const intentClientSecret = intentJson.data?.id;
1988
- if (!intentClientSecret) throw new import_shared6.FloPayError("No client_secret in response", "api_error");
2402
+ if (!intentClientSecret) throw new import_shared7.FloPayError("No client_secret in response", "api_error");
1989
2403
  const result = await flopay.confirmPayment({
1990
2404
  clientSecret: intentClientSecret,
1991
2405
  returnUrl: window.location.href