@flopay/react 0.3.5 → 0.3.12

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_react7 = __toESM(require("react"), 1);
120
120
  var import_js = require("@flopay/js");
121
- var import_shared5 = require("@flopay/shared");
121
+ var import_shared6 = require("@flopay/shared");
122
122
 
123
123
  // src/card-button-content.tsx
124
124
  var import_react3 = require("react");
@@ -162,10 +162,29 @@ function DefaultCardButtonContent() {
162
162
  )
163
163
  ] });
164
164
  }
165
+ function DefaultBackButtonContent() {
166
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: "Go back" });
167
+ }
168
+ function DefaultTitleContent() {
169
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: "Secure card checkout" });
170
+ }
171
+ function isEmptySlotContent(content) {
172
+ return content !== void 0 && (content === "" || content === null || content === false);
173
+ }
165
174
  function CardButtonContentSlot({
166
175
  content
167
176
  }) {
168
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: content ?? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultCardButtonContent, {}) });
177
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: content === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultCardButtonContent, {}) : content });
178
+ }
179
+ function BackButtonContentSlot({
180
+ content
181
+ }) {
182
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: content === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultBackButtonContent, {}) : content });
183
+ }
184
+ function TitleContentSlot({
185
+ content
186
+ }) {
187
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: content === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultTitleContent, {}) : content });
169
188
  }
170
189
 
171
190
  // src/elements.tsx
@@ -230,7 +249,7 @@ var AddressElement = createElementComponent("address", "AddressElement");
230
249
 
231
250
  // src/split-card-form.tsx
232
251
  var import_react_stripe_js = require("@stripe/react-stripe-js");
233
- var import_shared3 = require("@flopay/shared");
252
+ var import_shared4 = require("@flopay/shared");
234
253
  var import_react6 = require("react");
235
254
 
236
255
  // src/hooks.ts
@@ -252,8 +271,116 @@ function useBillingApiUrl() {
252
271
  return ctx.billingApiUrl || (0, import_shared2.resolveBillingApiUrl)();
253
272
  }
254
273
 
274
+ // src/checkout-utils.ts
275
+ var import_shared3 = require("@flopay/shared");
276
+ function mergeInlineSessionPatches(base, patch) {
277
+ if (!patch) return base;
278
+ if (!base) return patch;
279
+ return {
280
+ account: { ...base.account ?? {}, ...patch.account ?? {} },
281
+ couponCodes: patch.couponCodes ?? base.couponCodes,
282
+ tagsData: {
283
+ ...base.tagsData ?? {},
284
+ ...patch.tagsData ?? {}
285
+ },
286
+ utmMetadata: patch.utmMetadata ?? base.utmMetadata
287
+ };
288
+ }
289
+ function mergeInlineSessionPatch(params, patch) {
290
+ if (!patch) return params;
291
+ return {
292
+ ...params,
293
+ account: {
294
+ ...params.account,
295
+ ...patch.account ?? {}
296
+ },
297
+ couponCodes: patch.couponCodes ?? params.couponCodes,
298
+ tagsData: {
299
+ ...params.tagsData ?? {},
300
+ ...patch.tagsData ?? {}
301
+ },
302
+ utmMetadata: patch.utmMetadata ?? params.utmMetadata
303
+ };
304
+ }
305
+ function buildSyntheticSession(params, checkoutModeOverride) {
306
+ const totalAmount = [
307
+ ...(params.items ?? []).map((item) => item.overrideAmount ?? item.totalAmount ?? 0),
308
+ ...(params.subscriptions ?? []).map((subscription) => subscription.overrideAmount ?? subscription.totalAmount ?? 0)
309
+ ].reduce((sum, value) => sum + value, 0);
310
+ const currency = params.items?.[0]?.currency ?? params.subscriptions?.[0]?.currency ?? "USD";
311
+ return {
312
+ id: "",
313
+ clientSecret: "",
314
+ mode: "payment",
315
+ amount: Math.round(totalAmount * 100),
316
+ currency,
317
+ status: "open",
318
+ customer: {
319
+ id: params.account.userId,
320
+ email: params.account.email ?? "",
321
+ firstName: params.account.firstName,
322
+ lastName: params.account.lastName,
323
+ gender: params.account.gender ?? void 0,
324
+ city: params.account.city ?? void 0,
325
+ state: params.account.state ?? void 0,
326
+ country: params.account.country ?? void 0,
327
+ zip: params.account.zip ?? void 0
328
+ },
329
+ successUrl: params.successUrl,
330
+ cancelUrl: params.cancelUrl,
331
+ checkoutMode: params.checkoutMode ?? checkoutModeOverride ?? "full",
332
+ items: (params.items ?? []).map((item, idx) => ({
333
+ uuid: `synthetic-item-${idx}`,
334
+ checkoutSessionId: "",
335
+ providerItemId: item.providerItemId,
336
+ providerItemName: item.providerItemName ?? item.providerItemId,
337
+ quantity: item.quantity ?? 1,
338
+ totalAmount: item.totalAmount,
339
+ overrideAmount: item.overrideAmount ?? null,
340
+ currency: item.currency ?? currency
341
+ })),
342
+ subscriptions: (params.subscriptions ?? []).map((subscription, idx) => ({
343
+ uuid: `synthetic-sub-${idx}`,
344
+ checkoutSessionId: "",
345
+ providerPlanId: subscription.providerPlanId,
346
+ providerPlanName: subscription.providerPlanName ?? subscription.providerPlanId,
347
+ quantity: subscription.quantity ?? 1,
348
+ totalAmount: subscription.totalAmount,
349
+ overrideAmount: subscription.overrideAmount ?? null,
350
+ currency: subscription.currency ?? currency
351
+ }))
352
+ };
353
+ }
354
+ function ensureInlineSessionReady(params) {
355
+ if (!params.account.email?.trim()) {
356
+ throw new import_shared3.FloPayError(
357
+ "Email is required before continuing with card checkout.",
358
+ "validation_error",
359
+ { param: "createSession.account.email" }
360
+ );
361
+ }
362
+ }
363
+ function mergeAccountPatch(base, patch) {
364
+ if (!patch) return base;
365
+ return {
366
+ ...base,
367
+ ...patch
368
+ };
369
+ }
370
+ function buildDeclineEvent(method, input, overrides) {
371
+ const message = typeof input === "string" ? input : input.message;
372
+ const code = typeof input === "string" ? overrides?.code : overrides?.code ?? input.code;
373
+ const declineCode = typeof input === "string" ? overrides?.declineCode : overrides?.declineCode ?? input.declineCode;
374
+ return {
375
+ method,
376
+ message,
377
+ ...code ? { code } : {},
378
+ ...declineCode ? { declineCode } : {}
379
+ };
380
+ }
381
+
255
382
  // src/split-card-form.tsx
256
- var import_shared4 = require("@flopay/shared");
383
+ var import_shared5 = require("@flopay/shared");
257
384
  var import_jsx_runtime4 = require("react/jsx-runtime");
258
385
  var WALLET_RESUME_KEY = "flopay_wallet_resume";
259
386
  var FLOPAY_KEYFRAMES = `
@@ -391,7 +518,8 @@ function PayPalButtonInner({
391
518
  onTokenizedBody,
392
519
  onErrorChange,
393
520
  isProcessing = false,
394
- onButtonClick
521
+ onButtonClick,
522
+ onDecline
395
523
  }) {
396
524
  const stripe = (0, import_react_stripe_js.useStripe)();
397
525
  const elements = (0, import_react_stripe_js.useElements)();
@@ -411,7 +539,9 @@ function PayPalButtonInner({
411
539
  try {
412
540
  setSubmitting(true);
413
541
  if (redirectStatus === "failed") {
414
- onErrorChange?.("PayPal payment was declined. Please try again.");
542
+ const message = "PayPal payment was declined. Please try again.";
543
+ onErrorChange?.(message);
544
+ onDecline?.(buildDeclineEvent("paypal", message));
415
545
  return;
416
546
  }
417
547
  const { paymentIntent, error } = await stripe.retrievePaymentIntent(clientSecret);
@@ -433,7 +563,9 @@ function PayPalButtonInner({
433
563
  url.searchParams.delete("redirect_status");
434
564
  window.history.replaceState({}, "", url.toString());
435
565
  } else {
436
- onErrorChange?.("PayPal payment was not completed. Please try again.");
566
+ const message = "PayPal payment was not completed. Please try again.";
567
+ onErrorChange?.(message);
568
+ onDecline?.(buildDeclineEvent("paypal", message));
437
569
  }
438
570
  } catch (err) {
439
571
  onErrorChange?.(err instanceof Error ? err.message : "Failed to complete PayPal payment.");
@@ -441,7 +573,7 @@ function PayPalButtonInner({
441
573
  setSubmitting(false);
442
574
  }
443
575
  })();
444
- }, [stripe, onTokenizedBody, onErrorChange]);
576
+ }, [stripe, onTokenizedBody, onErrorChange, onDecline]);
445
577
  const handlePayPalConfirm = (0, import_react6.useCallback)(async (_event) => {
446
578
  if (!stripe || !elements) return;
447
579
  onButtonClick?.("paypal");
@@ -482,7 +614,11 @@ function PayPalButtonInner({
482
614
  redirect: "if_required"
483
615
  });
484
616
  if (confirmError) {
485
- onErrorChange?.(confirmError.message ?? "PayPal payment failed.");
617
+ const message = confirmError.message ?? "PayPal payment failed.";
618
+ onErrorChange?.(message);
619
+ onDecline?.(buildDeclineEvent("paypal", message, {
620
+ code: confirmError.code
621
+ }));
486
622
  return;
487
623
  }
488
624
  const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
@@ -497,7 +633,7 @@ function PayPalButtonInner({
497
633
  } finally {
498
634
  setSubmitting(false);
499
635
  }
500
- }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]);
636
+ }, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]);
501
637
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
502
638
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
503
639
  import_react_stripe_js.ExpressCheckoutElement,
@@ -506,6 +642,9 @@ function PayPalButtonInner({
506
642
  onLoadError: () => {
507
643
  },
508
644
  onConfirm: handlePayPalConfirm,
645
+ onCancel: () => {
646
+ onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
647
+ },
509
648
  options: {
510
649
  buttonType: { paypal: "paypal" },
511
650
  paymentMethods: {
@@ -528,13 +667,15 @@ function WalletButtonInner({
528
667
  showGooglePay = true,
529
668
  onTokenizedBody,
530
669
  onErrorChange,
531
- onButtonClick
670
+ onButtonClick,
671
+ onDecline
532
672
  }) {
533
673
  const stripe = (0, import_react_stripe_js.useStripe)();
534
674
  const elements = (0, import_react_stripe_js.useElements)();
535
675
  const [ready, setReady] = (0, import_react6.useState)(false);
536
676
  const [submitting, setSubmitting] = (0, import_react6.useState)(false);
537
677
  const baseUrl = billingApiUrl.replace(/\/+$/, "");
678
+ const lastWalletMethodRef = (0, import_react6.useRef)("card");
538
679
  const handleWalletConfirm = (0, import_react6.useCallback)(
539
680
  async (_event) => {
540
681
  if (!stripe || !elements) return;
@@ -575,7 +716,12 @@ function WalletButtonInner({
575
716
  { payment_method: paymentMethod.id }
576
717
  );
577
718
  if (confirmError) {
578
- onErrorChange?.(confirmError.message ?? "Wallet payment failed.");
719
+ const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
720
+ const message = confirmError.message ?? "Wallet payment failed.";
721
+ onErrorChange?.(message);
722
+ onDecline?.(buildDeclineEvent(method, message, {
723
+ code: confirmError.code
724
+ }));
579
725
  return;
580
726
  }
581
727
  onTokenizedBody({
@@ -589,7 +735,7 @@ function WalletButtonInner({
589
735
  setSubmitting(false);
590
736
  }
591
737
  },
592
- [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]
738
+ [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]
593
739
  );
594
740
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
595
741
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
@@ -598,7 +744,14 @@ function WalletButtonInner({
598
744
  onReady: () => setReady(true),
599
745
  onLoadError: () => {
600
746
  },
747
+ onClick: (event) => {
748
+ lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
749
+ event.resolve();
750
+ },
601
751
  onConfirm: handleWalletConfirm,
752
+ onCancel: () => {
753
+ onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
754
+ },
602
755
  options: {
603
756
  buttonType: { applePay: "plain", googlePay: "plain" },
604
757
  paymentMethods: {
@@ -623,6 +776,7 @@ function SplitCardFormInner({
623
776
  userId,
624
777
  onComplete,
625
778
  onError,
779
+ onDecline,
626
780
  onTokenizedBody,
627
781
  firstName,
628
782
  lastName,
@@ -642,7 +796,10 @@ function SplitCardFormInner({
642
796
  buttonsTheme,
643
797
  buttonsStyles: buttonsStylesOverride,
644
798
  cardButtonContent,
799
+ cardBackButtonContent,
800
+ cardTitleContent,
645
801
  onButtonClick,
802
+ onBeforeButtonClick,
646
803
  enableAVS = false,
647
804
  avsLayout: avsLayoutProp = "row",
648
805
  country: countryProp,
@@ -651,6 +808,7 @@ function SplitCardFormInner({
651
808
  onZipChange,
652
809
  totalAmount = 0,
653
810
  currency = "usd",
811
+ initialCardOpen = false,
654
812
  innerRef
655
813
  }) {
656
814
  const flopay = useFloPay();
@@ -661,9 +819,10 @@ function SplitCardFormInner({
661
819
  const [is3DSActive, setIs3DSActive] = (0, import_react6.useState)(false);
662
820
  const [selectedCountry, setSelectedCountry] = (0, import_react6.useState)(countryProp ?? "US");
663
821
  const [zipCode, setZipCode] = (0, import_react6.useState)(zipProp ?? "");
822
+ const [accountPatch, setAccountPatch] = (0, import_react6.useState)({});
664
823
  const zipCodeRef = (0, import_react6.useRef)(zipProp ?? "");
665
824
  const selectedCountryRef = (0, import_react6.useRef)(countryProp ?? "US");
666
- const [viewState, setViewState] = (0, import_react6.useState)("buttons");
825
+ const [viewState, setViewState] = (0, import_react6.useState)(initialCardOpen ? "card" : "buttons");
667
826
  const showCardForm = viewState === "expanding" || viewState === "card";
668
827
  const TRANSITION_MS = 280;
669
828
  const expandToCard = (0, import_react6.useCallback)(() => {
@@ -674,6 +833,11 @@ function SplitCardFormInner({
674
833
  setViewState("collapsing");
675
834
  setTimeout(() => setViewState("buttons"), TRANSITION_MS);
676
835
  }, []);
836
+ (0, import_react6.useEffect)(() => {
837
+ if (layout === "buttons" && initialCardOpen) {
838
+ setViewState("card");
839
+ }
840
+ }, [layout, initialCardOpen]);
677
841
  const [fullName, setFullName] = (0, import_react6.useState)("");
678
842
  const [formReady, setFormReady] = (0, import_react6.useState)(false);
679
843
  const [overlayStatus, setOverlayStatus] = (0, import_react6.useState)(null);
@@ -681,7 +845,7 @@ function SplitCardFormInner({
681
845
  const resolvedBillingApiUrl = billingApiUrl || contextBillingUrl;
682
846
  const displayError = externalError ?? error;
683
847
  const bStyles = (0, import_react6.useMemo)(() => {
684
- const base = (0, import_shared3.resolveButtonsLayoutTheme)(buttonsTheme);
848
+ const base = (0, import_shared4.resolveButtonsLayoutTheme)(buttonsTheme);
685
849
  if (!buttonsStylesOverride) return base;
686
850
  return {
687
851
  ...base,
@@ -697,6 +861,14 @@ function SplitCardFormInner({
697
861
  const isSubmitting = externalProcessing ?? processing;
698
862
  const isSelfContained = !onTokenizedBody;
699
863
  const baseUrl = resolvedBillingApiUrl.replace(/\/+$/, "");
864
+ const resolvedAccount = (0, import_react6.useMemo)(() => mergeAccountPatch({
865
+ userId,
866
+ email,
867
+ firstName,
868
+ lastName,
869
+ country: countryProp,
870
+ zip: zipProp
871
+ }, accountPatch), [userId, email, firstName, lastName, countryProp, zipProp, accountPatch]);
700
872
  const stripeInstance = (0, import_react6.useMemo)(() => {
701
873
  if (!flopay) return null;
702
874
  return flopay.getRawProvider();
@@ -722,6 +894,12 @@ function SplitCardFormInner({
722
894
  },
723
895
  [onErrorChange]
724
896
  );
897
+ const emitDecline = (0, import_react6.useCallback)(
898
+ (method, input, overrides) => {
899
+ onDecline?.(buildDeclineEvent(method, input, overrides));
900
+ },
901
+ [onDecline]
902
+ );
725
903
  const showWallets = showApplePay || showGooglePay;
726
904
  const handleNameChange = (0, import_react6.useCallback)((value) => {
727
905
  setFullName(value);
@@ -729,6 +907,30 @@ function SplitCardFormInner({
729
907
  onFirstNameChange?.(parts[0] ?? "");
730
908
  onLastNameChange?.(parts.length > 1 ? parts.slice(1).join(" ") : "");
731
909
  }, [onFirstNameChange, onLastNameChange]);
910
+ const runBeforeCardButtonClick = (0, import_react6.useCallback)(async () => {
911
+ if (!onBeforeButtonClick) return true;
912
+ try {
913
+ const result = await onBeforeButtonClick({
914
+ method: "card",
915
+ sessionId: sessionId || void 0
916
+ });
917
+ if (result === false) {
918
+ return false;
919
+ }
920
+ if (result?.account) {
921
+ setAccountPatch((prev) => ({ ...prev ?? {}, ...result.account }));
922
+ }
923
+ return true;
924
+ } catch (err) {
925
+ const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(
926
+ err instanceof Error ? err.message : "Card before-click hook failed.",
927
+ "validation_error"
928
+ );
929
+ updateError(floPayErr.message);
930
+ onError?.(floPayErr);
931
+ return false;
932
+ }
933
+ }, [onBeforeButtonClick, sessionId, updateError, onError]);
732
934
  const processPaymentInternal = (0, import_react6.useCallback)(
733
935
  async (tokenizedBody) => {
734
936
  if (processingRef.current) return;
@@ -741,16 +943,16 @@ function SplitCardFormInner({
741
943
  method: "POST",
742
944
  headers: {
743
945
  "Content-Type": "application/json",
744
- "x-user-id": userId ?? ""
946
+ "x-user-id": resolvedAccount.userId ?? ""
745
947
  },
746
948
  body: JSON.stringify({
747
949
  sessionId,
748
950
  tokenizedData: tokenizedBody,
749
951
  accountData: {
750
- userId: userId ?? "",
751
- email: email ?? "",
752
- firstName: firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
753
- lastName: lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
952
+ userId: resolvedAccount.userId ?? "",
953
+ email: resolvedAccount.email ?? "",
954
+ firstName: resolvedAccount.firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
955
+ lastName: resolvedAccount.lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
754
956
  ...enableAVS ? { zip: zipCodeRef.current, country: selectedCountryRef.current } : {}
755
957
  },
756
958
  chv
@@ -783,6 +985,7 @@ function SplitCardFormInner({
783
985
  setOverlayStatus("error");
784
986
  updateError(result.error.message);
785
987
  onError?.(result.error);
988
+ emitDecline("card", result.error);
786
989
  return;
787
990
  }
788
991
  if (result.status === "succeeded" || result.status === "processing") {
@@ -825,7 +1028,11 @@ function SplitCardFormInner({
825
1028
  });
826
1029
  if (confirmError) {
827
1030
  setOverlayStatus("error");
828
- updateError(confirmError.message ?? "PayPal payment failed.");
1031
+ const message2 = confirmError.message ?? "PayPal payment failed.";
1032
+ updateError(message2);
1033
+ emitDecline("paypal", message2, {
1034
+ code: confirmError.code
1035
+ });
829
1036
  }
830
1037
  } catch (err) {
831
1038
  setOverlayStatus("error");
@@ -834,7 +1041,12 @@ function SplitCardFormInner({
834
1041
  return;
835
1042
  }
836
1043
  setOverlayStatus("error");
837
- updateError(json?.message ?? "Payment failed. Please try again.");
1044
+ const message = json?.message ?? "Payment failed. Please try again.";
1045
+ updateError(message);
1046
+ emitDecline(tokenizedBody.isPaypal ? "paypal" : "card", message, {
1047
+ code: json?.code ?? json?.gatewayErrorCode,
1048
+ declineCode: json?.declineCode ?? json?.gatewayDeclineReason
1049
+ });
838
1050
  await new Promise((r) => setTimeout(r, 1500));
839
1051
  } catch (err) {
840
1052
  setOverlayStatus("error");
@@ -846,7 +1058,7 @@ function SplitCardFormInner({
846
1058
  processingRef.current = false;
847
1059
  }
848
1060
  },
849
- [baseUrl, sessionId, userId, email, firstName, lastName, fullName, chv, flopay, onComplete, onError, updateError]
1061
+ [baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, onComplete, onError, updateError, emitDecline]
850
1062
  );
851
1063
  const dispatchTokenizedBody = (0, import_react6.useCallback)(
852
1064
  (tokenizedBody) => {
@@ -870,6 +1082,7 @@ function SplitCardFormInner({
870
1082
  if (result.error) {
871
1083
  updateError(result.error.message);
872
1084
  onError?.(result.error);
1085
+ emitDecline("card", result.error);
873
1086
  } else if (result.status === "succeeded" || result.status === "processing") {
874
1087
  dispatchTokenizedBody({
875
1088
  id: result.paymentIntentId,
@@ -883,7 +1096,7 @@ function SplitCardFormInner({
883
1096
  setIs3DSActive(false);
884
1097
  }
885
1098
  }
886
- }), [flopay, dispatchTokenizedBody, onError, updateError]);
1099
+ }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
887
1100
  (0, import_react6.useEffect)(() => {
888
1101
  if (typeof window === "undefined") return;
889
1102
  const stored = localStorage.getItem(WALLET_RESUME_KEY);
@@ -906,14 +1119,16 @@ function SplitCardFormInner({
906
1119
  async (e) => {
907
1120
  e.preventDefault();
908
1121
  if (!flopay || !elements || isSubmitting || processingRef.current) return;
909
- onButtonClick?.("card");
1122
+ if (layout !== "buttons") {
1123
+ onButtonClick?.("card");
1124
+ }
910
1125
  setProcessing(true);
911
1126
  setOverlayStatus("processing");
912
1127
  updateError(null);
913
1128
  let handedOff = false;
914
1129
  try {
915
1130
  if (enableAVS && !zipCodeRef.current.trim()) {
916
- updateError((0, import_shared3.getPostalCodeLabel)(selectedCountryRef.current) + " is required");
1131
+ updateError((0, import_shared4.getPostalCodeLabel)(selectedCountryRef.current) + " is required");
917
1132
  return;
918
1133
  }
919
1134
  const submitResult = await flopay.submitElements();
@@ -934,23 +1149,23 @@ function SplitCardFormInner({
934
1149
  updateError(pmResult.error?.message ?? "Failed to create payment method.");
935
1150
  return;
936
1151
  }
937
- if (!sessionId || !email) {
938
- throw new import_shared4.FloPayError("Missing sessionId or email", "validation_error");
1152
+ if (!sessionId || !resolvedAccount.email) {
1153
+ throw new import_shared5.FloPayError("Missing sessionId or email", "validation_error");
939
1154
  }
940
1155
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
941
1156
  method: "POST",
942
1157
  headers: { "Content-Type": "application/json" },
943
1158
  body: JSON.stringify({
944
1159
  sessionId,
945
- email,
1160
+ email: resolvedAccount.email,
946
1161
  paymentMethodType: pmResult.paymentMethodId,
947
1162
  isPaypal: false
948
1163
  })
949
1164
  });
950
- if (!intentResponse.ok) throw new import_shared4.FloPayError("Failed to create payment intent", "api_error");
1165
+ if (!intentResponse.ok) throw new import_shared5.FloPayError("Failed to create payment intent", "api_error");
951
1166
  const intentJson = await intentResponse.json();
952
1167
  const intentClientSecret = intentJson.data?.id;
953
- if (!intentClientSecret) throw new import_shared4.FloPayError("No client_secret in payment intent response", "api_error");
1168
+ if (!intentClientSecret) throw new import_shared5.FloPayError("No client_secret in payment intent response", "api_error");
954
1169
  const confirmResult = await flopay.confirmCardPayment({
955
1170
  clientSecret: intentClientSecret,
956
1171
  paymentMethodId: pmResult.paymentMethodId
@@ -958,6 +1173,8 @@ function SplitCardFormInner({
958
1173
  if (confirmResult.error) {
959
1174
  setOverlayStatus("error");
960
1175
  updateError(confirmResult.error.message);
1176
+ onError?.(confirmResult.error);
1177
+ emitDecline("card", confirmResult.error);
961
1178
  await new Promise((r) => setTimeout(r, 1500));
962
1179
  return;
963
1180
  }
@@ -978,7 +1195,7 @@ function SplitCardFormInner({
978
1195
  }
979
1196
  }
980
1197
  },
981
- [flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
1198
+ [flopay, elements, isSubmitting, sessionId, resolvedAccount.email, baseUrl, isSelfContained, dispatchTokenizedBody, onButtonClick, onError, updateError, emitDecline, layout]
982
1199
  );
983
1200
  const isReady = flopay !== null && elements !== null;
984
1201
  if (!isReady) {
@@ -988,6 +1205,8 @@ function SplitCardFormInner({
988
1205
  const resolvedBorder = isButtons ? bStyles.cardInputBorder ?? "#e5e7eb" : "#A4A4FF";
989
1206
  const cardBg = isButtons ? bStyles.cardFormContainer?.backgroundColor ?? "white" : "#EDEDFF";
990
1207
  const cardInputBg = isButtons ? bStyles.cardInputBackground ?? "white" : "white";
1208
+ const hideBackButtonLabel = isEmptySlotContent(cardBackButtonContent);
1209
+ const hideTitle = isEmptySlotContent(cardTitleContent);
991
1210
  const stripeElementStyle = isButtons && (bStyles.cardInputColor || bStyles.cardInputPlaceholderColor || bStyles.cardInputFontSize) ? {
992
1211
  style: {
993
1212
  base: {
@@ -1018,7 +1237,7 @@ function SplitCardFormInner({
1018
1237
  style: {
1019
1238
  display: "inline-flex",
1020
1239
  alignItems: "center",
1021
- gap: "0.5rem",
1240
+ gap: hideBackButtonLabel ? 0 : "0.5rem",
1022
1241
  background: "none",
1023
1242
  border: "none",
1024
1243
  cursor: "pointer",
@@ -1043,11 +1262,11 @@ function SplitCardFormInner({
1043
1262
  transition: "background-color 0.15s",
1044
1263
  ...bStyles.backButtonIcon
1045
1264
  }, 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" }) }) }),
1046
- "Go back"
1265
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BackButtonContentSlot, { content: cardBackButtonContent })
1047
1266
  ]
1048
1267
  }
1049
1268
  ),
1050
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1269
+ hideTitle ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { flex: 1 } }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1051
1270
  flex: 1,
1052
1271
  textAlign: "center",
1053
1272
  fontWeight: 600,
@@ -1055,9 +1274,9 @@ function SplitCardFormInner({
1055
1274
  color: "#262833",
1056
1275
  paddingRight: 80,
1057
1276
  ...bStyles.title
1058
- }, children: "Secure card checkout" })
1277
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TitleContentSlot, { content: cardTitleContent }) })
1059
1278
  ] }),
1060
- !isButtons && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { textAlign: "center", fontWeight: 600, fontSize: "1.1rem", padding: "0.5rem 0", color: "#262833" }, children: "Secure card checkout" }),
1279
+ !isButtons && !hideTitle && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { textAlign: "center", fontWeight: 600, fontSize: "1.1rem", padding: "0.5rem 0", color: "#262833" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TitleContentSlot, { content: cardTitleContent }) }),
1061
1280
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
1062
1281
  backgroundColor: cardInputBg,
1063
1282
  border: `1px solid ${resolvedBorder}`,
@@ -1147,7 +1366,7 @@ function SplitCardFormInner({
1147
1366
  cursor: "pointer",
1148
1367
  ...isButtons && bStyles.nameInput ? bStyles.nameInput : {}
1149
1368
  },
1150
- children: import_shared3.COUNTRY_OPTIONS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("option", { value: c.code, children: [
1369
+ children: import_shared4.COUNTRY_OPTIONS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("option", { value: c.code, children: [
1151
1370
  c.flag,
1152
1371
  " ",
1153
1372
  c.name
@@ -1164,7 +1383,7 @@ function SplitCardFormInner({
1164
1383
  }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1165
1384
  "input",
1166
1385
  {
1167
- placeholder: (0, import_shared3.getPostalCodeLabel)(selectedCountry),
1386
+ placeholder: (0, import_shared4.getPostalCodeLabel)(selectedCountry),
1168
1387
  autoComplete: "postal-code",
1169
1388
  value: zipCode,
1170
1389
  onChange: (e) => {
@@ -1261,32 +1480,36 @@ function SplitCardFormInner({
1261
1480
  PayPalButtonInner,
1262
1481
  {
1263
1482
  sessionId,
1264
- email,
1483
+ email: resolvedAccount.email,
1265
1484
  billingApiUrl: resolvedBillingApiUrl,
1266
1485
  onTokenizedBody: dispatchTokenizedBody,
1267
1486
  onErrorChange: updateError,
1268
1487
  isProcessing: isSubmitting,
1269
- onButtonClick
1488
+ onButtonClick,
1489
+ onDecline
1270
1490
  }
1271
1491
  ) }) : showPayPal ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1272
1492
  showWallets && stripeInstance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1273
1493
  WalletButtonInner,
1274
1494
  {
1275
1495
  sessionId,
1276
- email,
1496
+ email: resolvedAccount.email,
1277
1497
  billingApiUrl: resolvedBillingApiUrl,
1278
1498
  showApplePay,
1279
1499
  showGooglePay,
1280
1500
  onTokenizedBody: dispatchTokenizedBody,
1281
1501
  onErrorChange: updateError,
1282
- onButtonClick
1502
+ onButtonClick,
1503
+ onDecline
1283
1504
  }
1284
1505
  ) }) : showWallets ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
1285
1506
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1286
1507
  "button",
1287
1508
  {
1288
1509
  type: "button",
1289
- onClick: () => {
1510
+ onClick: async () => {
1511
+ const shouldContinue = await runBeforeCardButtonClick();
1512
+ if (!shouldContinue) return;
1290
1513
  onButtonClick?.("card");
1291
1514
  expandToCard();
1292
1515
  },
@@ -1351,23 +1574,25 @@ function SplitCardFormInner({
1351
1574
  WalletButtonInner,
1352
1575
  {
1353
1576
  sessionId,
1354
- email,
1577
+ email: resolvedAccount.email,
1355
1578
  billingApiUrl: resolvedBillingApiUrl,
1356
1579
  showApplePay,
1357
1580
  showGooglePay,
1358
1581
  onTokenizedBody: dispatchTokenizedBody,
1359
- onErrorChange: updateError
1582
+ onErrorChange: updateError,
1583
+ onDecline
1360
1584
  }
1361
1585
  ) }),
1362
1586
  showPayPal && stripeInstance && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1363
1587
  PayPalButtonInner,
1364
1588
  {
1365
1589
  sessionId,
1366
- email,
1590
+ email: resolvedAccount.email,
1367
1591
  billingApiUrl: resolvedBillingApiUrl,
1368
1592
  onTokenizedBody: dispatchTokenizedBody,
1369
1593
  onErrorChange: updateError,
1370
- isProcessing: isSubmitting
1594
+ isProcessing: isSubmitting,
1595
+ onDecline
1371
1596
  }
1372
1597
  ) }),
1373
1598
  (showWallets && stripeInstance || showPayPal && stripeInstance) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
@@ -1399,6 +1624,7 @@ function FloPayCheckout({
1399
1624
  error: errorNode,
1400
1625
  onComplete,
1401
1626
  onError,
1627
+ onDecline,
1402
1628
  showPayPal = true,
1403
1629
  showApplePay = true,
1404
1630
  showGooglePay = true,
@@ -1406,7 +1632,10 @@ function FloPayCheckout({
1406
1632
  buttonsTheme,
1407
1633
  buttonsStyles,
1408
1634
  cardButtonContent,
1635
+ cardBackButtonContent,
1636
+ cardTitleContent,
1409
1637
  onButtonClick,
1638
+ onBeforeButtonClick,
1410
1639
  enableAVS,
1411
1640
  avsLayout,
1412
1641
  submitLabel,
@@ -1417,7 +1646,7 @@ function FloPayCheckout({
1417
1646
  renderConfirmButton,
1418
1647
  onSessionCompleted
1419
1648
  }) {
1420
- const resolvedBillingUrl = (0, import_shared5.resolveBillingApiUrl)(billingApiUrl);
1649
+ const resolvedBillingUrl = (0, import_shared6.resolveBillingApiUrl)(billingApiUrl);
1421
1650
  const [unified, setUnified] = (0, import_react7.useState)(null);
1422
1651
  const [flopay, setFloPay] = (0, import_react7.useState)(null);
1423
1652
  const flopayRef = (0, import_react7.useRef)(null);
@@ -1428,13 +1657,28 @@ function FloPayCheckout({
1428
1657
  const [currentMode, setCurrentMode] = (0, import_react7.useState)("full");
1429
1658
  const [confirmProcessing, setConfirmProcessing] = (0, import_react7.useState)(false);
1430
1659
  const [modeError, setModeError] = (0, import_react7.useState)(null);
1660
+ const [createSessionPatch, setCreateSessionPatch] = (0, import_react7.useState)(void 0);
1661
+ const [cardBootstrapPending, setCardBootstrapPending] = (0, import_react7.useState)(false);
1662
+ const [deferredCardOpen, setDeferredCardOpen] = (0, import_react7.useState)(false);
1431
1663
  const autoCheckoutAttempted = (0, import_react7.useRef)(false);
1432
1664
  const onCompleteRef = (0, import_react7.useRef)(onComplete);
1433
1665
  onCompleteRef.current = onComplete;
1434
1666
  const onErrorRef = (0, import_react7.useRef)(onError);
1435
1667
  onErrorRef.current = onError;
1668
+ const onDeclineRef = (0, import_react7.useRef)(onDecline);
1669
+ onDeclineRef.current = onDecline;
1436
1670
  const onSessionCompletedRef = (0, import_react7.useRef)(onSessionCompleted);
1437
1671
  onSessionCompletedRef.current = onSessionCompleted;
1672
+ const effectiveCreateSession = (0, import_react7.useMemo)(
1673
+ () => createSessionParams ? mergeInlineSessionPatch(createSessionParams, createSessionPatch) : void 0,
1674
+ [createSessionParams, createSessionPatch]
1675
+ );
1676
+ const emitDecline = (0, import_react7.useCallback)(
1677
+ (method, input, overrides) => {
1678
+ onDeclineRef.current?.(buildDeclineEvent(method, input, overrides));
1679
+ },
1680
+ []
1681
+ );
1438
1682
  const processPaymentForMode = (0, import_react7.useCallback)(
1439
1683
  async (sess) => {
1440
1684
  const baseUrl = resolvedBillingUrl.replace(/\/+$/, "");
@@ -1474,9 +1718,13 @@ function FloPayCheckout({
1474
1718
  // No client secret available
1475
1719
  };
1476
1720
  }
1477
- throw new import_shared5.FloPayError(
1721
+ throw new import_shared6.FloPayError(
1478
1722
  json?.message ?? "Payment failed. Please try again.",
1479
- "api_error"
1723
+ "api_error",
1724
+ {
1725
+ code: json?.code ?? json?.gatewayErrorCode,
1726
+ declineCode: json?.declineCode ?? json?.gatewayDeclineReason
1727
+ }
1480
1728
  );
1481
1729
  },
1482
1730
  [resolvedBillingUrl, resolvedSessionId]
@@ -1495,6 +1743,9 @@ function FloPayCheckout({
1495
1743
  });
1496
1744
  if (nextActionError) {
1497
1745
  setModeError(nextActionError.message ?? "3DS authentication failed.");
1746
+ emitDecline("card", nextActionError.message ?? "3DS authentication failed.", {
1747
+ code: nextActionError.code
1748
+ });
1498
1749
  return false;
1499
1750
  }
1500
1751
  if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded")) {
@@ -1517,6 +1768,9 @@ function FloPayCheckout({
1517
1768
  });
1518
1769
  if (error) {
1519
1770
  setModeError(error.message ?? "PayPal authorization failed.");
1771
+ emitDecline("paypal", error.message ?? "PayPal authorization failed.", {
1772
+ code: error.code
1773
+ });
1520
1774
  return false;
1521
1775
  }
1522
1776
  onCompleteRef.current?.({ status: "succeeded" });
@@ -1524,16 +1778,24 @@ function FloPayCheckout({
1524
1778
  }
1525
1779
  return false;
1526
1780
  },
1527
- [resolvedBillingUrl, resolvedSessionId]
1781
+ [resolvedBillingUrl, resolvedSessionId, emitDecline]
1528
1782
  );
1529
1783
  const inflightRef = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
1530
1784
  const initializedHashRef = (0, import_react7.useRef)(null);
1785
+ const deferInlineSessionUntilCardClick = Boolean(
1786
+ effectiveCreateSession && !children && layout === "buttons" && onBeforeButtonClick && (effectiveCreateSession.checkoutMode ?? checkoutModeProp ?? "full") === "full"
1787
+ );
1531
1788
  function hashCreateParams(params) {
1532
1789
  const key = JSON.stringify({
1533
1790
  c: params?.clientId,
1791
+ successUrl: params?.successUrl,
1792
+ cancelUrl: params?.cancelUrl,
1534
1793
  i: params?.items?.map((x) => `${x.providerItemId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
1535
1794
  s: params?.subscriptions?.map((x) => `${x.providerPlanId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
1536
- e: params?.account.email,
1795
+ account: params?.account,
1796
+ couponCodes: params?.couponCodes,
1797
+ tagsData: params?.tagsData,
1798
+ utmMetadata: params?.utmMetadata,
1537
1799
  m: params?.checkoutMode ?? "full"
1538
1800
  });
1539
1801
  let h = 0;
@@ -1543,138 +1805,167 @@ function FloPayCheckout({
1543
1805
  return `flopay_session_${Math.abs(h).toString(36)}`;
1544
1806
  }
1545
1807
  const createSessionHash = (0, import_react7.useMemo)(
1546
- () => createSessionParams ? hashCreateParams(createSessionParams) : "",
1547
- // eslint-disable-next-line react-hooks/exhaustive-deps
1548
- [
1549
- createSessionParams?.clientId,
1550
- createSessionParams?.account?.email,
1551
- createSessionParams?.checkoutMode,
1552
- JSON.stringify(createSessionParams?.items),
1553
- JSON.stringify(createSessionParams?.subscriptions)
1554
- ]
1808
+ () => effectiveCreateSession ? hashCreateParams(effectiveCreateSession) : "",
1809
+ [effectiveCreateSession]
1810
+ );
1811
+ const createSessionParamsRef = (0, import_react7.useRef)(effectiveCreateSession);
1812
+ createSessionParamsRef.current = effectiveCreateSession;
1813
+ (0, import_react7.useEffect)(() => {
1814
+ setResolvedSessionId(sessionIdProp ?? "");
1815
+ }, [sessionIdProp]);
1816
+ async function resolveInlineSession(params, cacheKey) {
1817
+ ensureInlineSessionReady(params);
1818
+ const api = new import_js.PaymentAPI(resolvedBillingUrl);
1819
+ let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
1820
+ let realResult = null;
1821
+ if (sid) {
1822
+ try {
1823
+ realResult = await api.getUnifiedCheckoutSession(sid);
1824
+ if (realResult.data.session?.status === "complete") {
1825
+ if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
1826
+ sid = null;
1827
+ realResult = null;
1828
+ }
1829
+ } catch {
1830
+ if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
1831
+ sid = null;
1832
+ }
1833
+ }
1834
+ if (!sid) {
1835
+ realResult = await api.createAndFetchSession(params);
1836
+ sid = realResult.data.session?.id ?? "";
1837
+ if (sid && typeof window !== "undefined") {
1838
+ window.sessionStorage.setItem(cacheKey, sid);
1839
+ }
1840
+ }
1841
+ return { sid: sid ?? "", result: realResult };
1842
+ }
1843
+ const bootstrapInlineSession = (0, import_react7.useCallback)(
1844
+ async (patch) => {
1845
+ const baseParams = createSessionParamsRef.current;
1846
+ if (!baseParams) {
1847
+ throw new import_shared6.FloPayError("createSession is required to bootstrap checkout.", "validation_error");
1848
+ }
1849
+ const mergedParams = mergeInlineSessionPatch(baseParams, patch);
1850
+ const cacheKey = hashCreateParams(mergedParams);
1851
+ let promise = inflightRef.current.get(cacheKey);
1852
+ if (!promise) {
1853
+ promise = resolveInlineSession(mergedParams, cacheKey);
1854
+ inflightRef.current.set(cacheKey, promise);
1855
+ }
1856
+ let resolved;
1857
+ try {
1858
+ resolved = await promise;
1859
+ } finally {
1860
+ inflightRef.current.delete(cacheKey);
1861
+ }
1862
+ if (patch) {
1863
+ setCreateSessionPatch((prev) => mergeInlineSessionPatches(prev, patch));
1864
+ }
1865
+ const { sid, result: realResult } = resolved;
1866
+ setUnified(realResult);
1867
+ if (realResult.data.session) {
1868
+ setSession(realResult.data.session);
1869
+ }
1870
+ if (sid) {
1871
+ setResolvedSessionId(sid);
1872
+ }
1873
+ let publishableKey;
1874
+ if (realResult.provider === "stripe") {
1875
+ publishableKey = realResult.data.stripe?.publishableKey;
1876
+ }
1877
+ if (!publishableKey) publishableKey = fallbackPublishableKey;
1878
+ if (!publishableKey) {
1879
+ throw new import_shared6.FloPayError(
1880
+ "No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
1881
+ "validation_error"
1882
+ );
1883
+ }
1884
+ const instance = await (0, import_js.loadFloPay)(publishableKey, {
1885
+ billingApiUrl: resolvedBillingUrl,
1886
+ locale
1887
+ });
1888
+ flopayRef.current = instance;
1889
+ setFloPay(instance);
1890
+ initializedHashRef.current = cacheKey;
1891
+ setIsLoading(false);
1892
+ return resolved;
1893
+ },
1894
+ [fallbackPublishableKey, locale, resolvedBillingUrl]
1555
1895
  );
1556
- const createSessionParamsRef = (0, import_react7.useRef)(createSessionParams);
1557
- createSessionParamsRef.current = createSessionParams;
1896
+ const handleDeferredCardButtonClick = (0, import_react7.useCallback)(async () => {
1897
+ if (cardBootstrapPending) return;
1898
+ setLoadError(null);
1899
+ setCardBootstrapPending(true);
1900
+ try {
1901
+ const beforeClickResult = await onBeforeButtonClick?.({
1902
+ method: "card",
1903
+ createSession: effectiveCreateSession
1904
+ });
1905
+ if (beforeClickResult === false) {
1906
+ setDeferredCardOpen(false);
1907
+ return;
1908
+ }
1909
+ const patch = beforeClickResult && typeof beforeClickResult === "object" ? beforeClickResult : void 0;
1910
+ const baseParams = createSessionParamsRef.current;
1911
+ if (!baseParams) {
1912
+ throw new import_shared6.FloPayError(
1913
+ "createSession is required to bootstrap checkout.",
1914
+ "validation_error"
1915
+ );
1916
+ }
1917
+ ensureInlineSessionReady(mergeInlineSessionPatch(baseParams, patch));
1918
+ setDeferredCardOpen(true);
1919
+ onButtonClick?.("card");
1920
+ await bootstrapInlineSession(patch);
1921
+ } catch (err) {
1922
+ setDeferredCardOpen(false);
1923
+ const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(
1924
+ err instanceof Error ? err.message : "Failed to start card checkout.",
1925
+ "api_error"
1926
+ );
1927
+ setLoadError(floPayErr);
1928
+ onErrorRef.current?.(floPayErr);
1929
+ } finally {
1930
+ setCardBootstrapPending(false);
1931
+ }
1932
+ }, [
1933
+ bootstrapInlineSession,
1934
+ cardBootstrapPending,
1935
+ effectiveCreateSession,
1936
+ onButtonClick,
1937
+ onBeforeButtonClick
1938
+ ]);
1558
1939
  (0, import_react7.useEffect)(() => {
1559
1940
  let cancelled = false;
1560
1941
  setLoadError(null);
1561
1942
  if (createSessionHash) {
1943
+ if (deferInlineSessionUntilCardClick) {
1944
+ if (initializedHashRef.current !== createSessionHash) {
1945
+ setSession(null);
1946
+ setUnified(null);
1947
+ setFloPay(null);
1948
+ setResolvedSessionId("");
1949
+ setDeferredCardOpen(false);
1950
+ flopayRef.current = null;
1951
+ }
1952
+ setCurrentMode(createSessionParamsRef.current?.checkoutMode ?? checkoutModeProp ?? "full");
1953
+ setIsLoading(false);
1954
+ return () => {
1955
+ cancelled = true;
1956
+ };
1957
+ }
1562
1958
  if (initializedHashRef.current === createSessionHash) return;
1563
1959
  const params = createSessionParamsRef.current;
1564
- const totalAmount = [
1565
- ...(params.items ?? []).map((i) => i.overrideAmount ?? i.totalAmount ?? 0),
1566
- ...(params.subscriptions ?? []).map((s) => s.overrideAmount ?? s.totalAmount ?? 0)
1567
- ].reduce((sum, v) => sum + v, 0);
1568
- const currency = params.items?.[0]?.currency ?? params.subscriptions?.[0]?.currency ?? "USD";
1569
- const syntheticSession = {
1570
- id: "",
1571
- clientSecret: "",
1572
- mode: "payment",
1573
- amount: Math.round(totalAmount * 100),
1574
- currency,
1575
- status: "open",
1576
- customer: {
1577
- id: params.account.userId,
1578
- email: params.account.email,
1579
- firstName: params.account.firstName,
1580
- lastName: params.account.lastName
1581
- },
1582
- successUrl: params.successUrl,
1583
- cancelUrl: params.cancelUrl,
1584
- checkoutMode: params.checkoutMode ?? "full",
1585
- items: (params.items ?? []).map((i, idx) => ({
1586
- uuid: `synthetic-item-${idx}`,
1587
- checkoutSessionId: "",
1588
- providerItemId: i.providerItemId,
1589
- providerItemName: i.providerItemName ?? i.providerItemId,
1590
- quantity: i.quantity ?? 1,
1591
- totalAmount: i.totalAmount,
1592
- overrideAmount: i.overrideAmount ?? null,
1593
- currency: i.currency ?? currency
1594
- })),
1595
- subscriptions: (params.subscriptions ?? []).map((s, idx) => ({
1596
- uuid: `synthetic-sub-${idx}`,
1597
- checkoutSessionId: "",
1598
- providerPlanId: s.providerPlanId,
1599
- providerPlanName: s.providerPlanName ?? s.providerPlanId,
1600
- quantity: s.quantity ?? 1,
1601
- totalAmount: s.totalAmount,
1602
- overrideAmount: s.overrideAmount ?? null,
1603
- currency: s.currency ?? currency
1604
- }))
1605
- };
1606
- setSession(syntheticSession);
1960
+ setSession(buildSyntheticSession(params, checkoutModeProp));
1607
1961
  setCurrentMode(params.checkoutMode ?? checkoutModeProp ?? "full");
1608
1962
  setIsLoading(false);
1609
- const cacheKey = createSessionHash;
1610
- async function resolveSession() {
1611
- const api = new import_js.PaymentAPI(resolvedBillingUrl);
1612
- let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
1613
- let realResult = null;
1614
- if (sid) {
1615
- try {
1616
- realResult = await api.getUnifiedCheckoutSession(sid);
1617
- if (realResult.data.session?.status === "complete") {
1618
- if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
1619
- sid = null;
1620
- realResult = null;
1621
- }
1622
- } catch {
1623
- if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
1624
- sid = null;
1625
- }
1626
- }
1627
- if (!sid) {
1628
- realResult = await api.createAndFetchSession(createSessionParamsRef.current);
1629
- sid = realResult.data.session?.id ?? "";
1630
- if (sid && typeof window !== "undefined") {
1631
- window.sessionStorage.setItem(cacheKey, sid);
1632
- }
1633
- }
1634
- return { sid: sid ?? "", result: realResult };
1635
- }
1636
1963
  (async () => {
1637
1964
  try {
1638
- let promise = inflightRef.current.get(cacheKey);
1639
- if (!promise) {
1640
- promise = resolveSession();
1641
- inflightRef.current.set(cacheKey, promise);
1642
- }
1643
- let resolved;
1644
- try {
1645
- resolved = await promise;
1646
- } finally {
1647
- inflightRef.current.delete(cacheKey);
1648
- }
1649
- if (cancelled) return;
1650
- const { sid, result: realResult } = resolved;
1651
- if (realResult) {
1652
- setUnified(realResult);
1653
- if (realResult.data.session) setSession(realResult.data.session);
1654
- }
1655
- if (sid) {
1656
- setResolvedSessionId(sid);
1657
- }
1658
- let pk;
1659
- if (realResult?.provider === "stripe") {
1660
- pk = realResult.data.stripe?.publishableKey;
1661
- }
1662
- if (!pk) pk = fallbackPublishableKey;
1663
- if (!pk) {
1664
- throw new import_shared5.FloPayError(
1665
- "No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
1666
- "validation_error"
1667
- );
1668
- }
1669
- const instance = await (0, import_js.loadFloPay)(pk, { billingApiUrl: resolvedBillingUrl, locale });
1670
- if (!cancelled) {
1671
- flopayRef.current = instance;
1672
- setFloPay(instance);
1673
- initializedHashRef.current = cacheKey;
1674
- }
1965
+ await bootstrapInlineSession();
1675
1966
  } catch (err) {
1676
1967
  if (cancelled) return;
1677
- const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(err instanceof Error ? err.message : "Failed to create session", "api_error");
1968
+ const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(err instanceof Error ? err.message : "Failed to create session", "api_error");
1678
1969
  setLoadError(floPayErr);
1679
1970
  }
1680
1971
  })();
@@ -1692,7 +1983,7 @@ function FloPayCheckout({
1692
1983
  const sess = result.data.session ?? null;
1693
1984
  setSession(sess);
1694
1985
  if (!sess) {
1695
- throw new import_shared5.FloPayError("No session data returned", "api_error");
1986
+ throw new import_shared6.FloPayError("No session data returned", "api_error");
1696
1987
  }
1697
1988
  if (sess.status === "complete") {
1698
1989
  setIsLoading(false);
@@ -1731,7 +2022,7 @@ function FloPayCheckout({
1731
2022
  if (!cancelled) setIsLoading(false);
1732
2023
  } catch (err) {
1733
2024
  if (cancelled) return;
1734
- const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(err instanceof Error ? err.message : "Failed to initialize checkout", "api_error");
2025
+ const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(err instanceof Error ? err.message : "Failed to initialize checkout", "api_error");
1735
2026
  setLoadError(floPayErr);
1736
2027
  setIsLoading(false);
1737
2028
  }
@@ -1743,7 +2034,7 @@ function FloPayCheckout({
1743
2034
  }
1744
2035
  if (!publishableKey) publishableKey = fallbackPublishableKey;
1745
2036
  if (!publishableKey) {
1746
- throw new import_shared5.FloPayError(
2037
+ throw new import_shared6.FloPayError(
1747
2038
  "No publishable key found in session response. Provide a fallbackPublishableKey prop or ensure the session includes gatewayData.publishableKey.",
1748
2039
  "validation_error"
1749
2040
  );
@@ -1759,7 +2050,7 @@ function FloPayCheckout({
1759
2050
  return () => {
1760
2051
  cancelled = true;
1761
2052
  };
1762
- }, [resolvedSessionId, createSessionHash, resolvedBillingUrl, fallbackPublishableKey, locale, checkoutModeProp]);
2053
+ }, [resolvedSessionId, createSessionHash, checkoutModeProp, deferInlineSessionUntilCardClick, bootstrapInlineSession]);
1763
2054
  const handleConfirmCheckout = (0, import_react7.useCallback)(async () => {
1764
2055
  if (confirmProcessing || !session) return;
1765
2056
  setConfirmProcessing(true);
@@ -1774,17 +2065,18 @@ function FloPayCheckout({
1774
2065
  setCurrentMode("full");
1775
2066
  }
1776
2067
  } catch (err) {
1777
- const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(
2068
+ const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(
1778
2069
  err instanceof Error ? err.message : "Payment failed",
1779
2070
  "api_error"
1780
2071
  );
1781
2072
  setModeError(floPayErr.message);
1782
2073
  onError?.(floPayErr);
2074
+ emitDecline("card", floPayErr);
1783
2075
  setCurrentMode("full");
1784
2076
  } finally {
1785
2077
  setConfirmProcessing(false);
1786
2078
  }
1787
- }, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError]);
2079
+ }, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError, emitDecline]);
1788
2080
  const providerOptions = (0, import_react7.useMemo)(() => {
1789
2081
  if (!unified || !session) return void 0;
1790
2082
  const opts = {
@@ -1795,7 +2087,7 @@ function FloPayCheckout({
1795
2087
  if (unified.provider === "stripe" && unified.data.stripe?.clientSecret) {
1796
2088
  opts.clientSecret = unified.data.stripe.clientSecret;
1797
2089
  } else {
1798
- const displayTotal = (0, import_shared5.buildCheckoutDisplayData)(session).total;
2090
+ const displayTotal = (0, import_shared6.buildCheckoutDisplayData)(session).total;
1799
2091
  opts.amount = Math.round(displayTotal * 100) || session.amount;
1800
2092
  opts.currency = session.currency?.toLowerCase();
1801
2093
  }
@@ -1810,6 +2102,8 @@ function FloPayCheckout({
1810
2102
  }),
1811
2103
  [session, isLoading, loadError, currentMode]
1812
2104
  );
2105
+ const shouldShowInterimButtons = Boolean(createSessionParams) && layout === "buttons" && (!flopay || !providerOptions);
2106
+ const shouldKeepDeferredInterimVisible = shouldShowInterimButtons && deferInlineSessionUntilCardClick;
1813
2107
  if (isLoading) {
1814
2108
  if (loadingNode) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: loadingNode });
1815
2109
  if (layout === "buttons") {
@@ -1838,7 +2132,7 @@ function FloPayCheckout({
1838
2132
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
1839
2133
  ] });
1840
2134
  }
1841
- if (loadError) {
2135
+ if (loadError && !shouldKeepDeferredInterimVisible) {
1842
2136
  if (errorNode) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: errorNode(loadError) });
1843
2137
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1844
2138
  "div",
@@ -1853,17 +2147,23 @@ function FloPayCheckout({
1853
2147
  }
1854
2148
  );
1855
2149
  }
1856
- if ((!flopay || !providerOptions) && createSessionParams && layout === "buttons") {
2150
+ if (shouldShowInterimButtons) {
1857
2151
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1858
2152
  InterimButtonsView,
1859
2153
  {
1860
2154
  onButtonClick,
1861
- showPayPal,
1862
- showApplePay,
1863
- showGooglePay,
2155
+ onCardButtonClick: deferInlineSessionUntilCardClick ? handleDeferredCardButtonClick : void 0,
2156
+ cardLoading: cardBootstrapPending,
2157
+ cardOpen: deferInlineSessionUntilCardClick ? deferredCardOpen : void 0,
2158
+ errorMessage: shouldKeepDeferredInterimVisible ? loadError?.message ?? null : null,
2159
+ showPayPal: deferInlineSessionUntilCardClick ? false : showPayPal,
2160
+ showApplePay: deferInlineSessionUntilCardClick ? false : showApplePay,
2161
+ showGooglePay: deferInlineSessionUntilCardClick ? false : showGooglePay,
1864
2162
  buttonsTheme,
1865
2163
  buttonsStyles,
1866
- cardButtonContent
2164
+ cardButtonContent,
2165
+ cardBackButtonContent,
2166
+ cardTitleContent
1867
2167
  }
1868
2168
  ) });
1869
2169
  }
@@ -1940,10 +2240,11 @@ function FloPayCheckout({
1940
2240
  userId: session?.customer?.id,
1941
2241
  firstName: session?.customer?.firstName,
1942
2242
  lastName: session?.customer?.lastName,
1943
- totalAmount: session ? Math.round((0, import_shared5.buildCheckoutDisplayData)(session).total * 100) : 0,
2243
+ totalAmount: session ? Math.round((0, import_shared6.buildCheckoutDisplayData)(session).total * 100) : 0,
1944
2244
  currency: session?.currency?.toLowerCase() ?? "usd",
1945
2245
  onComplete,
1946
2246
  onError,
2247
+ onDecline,
1947
2248
  showPayPal,
1948
2249
  showApplePay,
1949
2250
  showGooglePay,
@@ -1951,11 +2252,15 @@ function FloPayCheckout({
1951
2252
  buttonsTheme,
1952
2253
  buttonsStyles,
1953
2254
  cardButtonContent,
2255
+ cardBackButtonContent,
2256
+ cardTitleContent,
1954
2257
  onButtonClick,
2258
+ onBeforeButtonClick,
1955
2259
  enableAVS,
1956
2260
  avsLayout,
1957
2261
  country: session?.customer?.country,
1958
2262
  zip: session?.customer?.zip,
2263
+ initialCardOpen: deferredCardOpen,
1959
2264
  submitLabel,
1960
2265
  className
1961
2266
  }
@@ -1988,22 +2293,36 @@ function SessionInjector({
1988
2293
  }
1989
2294
  function InterimButtonsView({
1990
2295
  onButtonClick,
2296
+ onCardButtonClick,
2297
+ cardLoading = false,
2298
+ cardOpen,
2299
+ errorMessage,
1991
2300
  showPayPal,
1992
2301
  showApplePay,
1993
2302
  showGooglePay,
1994
2303
  buttonsTheme,
1995
2304
  buttonsStyles: stylesOverride,
1996
- cardButtonContent
2305
+ cardButtonContent,
2306
+ cardBackButtonContent,
2307
+ cardTitleContent
1997
2308
  }) {
1998
2309
  const [showCardForm, setShowCardForm] = (0, import_react7.useState)(false);
2310
+ const isCardOpenControlled = typeof cardOpen === "boolean";
2311
+ (0, import_react7.useEffect)(() => {
2312
+ if (isCardOpenControlled) {
2313
+ setShowCardForm(cardOpen);
2314
+ }
2315
+ }, [cardOpen, isCardOpenControlled]);
1999
2316
  const bStyles = (0, import_react7.useMemo)(() => {
2000
- const base = (0, import_shared5.resolveButtonsLayoutTheme)(buttonsTheme);
2317
+ const base = (0, import_shared6.resolveButtonsLayoutTheme)(buttonsTheme);
2001
2318
  if (!stylesOverride) return base;
2002
2319
  return {
2003
2320
  ...base,
2004
2321
  ...stylesOverride,
2005
2322
  cardButton: { ...base.cardButton, ...stylesOverride.cardButton },
2006
2323
  cardFormContainer: { ...base.cardFormContainer, ...stylesOverride.cardFormContainer },
2324
+ backButton: { ...base.backButton, ...stylesOverride.backButton },
2325
+ backButtonIcon: { ...base.backButtonIcon, ...stylesOverride.backButtonIcon },
2007
2326
  submitButton: { ...base.submitButton, ...stylesOverride.submitButton },
2008
2327
  title: { ...base.title, ...stylesOverride.title }
2009
2328
  };
@@ -2017,6 +2336,8 @@ function InterimButtonsView({
2017
2336
  if (showCardForm) {
2018
2337
  const inputBorder = bStyles.cardInputBorder ?? "#e5e7eb";
2019
2338
  const inputBg = bStyles.cardInputBackground ?? "white";
2339
+ const hideBackButtonLabel = isEmptySlotContent(cardBackButtonContent);
2340
+ const hideTitle = isEmptySlotContent(cardTitleContent);
2020
2341
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
2021
2342
  backgroundColor: bStyles.cardFormContainer?.backgroundColor ?? "white",
2022
2343
  borderRadius: "8px",
@@ -2029,19 +2350,26 @@ function InterimButtonsView({
2029
2350
  "button",
2030
2351
  {
2031
2352
  type: "button",
2032
- onClick: () => setShowCardForm(false),
2353
+ onClick: () => {
2354
+ if (!isCardOpenControlled) {
2355
+ setShowCardForm(false);
2356
+ }
2357
+ },
2358
+ "aria-label": "Back to payment methods",
2359
+ disabled: isCardOpenControlled || cardLoading,
2033
2360
  style: {
2034
2361
  display: "inline-flex",
2035
2362
  alignItems: "center",
2036
- gap: "0.5rem",
2363
+ gap: hideBackButtonLabel ? 0 : "0.5rem",
2037
2364
  background: "none",
2038
2365
  border: "none",
2039
- cursor: "pointer",
2040
2366
  color: "#4b5563",
2041
2367
  fontSize: "0.85rem",
2042
2368
  fontWeight: 500,
2043
2369
  padding: 0,
2044
2370
  flexShrink: 0,
2371
+ opacity: isCardOpenControlled || cardLoading ? 0.6 : 1,
2372
+ cursor: isCardOpenControlled || cardLoading ? "not-allowed" : "pointer",
2045
2373
  ...bStyles.backButton
2046
2374
  },
2047
2375
  children: [
@@ -2055,11 +2383,11 @@ function InterimButtonsView({
2055
2383
  backgroundColor: "#f3f4f6",
2056
2384
  ...bStyles.backButtonIcon
2057
2385
  }, children: /* @__PURE__ */ (0, import_jsx_runtime5.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_runtime5.jsx)("path", { d: "M15 18l-6-6 6-6" }) }) }),
2058
- "Go back"
2386
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BackButtonContentSlot, { content: cardBackButtonContent })
2059
2387
  ]
2060
2388
  }
2061
2389
  ),
2062
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
2390
+ hideTitle ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { flex: 1 } }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
2063
2391
  flex: 1,
2064
2392
  textAlign: "center",
2065
2393
  fontWeight: 600,
@@ -2067,7 +2395,7 @@ function InterimButtonsView({
2067
2395
  color: "#262833",
2068
2396
  paddingRight: 80,
2069
2397
  ...bStyles.title
2070
- }, children: "Secure card checkout" })
2398
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TitleContentSlot, { content: cardTitleContent }) })
2071
2399
  ] }),
2072
2400
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTopLeftRadius: 8, borderTopRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { width: "60%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
2073
2401
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex" }, children: [
@@ -2101,10 +2429,16 @@ function InterimButtonsView({
2101
2429
  "button",
2102
2430
  {
2103
2431
  type: "button",
2104
- onClick: () => {
2432
+ onClick: async () => {
2433
+ if (cardLoading) return;
2434
+ if (onCardButtonClick) {
2435
+ await onCardButtonClick();
2436
+ return;
2437
+ }
2105
2438
  onButtonClick?.("card");
2106
2439
  setShowCardForm(true);
2107
2440
  },
2441
+ disabled: cardLoading,
2108
2442
  style: {
2109
2443
  width: "100%",
2110
2444
  padding: "0.9rem 1rem",
@@ -2114,7 +2448,7 @@ function InterimButtonsView({
2114
2448
  borderRadius: "8px",
2115
2449
  fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
2116
2450
  fontWeight: 600,
2117
- cursor: "pointer",
2451
+ cursor: cardLoading ? "not-allowed" : "pointer",
2118
2452
  display: "flex",
2119
2453
  alignItems: "center",
2120
2454
  justifyContent: "center",
@@ -2122,6 +2456,7 @@ function InterimButtonsView({
2122
2456
  boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
2123
2457
  transition: "transform 0.1s",
2124
2458
  position: "relative",
2459
+ opacity: cardLoading ? 0.6 : 1,
2125
2460
  ...bStyles.cardButton
2126
2461
  },
2127
2462
  onMouseDown: (e) => {
@@ -2133,12 +2468,29 @@ function InterimButtonsView({
2133
2468
  children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CardButtonContentSlot, { content: cardButtonContent })
2134
2469
  }
2135
2470
  ),
2471
+ errorMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
2472
+ margin: "0.25rem 0",
2473
+ padding: "0.625rem 0.875rem",
2474
+ background: "#FEF2F2",
2475
+ border: "1px solid #FECACA",
2476
+ borderRadius: "8px",
2477
+ color: "#991B1B",
2478
+ fontSize: "0.85rem",
2479
+ fontWeight: 600,
2480
+ display: "flex",
2481
+ alignItems: "center",
2482
+ gap: "0.5rem",
2483
+ ...bStyles.errorBanner
2484
+ }, children: [
2485
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.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" }) }),
2486
+ errorMessage
2487
+ ] }),
2136
2488
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
2137
2489
  ] });
2138
2490
  }
2139
2491
 
2140
2492
  // src/checkout-form.tsx
2141
- var import_shared6 = require("@flopay/shared");
2493
+ var import_shared7 = require("@flopay/shared");
2142
2494
  var import_react8 = require("react");
2143
2495
  var import_jsx_runtime6 = require("react/jsx-runtime");
2144
2496
  var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
@@ -2154,6 +2506,7 @@ function CheckoutFormInner({
2154
2506
  userId,
2155
2507
  onComplete,
2156
2508
  onError,
2509
+ onDecline,
2157
2510
  onTokenizedBody,
2158
2511
  layout = "auto",
2159
2512
  submitLabel = "Pay",
@@ -2185,6 +2538,12 @@ function CheckoutFormInner({
2185
2538
  },
2186
2539
  [onErrorChange]
2187
2540
  );
2541
+ const emitDecline = (0, import_react8.useCallback)(
2542
+ (input, overrides) => {
2543
+ onDecline?.(buildDeclineEvent("card", input, overrides));
2544
+ },
2545
+ [onDecline]
2546
+ );
2188
2547
  const processPaymentInternal = (0, import_react8.useCallback)(
2189
2548
  async (tokenizedBody) => {
2190
2549
  setProcessing(true);
@@ -2231,6 +2590,7 @@ function CheckoutFormInner({
2231
2590
  if (result.error) {
2232
2591
  updateError(result.error.message);
2233
2592
  onError?.(result.error);
2593
+ emitDecline(result.error);
2234
2594
  return;
2235
2595
  }
2236
2596
  if (result.status === "succeeded" || result.status === "processing") {
@@ -2265,13 +2625,17 @@ function CheckoutFormInner({
2265
2625
  }
2266
2626
  const errorMessage = json?.message ?? "Payment failed. Please try again.";
2267
2627
  updateError(errorMessage);
2628
+ emitDecline(errorMessage, {
2629
+ code: json?.code,
2630
+ declineCode: json?.declineCode ?? json?.gatewayDeclineReason
2631
+ });
2268
2632
  } catch (err) {
2269
2633
  updateError(err instanceof Error ? err.message : "An unexpected error occurred");
2270
2634
  } finally {
2271
2635
  setProcessing(false);
2272
2636
  }
2273
2637
  },
2274
- [baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError]
2638
+ [baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError, emitDecline]
2275
2639
  );
2276
2640
  const dispatchTokenizedBody = (0, import_react8.useCallback)(
2277
2641
  (tokenizedBody) => {
@@ -2295,6 +2659,7 @@ function CheckoutFormInner({
2295
2659
  if (result.error) {
2296
2660
  updateError(result.error.message);
2297
2661
  onError?.(result.error);
2662
+ emitDecline(result.error);
2298
2663
  } else if (result.status === "succeeded" || result.status === "processing") {
2299
2664
  dispatchTokenizedBody({
2300
2665
  id: result.paymentIntentId,
@@ -2308,7 +2673,7 @@ function CheckoutFormInner({
2308
2673
  setIs3DSActive(false);
2309
2674
  }
2310
2675
  }
2311
- }), [flopay, dispatchTokenizedBody, onError, updateError]);
2676
+ }), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
2312
2677
  (0, import_react8.useEffect)(() => {
2313
2678
  if (typeof window === "undefined") return;
2314
2679
  const stored = localStorage.getItem(WALLET_RESUME_KEY2);
@@ -2346,7 +2711,7 @@ function CheckoutFormInner({
2346
2711
  return;
2347
2712
  }
2348
2713
  if (!sessionId || !email) {
2349
- throw new import_shared6.FloPayError("Missing sessionId or email", "validation_error");
2714
+ throw new import_shared7.FloPayError("Missing sessionId or email", "validation_error");
2350
2715
  }
2351
2716
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
2352
2717
  method: "POST",
@@ -2358,16 +2723,18 @@ function CheckoutFormInner({
2358
2723
  isPaypal: false
2359
2724
  })
2360
2725
  });
2361
- if (!intentResponse.ok) throw new import_shared6.FloPayError("Failed to create payment intent", "api_error");
2726
+ if (!intentResponse.ok) throw new import_shared7.FloPayError("Failed to create payment intent", "api_error");
2362
2727
  const intentJson = await intentResponse.json();
2363
2728
  const intentClientSecret = intentJson.data?.id;
2364
- if (!intentClientSecret) throw new import_shared6.FloPayError("No client_secret in payment intent response", "api_error");
2729
+ if (!intentClientSecret) throw new import_shared7.FloPayError("No client_secret in payment intent response", "api_error");
2365
2730
  const confirmResult = await flopay.confirmCardPayment({
2366
2731
  clientSecret: intentClientSecret,
2367
2732
  paymentMethodId: pmResult.paymentMethodId
2368
2733
  });
2369
2734
  if (confirmResult.error) {
2370
2735
  updateError(confirmResult.error.message);
2736
+ onError?.(confirmResult.error);
2737
+ emitDecline(confirmResult.error);
2371
2738
  return;
2372
2739
  }
2373
2740
  dispatchTokenizedBody({
@@ -2384,7 +2751,7 @@ function CheckoutFormInner({
2384
2751
  }
2385
2752
  }
2386
2753
  },
2387
- [flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
2754
+ [flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError, emitDecline]
2388
2755
  );
2389
2756
  const isReady = flopay !== null && elements !== null;
2390
2757
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
@@ -2416,7 +2783,7 @@ function CheckoutFormInner({
2416
2783
  }
2417
2784
 
2418
2785
  // src/paypal-button.tsx
2419
- var import_shared7 = require("@flopay/shared");
2786
+ var import_shared8 = require("@flopay/shared");
2420
2787
  var import_react9 = require("react");
2421
2788
  var import_jsx_runtime7 = require("react/jsx-runtime");
2422
2789
  function PayPalButton({
@@ -2536,7 +2903,7 @@ function PayPalButton({
2536
2903
  setSubmitting(true);
2537
2904
  onErrorChange?.(null);
2538
2905
  if (!sessionId || !email) {
2539
- throw new import_shared7.FloPayError("Missing sessionId or email for PayPal payment", "validation_error");
2906
+ throw new import_shared8.FloPayError("Missing sessionId or email for PayPal payment", "validation_error");
2540
2907
  }
2541
2908
  const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
2542
2909
  method: "POST",
@@ -2548,10 +2915,10 @@ function PayPalButton({
2548
2915
  isPaypal: "true"
2549
2916
  })
2550
2917
  });
2551
- if (!intentResponse.ok) throw new import_shared7.FloPayError("Failed to create payment intent", "api_error");
2918
+ if (!intentResponse.ok) throw new import_shared8.FloPayError("Failed to create payment intent", "api_error");
2552
2919
  const intentJson = await intentResponse.json();
2553
2920
  const intentClientSecret = intentJson.data?.id;
2554
- if (!intentClientSecret) throw new import_shared7.FloPayError("No client_secret in response", "api_error");
2921
+ if (!intentClientSecret) throw new import_shared8.FloPayError("No client_secret in response", "api_error");
2555
2922
  const result = await flopay.confirmPayment({
2556
2923
  clientSecret: intentClientSecret,
2557
2924
  returnUrl: window.location.href