@flopay/react 0.3.11 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/index.cjs +542 -197
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -5
- package/dist/index.d.ts +27 -5
- package/dist/index.mjs +533 -188
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
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
|
|
121
|
+
var import_shared6 = require("@flopay/shared");
|
|
122
122
|
|
|
123
123
|
// src/card-button-content.tsx
|
|
124
124
|
var import_react3 = require("react");
|
|
@@ -249,7 +249,7 @@ var AddressElement = createElementComponent("address", "AddressElement");
|
|
|
249
249
|
|
|
250
250
|
// src/split-card-form.tsx
|
|
251
251
|
var import_react_stripe_js = require("@stripe/react-stripe-js");
|
|
252
|
-
var
|
|
252
|
+
var import_shared4 = require("@flopay/shared");
|
|
253
253
|
var import_react6 = require("react");
|
|
254
254
|
|
|
255
255
|
// src/hooks.ts
|
|
@@ -271,8 +271,116 @@ function useBillingApiUrl() {
|
|
|
271
271
|
return ctx.billingApiUrl || (0, import_shared2.resolveBillingApiUrl)();
|
|
272
272
|
}
|
|
273
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
|
+
|
|
274
382
|
// src/split-card-form.tsx
|
|
275
|
-
var
|
|
383
|
+
var import_shared5 = require("@flopay/shared");
|
|
276
384
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
277
385
|
var WALLET_RESUME_KEY = "flopay_wallet_resume";
|
|
278
386
|
var FLOPAY_KEYFRAMES = `
|
|
@@ -410,7 +518,8 @@ function PayPalButtonInner({
|
|
|
410
518
|
onTokenizedBody,
|
|
411
519
|
onErrorChange,
|
|
412
520
|
isProcessing = false,
|
|
413
|
-
onButtonClick
|
|
521
|
+
onButtonClick,
|
|
522
|
+
onDecline
|
|
414
523
|
}) {
|
|
415
524
|
const stripe = (0, import_react_stripe_js.useStripe)();
|
|
416
525
|
const elements = (0, import_react_stripe_js.useElements)();
|
|
@@ -430,7 +539,9 @@ function PayPalButtonInner({
|
|
|
430
539
|
try {
|
|
431
540
|
setSubmitting(true);
|
|
432
541
|
if (redirectStatus === "failed") {
|
|
433
|
-
|
|
542
|
+
const message = "PayPal payment was declined. Please try again.";
|
|
543
|
+
onErrorChange?.(message);
|
|
544
|
+
onDecline?.(buildDeclineEvent("paypal", message));
|
|
434
545
|
return;
|
|
435
546
|
}
|
|
436
547
|
const { paymentIntent, error } = await stripe.retrievePaymentIntent(clientSecret);
|
|
@@ -452,7 +563,9 @@ function PayPalButtonInner({
|
|
|
452
563
|
url.searchParams.delete("redirect_status");
|
|
453
564
|
window.history.replaceState({}, "", url.toString());
|
|
454
565
|
} else {
|
|
455
|
-
|
|
566
|
+
const message = "PayPal payment was not completed. Please try again.";
|
|
567
|
+
onErrorChange?.(message);
|
|
568
|
+
onDecline?.(buildDeclineEvent("paypal", message));
|
|
456
569
|
}
|
|
457
570
|
} catch (err) {
|
|
458
571
|
onErrorChange?.(err instanceof Error ? err.message : "Failed to complete PayPal payment.");
|
|
@@ -460,7 +573,7 @@ function PayPalButtonInner({
|
|
|
460
573
|
setSubmitting(false);
|
|
461
574
|
}
|
|
462
575
|
})();
|
|
463
|
-
}, [stripe, onTokenizedBody, onErrorChange]);
|
|
576
|
+
}, [stripe, onTokenizedBody, onErrorChange, onDecline]);
|
|
464
577
|
const handlePayPalConfirm = (0, import_react6.useCallback)(async (_event) => {
|
|
465
578
|
if (!stripe || !elements) return;
|
|
466
579
|
onButtonClick?.("paypal");
|
|
@@ -501,7 +614,11 @@ function PayPalButtonInner({
|
|
|
501
614
|
redirect: "if_required"
|
|
502
615
|
});
|
|
503
616
|
if (confirmError) {
|
|
504
|
-
|
|
617
|
+
const message = confirmError.message ?? "PayPal payment failed.";
|
|
618
|
+
onErrorChange?.(message);
|
|
619
|
+
onDecline?.(buildDeclineEvent("paypal", message, {
|
|
620
|
+
code: confirmError.code
|
|
621
|
+
}));
|
|
505
622
|
return;
|
|
506
623
|
}
|
|
507
624
|
const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
|
|
@@ -516,7 +633,7 @@ function PayPalButtonInner({
|
|
|
516
633
|
} finally {
|
|
517
634
|
setSubmitting(false);
|
|
518
635
|
}
|
|
519
|
-
}, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]);
|
|
636
|
+
}, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]);
|
|
520
637
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
521
638
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
522
639
|
import_react_stripe_js.ExpressCheckoutElement,
|
|
@@ -525,6 +642,9 @@ function PayPalButtonInner({
|
|
|
525
642
|
onLoadError: () => {
|
|
526
643
|
},
|
|
527
644
|
onConfirm: handlePayPalConfirm,
|
|
645
|
+
onCancel: () => {
|
|
646
|
+
onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
|
|
647
|
+
},
|
|
528
648
|
options: {
|
|
529
649
|
buttonType: { paypal: "paypal" },
|
|
530
650
|
paymentMethods: {
|
|
@@ -547,13 +667,15 @@ function WalletButtonInner({
|
|
|
547
667
|
showGooglePay = true,
|
|
548
668
|
onTokenizedBody,
|
|
549
669
|
onErrorChange,
|
|
550
|
-
onButtonClick
|
|
670
|
+
onButtonClick,
|
|
671
|
+
onDecline
|
|
551
672
|
}) {
|
|
552
673
|
const stripe = (0, import_react_stripe_js.useStripe)();
|
|
553
674
|
const elements = (0, import_react_stripe_js.useElements)();
|
|
554
675
|
const [ready, setReady] = (0, import_react6.useState)(false);
|
|
555
676
|
const [submitting, setSubmitting] = (0, import_react6.useState)(false);
|
|
556
677
|
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
678
|
+
const lastWalletMethodRef = (0, import_react6.useRef)("card");
|
|
557
679
|
const handleWalletConfirm = (0, import_react6.useCallback)(
|
|
558
680
|
async (_event) => {
|
|
559
681
|
if (!stripe || !elements) return;
|
|
@@ -594,7 +716,12 @@ function WalletButtonInner({
|
|
|
594
716
|
{ payment_method: paymentMethod.id }
|
|
595
717
|
);
|
|
596
718
|
if (confirmError) {
|
|
597
|
-
|
|
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
|
+
}));
|
|
598
725
|
return;
|
|
599
726
|
}
|
|
600
727
|
onTokenizedBody({
|
|
@@ -608,7 +735,7 @@ function WalletButtonInner({
|
|
|
608
735
|
setSubmitting(false);
|
|
609
736
|
}
|
|
610
737
|
},
|
|
611
|
-
[stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]
|
|
738
|
+
[stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]
|
|
612
739
|
);
|
|
613
740
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
614
741
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -617,7 +744,14 @@ function WalletButtonInner({
|
|
|
617
744
|
onReady: () => setReady(true),
|
|
618
745
|
onLoadError: () => {
|
|
619
746
|
},
|
|
747
|
+
onClick: (event) => {
|
|
748
|
+
lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
|
|
749
|
+
event.resolve();
|
|
750
|
+
},
|
|
620
751
|
onConfirm: handleWalletConfirm,
|
|
752
|
+
onCancel: () => {
|
|
753
|
+
onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
|
|
754
|
+
},
|
|
621
755
|
options: {
|
|
622
756
|
buttonType: { applePay: "plain", googlePay: "plain" },
|
|
623
757
|
paymentMethods: {
|
|
@@ -628,7 +762,7 @@ function WalletButtonInner({
|
|
|
628
762
|
amazonPay: "never",
|
|
629
763
|
klarna: "never"
|
|
630
764
|
},
|
|
631
|
-
layout: { overflow: "never" }
|
|
765
|
+
layout: { maxColumns: 1, overflow: "never" }
|
|
632
766
|
}
|
|
633
767
|
}
|
|
634
768
|
) }),
|
|
@@ -642,6 +776,7 @@ function SplitCardFormInner({
|
|
|
642
776
|
userId,
|
|
643
777
|
onComplete,
|
|
644
778
|
onError,
|
|
779
|
+
onDecline,
|
|
645
780
|
onTokenizedBody,
|
|
646
781
|
firstName,
|
|
647
782
|
lastName,
|
|
@@ -664,6 +799,7 @@ function SplitCardFormInner({
|
|
|
664
799
|
cardBackButtonContent,
|
|
665
800
|
cardTitleContent,
|
|
666
801
|
onButtonClick,
|
|
802
|
+
onBeforeButtonClick,
|
|
667
803
|
enableAVS = false,
|
|
668
804
|
avsLayout: avsLayoutProp = "row",
|
|
669
805
|
country: countryProp,
|
|
@@ -672,6 +808,7 @@ function SplitCardFormInner({
|
|
|
672
808
|
onZipChange,
|
|
673
809
|
totalAmount = 0,
|
|
674
810
|
currency = "usd",
|
|
811
|
+
initialCardOpen = false,
|
|
675
812
|
innerRef
|
|
676
813
|
}) {
|
|
677
814
|
const flopay = useFloPay();
|
|
@@ -682,9 +819,10 @@ function SplitCardFormInner({
|
|
|
682
819
|
const [is3DSActive, setIs3DSActive] = (0, import_react6.useState)(false);
|
|
683
820
|
const [selectedCountry, setSelectedCountry] = (0, import_react6.useState)(countryProp ?? "US");
|
|
684
821
|
const [zipCode, setZipCode] = (0, import_react6.useState)(zipProp ?? "");
|
|
822
|
+
const [accountPatch, setAccountPatch] = (0, import_react6.useState)({});
|
|
685
823
|
const zipCodeRef = (0, import_react6.useRef)(zipProp ?? "");
|
|
686
824
|
const selectedCountryRef = (0, import_react6.useRef)(countryProp ?? "US");
|
|
687
|
-
const [viewState, setViewState] = (0, import_react6.useState)("buttons");
|
|
825
|
+
const [viewState, setViewState] = (0, import_react6.useState)(initialCardOpen ? "card" : "buttons");
|
|
688
826
|
const showCardForm = viewState === "expanding" || viewState === "card";
|
|
689
827
|
const TRANSITION_MS = 280;
|
|
690
828
|
const expandToCard = (0, import_react6.useCallback)(() => {
|
|
@@ -695,6 +833,11 @@ function SplitCardFormInner({
|
|
|
695
833
|
setViewState("collapsing");
|
|
696
834
|
setTimeout(() => setViewState("buttons"), TRANSITION_MS);
|
|
697
835
|
}, []);
|
|
836
|
+
(0, import_react6.useEffect)(() => {
|
|
837
|
+
if (layout === "buttons" && initialCardOpen) {
|
|
838
|
+
setViewState("card");
|
|
839
|
+
}
|
|
840
|
+
}, [layout, initialCardOpen]);
|
|
698
841
|
const [fullName, setFullName] = (0, import_react6.useState)("");
|
|
699
842
|
const [formReady, setFormReady] = (0, import_react6.useState)(false);
|
|
700
843
|
const [overlayStatus, setOverlayStatus] = (0, import_react6.useState)(null);
|
|
@@ -702,7 +845,7 @@ function SplitCardFormInner({
|
|
|
702
845
|
const resolvedBillingApiUrl = billingApiUrl || contextBillingUrl;
|
|
703
846
|
const displayError = externalError ?? error;
|
|
704
847
|
const bStyles = (0, import_react6.useMemo)(() => {
|
|
705
|
-
const base = (0,
|
|
848
|
+
const base = (0, import_shared4.resolveButtonsLayoutTheme)(buttonsTheme);
|
|
706
849
|
if (!buttonsStylesOverride) return base;
|
|
707
850
|
return {
|
|
708
851
|
...base,
|
|
@@ -718,6 +861,14 @@ function SplitCardFormInner({
|
|
|
718
861
|
const isSubmitting = externalProcessing ?? processing;
|
|
719
862
|
const isSelfContained = !onTokenizedBody;
|
|
720
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]);
|
|
721
872
|
const stripeInstance = (0, import_react6.useMemo)(() => {
|
|
722
873
|
if (!flopay) return null;
|
|
723
874
|
return flopay.getRawProvider();
|
|
@@ -743,6 +894,12 @@ function SplitCardFormInner({
|
|
|
743
894
|
},
|
|
744
895
|
[onErrorChange]
|
|
745
896
|
);
|
|
897
|
+
const emitDecline = (0, import_react6.useCallback)(
|
|
898
|
+
(method, input, overrides) => {
|
|
899
|
+
onDecline?.(buildDeclineEvent(method, input, overrides));
|
|
900
|
+
},
|
|
901
|
+
[onDecline]
|
|
902
|
+
);
|
|
746
903
|
const showWallets = showApplePay || showGooglePay;
|
|
747
904
|
const handleNameChange = (0, import_react6.useCallback)((value) => {
|
|
748
905
|
setFullName(value);
|
|
@@ -750,6 +907,30 @@ function SplitCardFormInner({
|
|
|
750
907
|
onFirstNameChange?.(parts[0] ?? "");
|
|
751
908
|
onLastNameChange?.(parts.length > 1 ? parts.slice(1).join(" ") : "");
|
|
752
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]);
|
|
753
934
|
const processPaymentInternal = (0, import_react6.useCallback)(
|
|
754
935
|
async (tokenizedBody) => {
|
|
755
936
|
if (processingRef.current) return;
|
|
@@ -762,16 +943,16 @@ function SplitCardFormInner({
|
|
|
762
943
|
method: "POST",
|
|
763
944
|
headers: {
|
|
764
945
|
"Content-Type": "application/json",
|
|
765
|
-
"x-user-id": userId ?? ""
|
|
946
|
+
"x-user-id": resolvedAccount.userId ?? ""
|
|
766
947
|
},
|
|
767
948
|
body: JSON.stringify({
|
|
768
949
|
sessionId,
|
|
769
950
|
tokenizedData: tokenizedBody,
|
|
770
951
|
accountData: {
|
|
771
|
-
userId: userId ?? "",
|
|
772
|
-
email: email ?? "",
|
|
773
|
-
firstName: firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
|
|
774
|
-
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(" ") ?? "",
|
|
775
956
|
...enableAVS ? { zip: zipCodeRef.current, country: selectedCountryRef.current } : {}
|
|
776
957
|
},
|
|
777
958
|
chv
|
|
@@ -804,6 +985,7 @@ function SplitCardFormInner({
|
|
|
804
985
|
setOverlayStatus("error");
|
|
805
986
|
updateError(result.error.message);
|
|
806
987
|
onError?.(result.error);
|
|
988
|
+
emitDecline("card", result.error);
|
|
807
989
|
return;
|
|
808
990
|
}
|
|
809
991
|
if (result.status === "succeeded" || result.status === "processing") {
|
|
@@ -846,7 +1028,11 @@ function SplitCardFormInner({
|
|
|
846
1028
|
});
|
|
847
1029
|
if (confirmError) {
|
|
848
1030
|
setOverlayStatus("error");
|
|
849
|
-
|
|
1031
|
+
const message2 = confirmError.message ?? "PayPal payment failed.";
|
|
1032
|
+
updateError(message2);
|
|
1033
|
+
emitDecline("paypal", message2, {
|
|
1034
|
+
code: confirmError.code
|
|
1035
|
+
});
|
|
850
1036
|
}
|
|
851
1037
|
} catch (err) {
|
|
852
1038
|
setOverlayStatus("error");
|
|
@@ -855,7 +1041,12 @@ function SplitCardFormInner({
|
|
|
855
1041
|
return;
|
|
856
1042
|
}
|
|
857
1043
|
setOverlayStatus("error");
|
|
858
|
-
|
|
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
|
+
});
|
|
859
1050
|
await new Promise((r) => setTimeout(r, 1500));
|
|
860
1051
|
} catch (err) {
|
|
861
1052
|
setOverlayStatus("error");
|
|
@@ -867,7 +1058,7 @@ function SplitCardFormInner({
|
|
|
867
1058
|
processingRef.current = false;
|
|
868
1059
|
}
|
|
869
1060
|
},
|
|
870
|
-
[baseUrl, sessionId,
|
|
1061
|
+
[baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, onComplete, onError, updateError, emitDecline]
|
|
871
1062
|
);
|
|
872
1063
|
const dispatchTokenizedBody = (0, import_react6.useCallback)(
|
|
873
1064
|
(tokenizedBody) => {
|
|
@@ -891,6 +1082,7 @@ function SplitCardFormInner({
|
|
|
891
1082
|
if (result.error) {
|
|
892
1083
|
updateError(result.error.message);
|
|
893
1084
|
onError?.(result.error);
|
|
1085
|
+
emitDecline("card", result.error);
|
|
894
1086
|
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
895
1087
|
dispatchTokenizedBody({
|
|
896
1088
|
id: result.paymentIntentId,
|
|
@@ -904,7 +1096,7 @@ function SplitCardFormInner({
|
|
|
904
1096
|
setIs3DSActive(false);
|
|
905
1097
|
}
|
|
906
1098
|
}
|
|
907
|
-
}), [flopay, dispatchTokenizedBody, onError, updateError]);
|
|
1099
|
+
}), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
|
|
908
1100
|
(0, import_react6.useEffect)(() => {
|
|
909
1101
|
if (typeof window === "undefined") return;
|
|
910
1102
|
const stored = localStorage.getItem(WALLET_RESUME_KEY);
|
|
@@ -927,14 +1119,16 @@ function SplitCardFormInner({
|
|
|
927
1119
|
async (e) => {
|
|
928
1120
|
e.preventDefault();
|
|
929
1121
|
if (!flopay || !elements || isSubmitting || processingRef.current) return;
|
|
930
|
-
|
|
1122
|
+
if (layout !== "buttons") {
|
|
1123
|
+
onButtonClick?.("card");
|
|
1124
|
+
}
|
|
931
1125
|
setProcessing(true);
|
|
932
1126
|
setOverlayStatus("processing");
|
|
933
1127
|
updateError(null);
|
|
934
1128
|
let handedOff = false;
|
|
935
1129
|
try {
|
|
936
1130
|
if (enableAVS && !zipCodeRef.current.trim()) {
|
|
937
|
-
updateError((0,
|
|
1131
|
+
updateError((0, import_shared4.getPostalCodeLabel)(selectedCountryRef.current) + " is required");
|
|
938
1132
|
return;
|
|
939
1133
|
}
|
|
940
1134
|
const submitResult = await flopay.submitElements();
|
|
@@ -955,23 +1149,23 @@ function SplitCardFormInner({
|
|
|
955
1149
|
updateError(pmResult.error?.message ?? "Failed to create payment method.");
|
|
956
1150
|
return;
|
|
957
1151
|
}
|
|
958
|
-
if (!sessionId || !email) {
|
|
959
|
-
throw new
|
|
1152
|
+
if (!sessionId || !resolvedAccount.email) {
|
|
1153
|
+
throw new import_shared5.FloPayError("Missing sessionId or email", "validation_error");
|
|
960
1154
|
}
|
|
961
1155
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
962
1156
|
method: "POST",
|
|
963
1157
|
headers: { "Content-Type": "application/json" },
|
|
964
1158
|
body: JSON.stringify({
|
|
965
1159
|
sessionId,
|
|
966
|
-
email,
|
|
1160
|
+
email: resolvedAccount.email,
|
|
967
1161
|
paymentMethodType: pmResult.paymentMethodId,
|
|
968
1162
|
isPaypal: false
|
|
969
1163
|
})
|
|
970
1164
|
});
|
|
971
|
-
if (!intentResponse.ok) throw new
|
|
1165
|
+
if (!intentResponse.ok) throw new import_shared5.FloPayError("Failed to create payment intent", "api_error");
|
|
972
1166
|
const intentJson = await intentResponse.json();
|
|
973
1167
|
const intentClientSecret = intentJson.data?.id;
|
|
974
|
-
if (!intentClientSecret) throw new
|
|
1168
|
+
if (!intentClientSecret) throw new import_shared5.FloPayError("No client_secret in payment intent response", "api_error");
|
|
975
1169
|
const confirmResult = await flopay.confirmCardPayment({
|
|
976
1170
|
clientSecret: intentClientSecret,
|
|
977
1171
|
paymentMethodId: pmResult.paymentMethodId
|
|
@@ -979,6 +1173,8 @@ function SplitCardFormInner({
|
|
|
979
1173
|
if (confirmResult.error) {
|
|
980
1174
|
setOverlayStatus("error");
|
|
981
1175
|
updateError(confirmResult.error.message);
|
|
1176
|
+
onError?.(confirmResult.error);
|
|
1177
|
+
emitDecline("card", confirmResult.error);
|
|
982
1178
|
await new Promise((r) => setTimeout(r, 1500));
|
|
983
1179
|
return;
|
|
984
1180
|
}
|
|
@@ -999,7 +1195,7 @@ function SplitCardFormInner({
|
|
|
999
1195
|
}
|
|
1000
1196
|
}
|
|
1001
1197
|
},
|
|
1002
|
-
[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]
|
|
1003
1199
|
);
|
|
1004
1200
|
const isReady = flopay !== null && elements !== null;
|
|
1005
1201
|
if (!isReady) {
|
|
@@ -1170,7 +1366,7 @@ function SplitCardFormInner({
|
|
|
1170
1366
|
cursor: "pointer",
|
|
1171
1367
|
...isButtons && bStyles.nameInput ? bStyles.nameInput : {}
|
|
1172
1368
|
},
|
|
1173
|
-
children:
|
|
1369
|
+
children: import_shared4.COUNTRY_OPTIONS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("option", { value: c.code, children: [
|
|
1174
1370
|
c.flag,
|
|
1175
1371
|
" ",
|
|
1176
1372
|
c.name
|
|
@@ -1187,7 +1383,7 @@ function SplitCardFormInner({
|
|
|
1187
1383
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1188
1384
|
"input",
|
|
1189
1385
|
{
|
|
1190
|
-
placeholder: (0,
|
|
1386
|
+
placeholder: (0, import_shared4.getPostalCodeLabel)(selectedCountry),
|
|
1191
1387
|
autoComplete: "postal-code",
|
|
1192
1388
|
value: zipCode,
|
|
1193
1389
|
onChange: (e) => {
|
|
@@ -1284,32 +1480,36 @@ function SplitCardFormInner({
|
|
|
1284
1480
|
PayPalButtonInner,
|
|
1285
1481
|
{
|
|
1286
1482
|
sessionId,
|
|
1287
|
-
email,
|
|
1483
|
+
email: resolvedAccount.email,
|
|
1288
1484
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1289
1485
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1290
1486
|
onErrorChange: updateError,
|
|
1291
1487
|
isProcessing: isSubmitting,
|
|
1292
|
-
onButtonClick
|
|
1488
|
+
onButtonClick,
|
|
1489
|
+
onDecline
|
|
1293
1490
|
}
|
|
1294
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,
|
|
1295
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)(
|
|
1296
1493
|
WalletButtonInner,
|
|
1297
1494
|
{
|
|
1298
1495
|
sessionId,
|
|
1299
|
-
email,
|
|
1496
|
+
email: resolvedAccount.email,
|
|
1300
1497
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1301
1498
|
showApplePay,
|
|
1302
1499
|
showGooglePay,
|
|
1303
1500
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1304
1501
|
onErrorChange: updateError,
|
|
1305
|
-
onButtonClick
|
|
1502
|
+
onButtonClick,
|
|
1503
|
+
onDecline
|
|
1306
1504
|
}
|
|
1307
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,
|
|
1308
1506
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1309
1507
|
"button",
|
|
1310
1508
|
{
|
|
1311
1509
|
type: "button",
|
|
1312
|
-
onClick: () => {
|
|
1510
|
+
onClick: async () => {
|
|
1511
|
+
const shouldContinue = await runBeforeCardButtonClick();
|
|
1512
|
+
if (!shouldContinue) return;
|
|
1313
1513
|
onButtonClick?.("card");
|
|
1314
1514
|
expandToCard();
|
|
1315
1515
|
},
|
|
@@ -1374,23 +1574,25 @@ function SplitCardFormInner({
|
|
|
1374
1574
|
WalletButtonInner,
|
|
1375
1575
|
{
|
|
1376
1576
|
sessionId,
|
|
1377
|
-
email,
|
|
1577
|
+
email: resolvedAccount.email,
|
|
1378
1578
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1379
1579
|
showApplePay,
|
|
1380
1580
|
showGooglePay,
|
|
1381
1581
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1382
|
-
onErrorChange: updateError
|
|
1582
|
+
onErrorChange: updateError,
|
|
1583
|
+
onDecline
|
|
1383
1584
|
}
|
|
1384
1585
|
) }),
|
|
1385
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)(
|
|
1386
1587
|
PayPalButtonInner,
|
|
1387
1588
|
{
|
|
1388
1589
|
sessionId,
|
|
1389
|
-
email,
|
|
1590
|
+
email: resolvedAccount.email,
|
|
1390
1591
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1391
1592
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1392
1593
|
onErrorChange: updateError,
|
|
1393
|
-
isProcessing: isSubmitting
|
|
1594
|
+
isProcessing: isSubmitting,
|
|
1595
|
+
onDecline
|
|
1394
1596
|
}
|
|
1395
1597
|
) }),
|
|
1396
1598
|
(showWallets && stripeInstance || showPayPal && stripeInstance) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
|
|
@@ -1422,6 +1624,7 @@ function FloPayCheckout({
|
|
|
1422
1624
|
error: errorNode,
|
|
1423
1625
|
onComplete,
|
|
1424
1626
|
onError,
|
|
1627
|
+
onDecline,
|
|
1425
1628
|
showPayPal = true,
|
|
1426
1629
|
showApplePay = true,
|
|
1427
1630
|
showGooglePay = true,
|
|
@@ -1432,6 +1635,7 @@ function FloPayCheckout({
|
|
|
1432
1635
|
cardBackButtonContent,
|
|
1433
1636
|
cardTitleContent,
|
|
1434
1637
|
onButtonClick,
|
|
1638
|
+
onBeforeButtonClick,
|
|
1435
1639
|
enableAVS,
|
|
1436
1640
|
avsLayout,
|
|
1437
1641
|
submitLabel,
|
|
@@ -1442,7 +1646,7 @@ function FloPayCheckout({
|
|
|
1442
1646
|
renderConfirmButton,
|
|
1443
1647
|
onSessionCompleted
|
|
1444
1648
|
}) {
|
|
1445
|
-
const resolvedBillingUrl = (0,
|
|
1649
|
+
const resolvedBillingUrl = (0, import_shared6.resolveBillingApiUrl)(billingApiUrl);
|
|
1446
1650
|
const [unified, setUnified] = (0, import_react7.useState)(null);
|
|
1447
1651
|
const [flopay, setFloPay] = (0, import_react7.useState)(null);
|
|
1448
1652
|
const flopayRef = (0, import_react7.useRef)(null);
|
|
@@ -1453,13 +1657,41 @@ function FloPayCheckout({
|
|
|
1453
1657
|
const [currentMode, setCurrentMode] = (0, import_react7.useState)("full");
|
|
1454
1658
|
const [confirmProcessing, setConfirmProcessing] = (0, import_react7.useState)(false);
|
|
1455
1659
|
const [modeError, setModeError] = (0, import_react7.useState)(null);
|
|
1660
|
+
const [createSessionPatch, setCreateSessionPatch] = (0, import_react7.useState)(void 0);
|
|
1661
|
+
const [createSessionPatchBaseHash, setCreateSessionPatchBaseHash] = (0, import_react7.useState)("");
|
|
1662
|
+
const [cardBootstrapPending, setCardBootstrapPending] = (0, import_react7.useState)(false);
|
|
1663
|
+
const [deferredCardOpen, setDeferredCardOpen] = (0, import_react7.useState)(false);
|
|
1456
1664
|
const autoCheckoutAttempted = (0, import_react7.useRef)(false);
|
|
1457
1665
|
const onCompleteRef = (0, import_react7.useRef)(onComplete);
|
|
1458
1666
|
onCompleteRef.current = onComplete;
|
|
1459
1667
|
const onErrorRef = (0, import_react7.useRef)(onError);
|
|
1460
1668
|
onErrorRef.current = onError;
|
|
1669
|
+
const onDeclineRef = (0, import_react7.useRef)(onDecline);
|
|
1670
|
+
onDeclineRef.current = onDecline;
|
|
1461
1671
|
const onSessionCompletedRef = (0, import_react7.useRef)(onSessionCompleted);
|
|
1462
1672
|
onSessionCompletedRef.current = onSessionCompleted;
|
|
1673
|
+
const baseCreateSessionHash = (0, import_react7.useMemo)(
|
|
1674
|
+
() => createSessionParams ? hashCreateParams(createSessionParams) : "",
|
|
1675
|
+
[createSessionParams]
|
|
1676
|
+
);
|
|
1677
|
+
const activeCreateSessionPatch = (0, import_react7.useMemo)(
|
|
1678
|
+
() => createSessionPatchBaseHash === baseCreateSessionHash ? createSessionPatch : void 0,
|
|
1679
|
+
[createSessionPatch, createSessionPatchBaseHash, baseCreateSessionHash]
|
|
1680
|
+
);
|
|
1681
|
+
const effectiveCreateSession = (0, import_react7.useMemo)(
|
|
1682
|
+
() => createSessionParams ? mergeInlineSessionPatch(createSessionParams, activeCreateSessionPatch) : void 0,
|
|
1683
|
+
[createSessionParams, activeCreateSessionPatch]
|
|
1684
|
+
);
|
|
1685
|
+
(0, import_react7.useEffect)(() => {
|
|
1686
|
+
setCreateSessionPatch(void 0);
|
|
1687
|
+
setCreateSessionPatchBaseHash(baseCreateSessionHash);
|
|
1688
|
+
}, [baseCreateSessionHash]);
|
|
1689
|
+
const emitDecline = (0, import_react7.useCallback)(
|
|
1690
|
+
(method, input, overrides) => {
|
|
1691
|
+
onDeclineRef.current?.(buildDeclineEvent(method, input, overrides));
|
|
1692
|
+
},
|
|
1693
|
+
[]
|
|
1694
|
+
);
|
|
1463
1695
|
const processPaymentForMode = (0, import_react7.useCallback)(
|
|
1464
1696
|
async (sess) => {
|
|
1465
1697
|
const baseUrl = resolvedBillingUrl.replace(/\/+$/, "");
|
|
@@ -1499,9 +1731,13 @@ function FloPayCheckout({
|
|
|
1499
1731
|
// No client secret available
|
|
1500
1732
|
};
|
|
1501
1733
|
}
|
|
1502
|
-
throw new
|
|
1734
|
+
throw new import_shared6.FloPayError(
|
|
1503
1735
|
json?.message ?? "Payment failed. Please try again.",
|
|
1504
|
-
"api_error"
|
|
1736
|
+
"api_error",
|
|
1737
|
+
{
|
|
1738
|
+
code: json?.code ?? json?.gatewayErrorCode,
|
|
1739
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
1740
|
+
}
|
|
1505
1741
|
);
|
|
1506
1742
|
},
|
|
1507
1743
|
[resolvedBillingUrl, resolvedSessionId]
|
|
@@ -1520,6 +1756,9 @@ function FloPayCheckout({
|
|
|
1520
1756
|
});
|
|
1521
1757
|
if (nextActionError) {
|
|
1522
1758
|
setModeError(nextActionError.message ?? "3DS authentication failed.");
|
|
1759
|
+
emitDecline("card", nextActionError.message ?? "3DS authentication failed.", {
|
|
1760
|
+
code: nextActionError.code
|
|
1761
|
+
});
|
|
1523
1762
|
return false;
|
|
1524
1763
|
}
|
|
1525
1764
|
if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded")) {
|
|
@@ -1542,6 +1781,9 @@ function FloPayCheckout({
|
|
|
1542
1781
|
});
|
|
1543
1782
|
if (error) {
|
|
1544
1783
|
setModeError(error.message ?? "PayPal authorization failed.");
|
|
1784
|
+
emitDecline("paypal", error.message ?? "PayPal authorization failed.", {
|
|
1785
|
+
code: error.code
|
|
1786
|
+
});
|
|
1545
1787
|
return false;
|
|
1546
1788
|
}
|
|
1547
1789
|
onCompleteRef.current?.({ status: "succeeded" });
|
|
@@ -1549,16 +1791,24 @@ function FloPayCheckout({
|
|
|
1549
1791
|
}
|
|
1550
1792
|
return false;
|
|
1551
1793
|
},
|
|
1552
|
-
[resolvedBillingUrl, resolvedSessionId]
|
|
1794
|
+
[resolvedBillingUrl, resolvedSessionId, emitDecline]
|
|
1553
1795
|
);
|
|
1554
1796
|
const inflightRef = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
|
|
1555
1797
|
const initializedHashRef = (0, import_react7.useRef)(null);
|
|
1798
|
+
const deferInlineSessionUntilCardClick = Boolean(
|
|
1799
|
+
effectiveCreateSession && !children && layout === "buttons" && onBeforeButtonClick && (effectiveCreateSession.checkoutMode ?? checkoutModeProp ?? "full") === "full"
|
|
1800
|
+
);
|
|
1556
1801
|
function hashCreateParams(params) {
|
|
1557
1802
|
const key = JSON.stringify({
|
|
1558
1803
|
c: params?.clientId,
|
|
1804
|
+
successUrl: params?.successUrl,
|
|
1805
|
+
cancelUrl: params?.cancelUrl,
|
|
1559
1806
|
i: params?.items?.map((x) => `${x.providerItemId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
|
|
1560
1807
|
s: params?.subscriptions?.map((x) => `${x.providerPlanId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
|
|
1561
|
-
|
|
1808
|
+
account: params?.account,
|
|
1809
|
+
couponCodes: params?.couponCodes,
|
|
1810
|
+
tagsData: params?.tagsData,
|
|
1811
|
+
utmMetadata: params?.utmMetadata,
|
|
1562
1812
|
m: params?.checkoutMode ?? "full"
|
|
1563
1813
|
});
|
|
1564
1814
|
let h = 0;
|
|
@@ -1568,138 +1818,169 @@ function FloPayCheckout({
|
|
|
1568
1818
|
return `flopay_session_${Math.abs(h).toString(36)}`;
|
|
1569
1819
|
}
|
|
1570
1820
|
const createSessionHash = (0, import_react7.useMemo)(
|
|
1571
|
-
() =>
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1821
|
+
() => effectiveCreateSession ? hashCreateParams(effectiveCreateSession) : "",
|
|
1822
|
+
[effectiveCreateSession]
|
|
1823
|
+
);
|
|
1824
|
+
const createSessionParamsRef = (0, import_react7.useRef)(effectiveCreateSession);
|
|
1825
|
+
createSessionParamsRef.current = effectiveCreateSession;
|
|
1826
|
+
(0, import_react7.useEffect)(() => {
|
|
1827
|
+
setResolvedSessionId(sessionIdProp ?? "");
|
|
1828
|
+
}, [sessionIdProp]);
|
|
1829
|
+
async function resolveInlineSession(params, cacheKey) {
|
|
1830
|
+
ensureInlineSessionReady(params);
|
|
1831
|
+
const api = new import_js.PaymentAPI(resolvedBillingUrl);
|
|
1832
|
+
let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
|
|
1833
|
+
let realResult = null;
|
|
1834
|
+
if (sid) {
|
|
1835
|
+
try {
|
|
1836
|
+
realResult = await api.getUnifiedCheckoutSession(sid);
|
|
1837
|
+
if (realResult.data.session?.status === "complete") {
|
|
1838
|
+
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1839
|
+
sid = null;
|
|
1840
|
+
realResult = null;
|
|
1841
|
+
}
|
|
1842
|
+
} catch {
|
|
1843
|
+
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1844
|
+
sid = null;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
if (!sid) {
|
|
1848
|
+
realResult = await api.createAndFetchSession(params);
|
|
1849
|
+
sid = realResult.data.session?.id ?? "";
|
|
1850
|
+
if (sid && typeof window !== "undefined") {
|
|
1851
|
+
window.sessionStorage.setItem(cacheKey, sid);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
return { sid: sid ?? "", result: realResult };
|
|
1855
|
+
}
|
|
1856
|
+
const bootstrapInlineSession = (0, import_react7.useCallback)(
|
|
1857
|
+
async (patch) => {
|
|
1858
|
+
const baseParams = createSessionParamsRef.current;
|
|
1859
|
+
if (!baseParams) {
|
|
1860
|
+
throw new import_shared6.FloPayError("createSession is required to bootstrap checkout.", "validation_error");
|
|
1861
|
+
}
|
|
1862
|
+
const mergedParams = mergeInlineSessionPatch(baseParams, patch);
|
|
1863
|
+
const cacheKey = hashCreateParams(mergedParams);
|
|
1864
|
+
let promise = inflightRef.current.get(cacheKey);
|
|
1865
|
+
if (!promise) {
|
|
1866
|
+
promise = resolveInlineSession(mergedParams, cacheKey);
|
|
1867
|
+
inflightRef.current.set(cacheKey, promise);
|
|
1868
|
+
}
|
|
1869
|
+
let resolved;
|
|
1870
|
+
try {
|
|
1871
|
+
resolved = await promise;
|
|
1872
|
+
} finally {
|
|
1873
|
+
inflightRef.current.delete(cacheKey);
|
|
1874
|
+
}
|
|
1875
|
+
if (patch) {
|
|
1876
|
+
setCreateSessionPatchBaseHash(baseCreateSessionHash);
|
|
1877
|
+
setCreateSessionPatch((prev) => mergeInlineSessionPatches(prev, patch));
|
|
1878
|
+
}
|
|
1879
|
+
const { sid, result: realResult } = resolved;
|
|
1880
|
+
setUnified(realResult);
|
|
1881
|
+
if (realResult.data.session) {
|
|
1882
|
+
setSession(realResult.data.session);
|
|
1883
|
+
}
|
|
1884
|
+
if (sid) {
|
|
1885
|
+
setResolvedSessionId(sid);
|
|
1886
|
+
}
|
|
1887
|
+
let publishableKey;
|
|
1888
|
+
if (realResult.provider === "stripe") {
|
|
1889
|
+
publishableKey = realResult.data.stripe?.publishableKey;
|
|
1890
|
+
}
|
|
1891
|
+
if (!publishableKey) publishableKey = fallbackPublishableKey;
|
|
1892
|
+
if (!publishableKey) {
|
|
1893
|
+
throw new import_shared6.FloPayError(
|
|
1894
|
+
"No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
|
|
1895
|
+
"validation_error"
|
|
1896
|
+
);
|
|
1897
|
+
}
|
|
1898
|
+
const instance = await (0, import_js.loadFloPay)(publishableKey, {
|
|
1899
|
+
billingApiUrl: resolvedBillingUrl,
|
|
1900
|
+
locale
|
|
1901
|
+
});
|
|
1902
|
+
flopayRef.current = instance;
|
|
1903
|
+
setFloPay(instance);
|
|
1904
|
+
initializedHashRef.current = cacheKey;
|
|
1905
|
+
setIsLoading(false);
|
|
1906
|
+
return resolved;
|
|
1907
|
+
},
|
|
1908
|
+
[fallbackPublishableKey, locale, resolvedBillingUrl]
|
|
1580
1909
|
);
|
|
1581
|
-
const
|
|
1582
|
-
|
|
1910
|
+
const handleDeferredCardButtonClick = (0, import_react7.useCallback)(async () => {
|
|
1911
|
+
if (cardBootstrapPending) return;
|
|
1912
|
+
setLoadError(null);
|
|
1913
|
+
setCardBootstrapPending(true);
|
|
1914
|
+
try {
|
|
1915
|
+
const beforeClickResult = await onBeforeButtonClick?.({
|
|
1916
|
+
method: "card",
|
|
1917
|
+
createSession: effectiveCreateSession
|
|
1918
|
+
});
|
|
1919
|
+
if (beforeClickResult === false) {
|
|
1920
|
+
setDeferredCardOpen(false);
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
const patch = beforeClickResult && typeof beforeClickResult === "object" ? beforeClickResult : void 0;
|
|
1924
|
+
const baseParams = createSessionParamsRef.current;
|
|
1925
|
+
if (!baseParams) {
|
|
1926
|
+
throw new import_shared6.FloPayError(
|
|
1927
|
+
"createSession is required to bootstrap checkout.",
|
|
1928
|
+
"validation_error"
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
ensureInlineSessionReady(mergeInlineSessionPatch(baseParams, patch));
|
|
1932
|
+
setDeferredCardOpen(true);
|
|
1933
|
+
onButtonClick?.("card");
|
|
1934
|
+
await bootstrapInlineSession(patch);
|
|
1935
|
+
} catch (err) {
|
|
1936
|
+
setDeferredCardOpen(false);
|
|
1937
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(
|
|
1938
|
+
err instanceof Error ? err.message : "Failed to start card checkout.",
|
|
1939
|
+
"api_error"
|
|
1940
|
+
);
|
|
1941
|
+
setLoadError(floPayErr);
|
|
1942
|
+
onErrorRef.current?.(floPayErr);
|
|
1943
|
+
} finally {
|
|
1944
|
+
setCardBootstrapPending(false);
|
|
1945
|
+
}
|
|
1946
|
+
}, [
|
|
1947
|
+
bootstrapInlineSession,
|
|
1948
|
+
baseCreateSessionHash,
|
|
1949
|
+
cardBootstrapPending,
|
|
1950
|
+
effectiveCreateSession,
|
|
1951
|
+
onButtonClick,
|
|
1952
|
+
onBeforeButtonClick
|
|
1953
|
+
]);
|
|
1583
1954
|
(0, import_react7.useEffect)(() => {
|
|
1584
1955
|
let cancelled = false;
|
|
1585
1956
|
setLoadError(null);
|
|
1586
1957
|
if (createSessionHash) {
|
|
1958
|
+
if (deferInlineSessionUntilCardClick) {
|
|
1959
|
+
if (initializedHashRef.current !== createSessionHash) {
|
|
1960
|
+
setSession(null);
|
|
1961
|
+
setUnified(null);
|
|
1962
|
+
setFloPay(null);
|
|
1963
|
+
setResolvedSessionId("");
|
|
1964
|
+
setDeferredCardOpen(false);
|
|
1965
|
+
flopayRef.current = null;
|
|
1966
|
+
}
|
|
1967
|
+
setCurrentMode(createSessionParamsRef.current?.checkoutMode ?? checkoutModeProp ?? "full");
|
|
1968
|
+
setIsLoading(false);
|
|
1969
|
+
return () => {
|
|
1970
|
+
cancelled = true;
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1587
1973
|
if (initializedHashRef.current === createSessionHash) return;
|
|
1588
1974
|
const params = createSessionParamsRef.current;
|
|
1589
|
-
|
|
1590
|
-
...(params.items ?? []).map((i) => i.overrideAmount ?? i.totalAmount ?? 0),
|
|
1591
|
-
...(params.subscriptions ?? []).map((s) => s.overrideAmount ?? s.totalAmount ?? 0)
|
|
1592
|
-
].reduce((sum, v) => sum + v, 0);
|
|
1593
|
-
const currency = params.items?.[0]?.currency ?? params.subscriptions?.[0]?.currency ?? "USD";
|
|
1594
|
-
const syntheticSession = {
|
|
1595
|
-
id: "",
|
|
1596
|
-
clientSecret: "",
|
|
1597
|
-
mode: "payment",
|
|
1598
|
-
amount: Math.round(totalAmount * 100),
|
|
1599
|
-
currency,
|
|
1600
|
-
status: "open",
|
|
1601
|
-
customer: {
|
|
1602
|
-
id: params.account.userId,
|
|
1603
|
-
email: params.account.email,
|
|
1604
|
-
firstName: params.account.firstName,
|
|
1605
|
-
lastName: params.account.lastName
|
|
1606
|
-
},
|
|
1607
|
-
successUrl: params.successUrl,
|
|
1608
|
-
cancelUrl: params.cancelUrl,
|
|
1609
|
-
checkoutMode: params.checkoutMode ?? "full",
|
|
1610
|
-
items: (params.items ?? []).map((i, idx) => ({
|
|
1611
|
-
uuid: `synthetic-item-${idx}`,
|
|
1612
|
-
checkoutSessionId: "",
|
|
1613
|
-
providerItemId: i.providerItemId,
|
|
1614
|
-
providerItemName: i.providerItemName ?? i.providerItemId,
|
|
1615
|
-
quantity: i.quantity ?? 1,
|
|
1616
|
-
totalAmount: i.totalAmount,
|
|
1617
|
-
overrideAmount: i.overrideAmount ?? null,
|
|
1618
|
-
currency: i.currency ?? currency
|
|
1619
|
-
})),
|
|
1620
|
-
subscriptions: (params.subscriptions ?? []).map((s, idx) => ({
|
|
1621
|
-
uuid: `synthetic-sub-${idx}`,
|
|
1622
|
-
checkoutSessionId: "",
|
|
1623
|
-
providerPlanId: s.providerPlanId,
|
|
1624
|
-
providerPlanName: s.providerPlanName ?? s.providerPlanId,
|
|
1625
|
-
quantity: s.quantity ?? 1,
|
|
1626
|
-
totalAmount: s.totalAmount,
|
|
1627
|
-
overrideAmount: s.overrideAmount ?? null,
|
|
1628
|
-
currency: s.currency ?? currency
|
|
1629
|
-
}))
|
|
1630
|
-
};
|
|
1631
|
-
setSession(syntheticSession);
|
|
1975
|
+
setSession(buildSyntheticSession(params, checkoutModeProp));
|
|
1632
1976
|
setCurrentMode(params.checkoutMode ?? checkoutModeProp ?? "full");
|
|
1633
1977
|
setIsLoading(false);
|
|
1634
|
-
const cacheKey = createSessionHash;
|
|
1635
|
-
async function resolveSession() {
|
|
1636
|
-
const api = new import_js.PaymentAPI(resolvedBillingUrl);
|
|
1637
|
-
let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
|
|
1638
|
-
let realResult = null;
|
|
1639
|
-
if (sid) {
|
|
1640
|
-
try {
|
|
1641
|
-
realResult = await api.getUnifiedCheckoutSession(sid);
|
|
1642
|
-
if (realResult.data.session?.status === "complete") {
|
|
1643
|
-
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1644
|
-
sid = null;
|
|
1645
|
-
realResult = null;
|
|
1646
|
-
}
|
|
1647
|
-
} catch {
|
|
1648
|
-
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1649
|
-
sid = null;
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
|
-
if (!sid) {
|
|
1653
|
-
realResult = await api.createAndFetchSession(createSessionParamsRef.current);
|
|
1654
|
-
sid = realResult.data.session?.id ?? "";
|
|
1655
|
-
if (sid && typeof window !== "undefined") {
|
|
1656
|
-
window.sessionStorage.setItem(cacheKey, sid);
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
return { sid: sid ?? "", result: realResult };
|
|
1660
|
-
}
|
|
1661
1978
|
(async () => {
|
|
1662
1979
|
try {
|
|
1663
|
-
|
|
1664
|
-
if (!promise) {
|
|
1665
|
-
promise = resolveSession();
|
|
1666
|
-
inflightRef.current.set(cacheKey, promise);
|
|
1667
|
-
}
|
|
1668
|
-
let resolved;
|
|
1669
|
-
try {
|
|
1670
|
-
resolved = await promise;
|
|
1671
|
-
} finally {
|
|
1672
|
-
inflightRef.current.delete(cacheKey);
|
|
1673
|
-
}
|
|
1674
|
-
if (cancelled) return;
|
|
1675
|
-
const { sid, result: realResult } = resolved;
|
|
1676
|
-
if (realResult) {
|
|
1677
|
-
setUnified(realResult);
|
|
1678
|
-
if (realResult.data.session) setSession(realResult.data.session);
|
|
1679
|
-
}
|
|
1680
|
-
if (sid) {
|
|
1681
|
-
setResolvedSessionId(sid);
|
|
1682
|
-
}
|
|
1683
|
-
let pk;
|
|
1684
|
-
if (realResult?.provider === "stripe") {
|
|
1685
|
-
pk = realResult.data.stripe?.publishableKey;
|
|
1686
|
-
}
|
|
1687
|
-
if (!pk) pk = fallbackPublishableKey;
|
|
1688
|
-
if (!pk) {
|
|
1689
|
-
throw new import_shared5.FloPayError(
|
|
1690
|
-
"No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
|
|
1691
|
-
"validation_error"
|
|
1692
|
-
);
|
|
1693
|
-
}
|
|
1694
|
-
const instance = await (0, import_js.loadFloPay)(pk, { billingApiUrl: resolvedBillingUrl, locale });
|
|
1695
|
-
if (!cancelled) {
|
|
1696
|
-
flopayRef.current = instance;
|
|
1697
|
-
setFloPay(instance);
|
|
1698
|
-
initializedHashRef.current = cacheKey;
|
|
1699
|
-
}
|
|
1980
|
+
await bootstrapInlineSession();
|
|
1700
1981
|
} catch (err) {
|
|
1701
1982
|
if (cancelled) return;
|
|
1702
|
-
const floPayErr = err instanceof
|
|
1983
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(err instanceof Error ? err.message : "Failed to create session", "api_error");
|
|
1703
1984
|
setLoadError(floPayErr);
|
|
1704
1985
|
}
|
|
1705
1986
|
})();
|
|
@@ -1717,7 +1998,7 @@ function FloPayCheckout({
|
|
|
1717
1998
|
const sess = result.data.session ?? null;
|
|
1718
1999
|
setSession(sess);
|
|
1719
2000
|
if (!sess) {
|
|
1720
|
-
throw new
|
|
2001
|
+
throw new import_shared6.FloPayError("No session data returned", "api_error");
|
|
1721
2002
|
}
|
|
1722
2003
|
if (sess.status === "complete") {
|
|
1723
2004
|
setIsLoading(false);
|
|
@@ -1756,7 +2037,7 @@ function FloPayCheckout({
|
|
|
1756
2037
|
if (!cancelled) setIsLoading(false);
|
|
1757
2038
|
} catch (err) {
|
|
1758
2039
|
if (cancelled) return;
|
|
1759
|
-
const floPayErr = err instanceof
|
|
2040
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(err instanceof Error ? err.message : "Failed to initialize checkout", "api_error");
|
|
1760
2041
|
setLoadError(floPayErr);
|
|
1761
2042
|
setIsLoading(false);
|
|
1762
2043
|
}
|
|
@@ -1768,7 +2049,7 @@ function FloPayCheckout({
|
|
|
1768
2049
|
}
|
|
1769
2050
|
if (!publishableKey) publishableKey = fallbackPublishableKey;
|
|
1770
2051
|
if (!publishableKey) {
|
|
1771
|
-
throw new
|
|
2052
|
+
throw new import_shared6.FloPayError(
|
|
1772
2053
|
"No publishable key found in session response. Provide a fallbackPublishableKey prop or ensure the session includes gatewayData.publishableKey.",
|
|
1773
2054
|
"validation_error"
|
|
1774
2055
|
);
|
|
@@ -1784,7 +2065,7 @@ function FloPayCheckout({
|
|
|
1784
2065
|
return () => {
|
|
1785
2066
|
cancelled = true;
|
|
1786
2067
|
};
|
|
1787
|
-
}, [resolvedSessionId, createSessionHash,
|
|
2068
|
+
}, [resolvedSessionId, createSessionHash, checkoutModeProp, deferInlineSessionUntilCardClick, bootstrapInlineSession]);
|
|
1788
2069
|
const handleConfirmCheckout = (0, import_react7.useCallback)(async () => {
|
|
1789
2070
|
if (confirmProcessing || !session) return;
|
|
1790
2071
|
setConfirmProcessing(true);
|
|
@@ -1799,17 +2080,18 @@ function FloPayCheckout({
|
|
|
1799
2080
|
setCurrentMode("full");
|
|
1800
2081
|
}
|
|
1801
2082
|
} catch (err) {
|
|
1802
|
-
const floPayErr = err instanceof
|
|
2083
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(
|
|
1803
2084
|
err instanceof Error ? err.message : "Payment failed",
|
|
1804
2085
|
"api_error"
|
|
1805
2086
|
);
|
|
1806
2087
|
setModeError(floPayErr.message);
|
|
1807
2088
|
onError?.(floPayErr);
|
|
2089
|
+
emitDecline("card", floPayErr);
|
|
1808
2090
|
setCurrentMode("full");
|
|
1809
2091
|
} finally {
|
|
1810
2092
|
setConfirmProcessing(false);
|
|
1811
2093
|
}
|
|
1812
|
-
}, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError]);
|
|
2094
|
+
}, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError, emitDecline]);
|
|
1813
2095
|
const providerOptions = (0, import_react7.useMemo)(() => {
|
|
1814
2096
|
if (!unified || !session) return void 0;
|
|
1815
2097
|
const opts = {
|
|
@@ -1820,7 +2102,7 @@ function FloPayCheckout({
|
|
|
1820
2102
|
if (unified.provider === "stripe" && unified.data.stripe?.clientSecret) {
|
|
1821
2103
|
opts.clientSecret = unified.data.stripe.clientSecret;
|
|
1822
2104
|
} else {
|
|
1823
|
-
const displayTotal = (0,
|
|
2105
|
+
const displayTotal = (0, import_shared6.buildCheckoutDisplayData)(session).total;
|
|
1824
2106
|
opts.amount = Math.round(displayTotal * 100) || session.amount;
|
|
1825
2107
|
opts.currency = session.currency?.toLowerCase();
|
|
1826
2108
|
}
|
|
@@ -1835,6 +2117,8 @@ function FloPayCheckout({
|
|
|
1835
2117
|
}),
|
|
1836
2118
|
[session, isLoading, loadError, currentMode]
|
|
1837
2119
|
);
|
|
2120
|
+
const shouldShowInterimButtons = Boolean(createSessionParams) && layout === "buttons" && (!flopay || !providerOptions);
|
|
2121
|
+
const shouldKeepDeferredInterimVisible = shouldShowInterimButtons && deferInlineSessionUntilCardClick;
|
|
1838
2122
|
if (isLoading) {
|
|
1839
2123
|
if (loadingNode) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: loadingNode });
|
|
1840
2124
|
if (layout === "buttons") {
|
|
@@ -1863,7 +2147,7 @@ function FloPayCheckout({
|
|
|
1863
2147
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
|
|
1864
2148
|
] });
|
|
1865
2149
|
}
|
|
1866
|
-
if (loadError) {
|
|
2150
|
+
if (loadError && !shouldKeepDeferredInterimVisible) {
|
|
1867
2151
|
if (errorNode) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: errorNode(loadError) });
|
|
1868
2152
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1869
2153
|
"div",
|
|
@@ -1878,14 +2162,18 @@ function FloPayCheckout({
|
|
|
1878
2162
|
}
|
|
1879
2163
|
);
|
|
1880
2164
|
}
|
|
1881
|
-
if (
|
|
2165
|
+
if (shouldShowInterimButtons) {
|
|
1882
2166
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1883
2167
|
InterimButtonsView,
|
|
1884
2168
|
{
|
|
1885
2169
|
onButtonClick,
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
2170
|
+
onCardButtonClick: deferInlineSessionUntilCardClick ? handleDeferredCardButtonClick : void 0,
|
|
2171
|
+
cardLoading: cardBootstrapPending,
|
|
2172
|
+
cardOpen: deferInlineSessionUntilCardClick ? deferredCardOpen : void 0,
|
|
2173
|
+
errorMessage: shouldKeepDeferredInterimVisible ? loadError?.message ?? null : null,
|
|
2174
|
+
showPayPal: deferInlineSessionUntilCardClick ? false : showPayPal,
|
|
2175
|
+
showApplePay: deferInlineSessionUntilCardClick ? false : showApplePay,
|
|
2176
|
+
showGooglePay: deferInlineSessionUntilCardClick ? false : showGooglePay,
|
|
1889
2177
|
buttonsTheme,
|
|
1890
2178
|
buttonsStyles,
|
|
1891
2179
|
cardButtonContent,
|
|
@@ -1967,10 +2255,11 @@ function FloPayCheckout({
|
|
|
1967
2255
|
userId: session?.customer?.id,
|
|
1968
2256
|
firstName: session?.customer?.firstName,
|
|
1969
2257
|
lastName: session?.customer?.lastName,
|
|
1970
|
-
totalAmount: session ? Math.round((0,
|
|
2258
|
+
totalAmount: session ? Math.round((0, import_shared6.buildCheckoutDisplayData)(session).total * 100) : 0,
|
|
1971
2259
|
currency: session?.currency?.toLowerCase() ?? "usd",
|
|
1972
2260
|
onComplete,
|
|
1973
2261
|
onError,
|
|
2262
|
+
onDecline,
|
|
1974
2263
|
showPayPal,
|
|
1975
2264
|
showApplePay,
|
|
1976
2265
|
showGooglePay,
|
|
@@ -1981,10 +2270,11 @@ function FloPayCheckout({
|
|
|
1981
2270
|
cardBackButtonContent,
|
|
1982
2271
|
cardTitleContent,
|
|
1983
2272
|
onButtonClick,
|
|
2273
|
+
onBeforeButtonClick,
|
|
1984
2274
|
enableAVS,
|
|
1985
2275
|
avsLayout,
|
|
1986
2276
|
country: session?.customer?.country,
|
|
1987
|
-
|
|
2277
|
+
initialCardOpen: deferredCardOpen,
|
|
1988
2278
|
submitLabel,
|
|
1989
2279
|
className
|
|
1990
2280
|
}
|
|
@@ -2017,6 +2307,10 @@ function SessionInjector({
|
|
|
2017
2307
|
}
|
|
2018
2308
|
function InterimButtonsView({
|
|
2019
2309
|
onButtonClick,
|
|
2310
|
+
onCardButtonClick,
|
|
2311
|
+
cardLoading = false,
|
|
2312
|
+
cardOpen,
|
|
2313
|
+
errorMessage,
|
|
2020
2314
|
showPayPal,
|
|
2021
2315
|
showApplePay,
|
|
2022
2316
|
showGooglePay,
|
|
@@ -2027,8 +2321,14 @@ function InterimButtonsView({
|
|
|
2027
2321
|
cardTitleContent
|
|
2028
2322
|
}) {
|
|
2029
2323
|
const [showCardForm, setShowCardForm] = (0, import_react7.useState)(false);
|
|
2324
|
+
const isCardOpenControlled = typeof cardOpen === "boolean";
|
|
2325
|
+
(0, import_react7.useEffect)(() => {
|
|
2326
|
+
if (isCardOpenControlled) {
|
|
2327
|
+
setShowCardForm(cardOpen);
|
|
2328
|
+
}
|
|
2329
|
+
}, [cardOpen, isCardOpenControlled]);
|
|
2030
2330
|
const bStyles = (0, import_react7.useMemo)(() => {
|
|
2031
|
-
const base = (0,
|
|
2331
|
+
const base = (0, import_shared6.resolveButtonsLayoutTheme)(buttonsTheme);
|
|
2032
2332
|
if (!stylesOverride) return base;
|
|
2033
2333
|
return {
|
|
2034
2334
|
...base,
|
|
@@ -2064,20 +2364,26 @@ function InterimButtonsView({
|
|
|
2064
2364
|
"button",
|
|
2065
2365
|
{
|
|
2066
2366
|
type: "button",
|
|
2067
|
-
onClick: () =>
|
|
2367
|
+
onClick: () => {
|
|
2368
|
+
if (!isCardOpenControlled) {
|
|
2369
|
+
setShowCardForm(false);
|
|
2370
|
+
}
|
|
2371
|
+
},
|
|
2068
2372
|
"aria-label": "Back to payment methods",
|
|
2373
|
+
disabled: isCardOpenControlled || cardLoading,
|
|
2069
2374
|
style: {
|
|
2070
2375
|
display: "inline-flex",
|
|
2071
2376
|
alignItems: "center",
|
|
2072
2377
|
gap: hideBackButtonLabel ? 0 : "0.5rem",
|
|
2073
2378
|
background: "none",
|
|
2074
2379
|
border: "none",
|
|
2075
|
-
cursor: "pointer",
|
|
2076
2380
|
color: "#4b5563",
|
|
2077
2381
|
fontSize: "0.85rem",
|
|
2078
2382
|
fontWeight: 500,
|
|
2079
2383
|
padding: 0,
|
|
2080
2384
|
flexShrink: 0,
|
|
2385
|
+
opacity: isCardOpenControlled || cardLoading ? 0.6 : 1,
|
|
2386
|
+
cursor: isCardOpenControlled || cardLoading ? "not-allowed" : "pointer",
|
|
2081
2387
|
...bStyles.backButton
|
|
2082
2388
|
},
|
|
2083
2389
|
children: [
|
|
@@ -2137,10 +2443,16 @@ function InterimButtonsView({
|
|
|
2137
2443
|
"button",
|
|
2138
2444
|
{
|
|
2139
2445
|
type: "button",
|
|
2140
|
-
onClick: () => {
|
|
2446
|
+
onClick: async () => {
|
|
2447
|
+
if (cardLoading) return;
|
|
2448
|
+
if (onCardButtonClick) {
|
|
2449
|
+
await onCardButtonClick();
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2141
2452
|
onButtonClick?.("card");
|
|
2142
2453
|
setShowCardForm(true);
|
|
2143
2454
|
},
|
|
2455
|
+
disabled: cardLoading,
|
|
2144
2456
|
style: {
|
|
2145
2457
|
width: "100%",
|
|
2146
2458
|
padding: "0.9rem 1rem",
|
|
@@ -2150,7 +2462,7 @@ function InterimButtonsView({
|
|
|
2150
2462
|
borderRadius: "8px",
|
|
2151
2463
|
fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
|
|
2152
2464
|
fontWeight: 600,
|
|
2153
|
-
cursor: "pointer",
|
|
2465
|
+
cursor: cardLoading ? "not-allowed" : "pointer",
|
|
2154
2466
|
display: "flex",
|
|
2155
2467
|
alignItems: "center",
|
|
2156
2468
|
justifyContent: "center",
|
|
@@ -2158,6 +2470,7 @@ function InterimButtonsView({
|
|
|
2158
2470
|
boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
|
|
2159
2471
|
transition: "transform 0.1s",
|
|
2160
2472
|
position: "relative",
|
|
2473
|
+
opacity: cardLoading ? 0.6 : 1,
|
|
2161
2474
|
...bStyles.cardButton
|
|
2162
2475
|
},
|
|
2163
2476
|
onMouseDown: (e) => {
|
|
@@ -2169,12 +2482,29 @@ function InterimButtonsView({
|
|
|
2169
2482
|
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CardButtonContentSlot, { content: cardButtonContent })
|
|
2170
2483
|
}
|
|
2171
2484
|
),
|
|
2485
|
+
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
|
|
2486
|
+
margin: "0.25rem 0",
|
|
2487
|
+
padding: "0.625rem 0.875rem",
|
|
2488
|
+
background: "#FEF2F2",
|
|
2489
|
+
border: "1px solid #FECACA",
|
|
2490
|
+
borderRadius: "8px",
|
|
2491
|
+
color: "#991B1B",
|
|
2492
|
+
fontSize: "0.85rem",
|
|
2493
|
+
fontWeight: 600,
|
|
2494
|
+
display: "flex",
|
|
2495
|
+
alignItems: "center",
|
|
2496
|
+
gap: "0.5rem",
|
|
2497
|
+
...bStyles.errorBanner
|
|
2498
|
+
}, children: [
|
|
2499
|
+
/* @__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" }) }),
|
|
2500
|
+
errorMessage
|
|
2501
|
+
] }),
|
|
2172
2502
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
|
|
2173
2503
|
] });
|
|
2174
2504
|
}
|
|
2175
2505
|
|
|
2176
2506
|
// src/checkout-form.tsx
|
|
2177
|
-
var
|
|
2507
|
+
var import_shared7 = require("@flopay/shared");
|
|
2178
2508
|
var import_react8 = require("react");
|
|
2179
2509
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2180
2510
|
var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
|
|
@@ -2190,6 +2520,7 @@ function CheckoutFormInner({
|
|
|
2190
2520
|
userId,
|
|
2191
2521
|
onComplete,
|
|
2192
2522
|
onError,
|
|
2523
|
+
onDecline,
|
|
2193
2524
|
onTokenizedBody,
|
|
2194
2525
|
layout = "auto",
|
|
2195
2526
|
submitLabel = "Pay",
|
|
@@ -2221,6 +2552,12 @@ function CheckoutFormInner({
|
|
|
2221
2552
|
},
|
|
2222
2553
|
[onErrorChange]
|
|
2223
2554
|
);
|
|
2555
|
+
const emitDecline = (0, import_react8.useCallback)(
|
|
2556
|
+
(input, overrides) => {
|
|
2557
|
+
onDecline?.(buildDeclineEvent("card", input, overrides));
|
|
2558
|
+
},
|
|
2559
|
+
[onDecline]
|
|
2560
|
+
);
|
|
2224
2561
|
const processPaymentInternal = (0, import_react8.useCallback)(
|
|
2225
2562
|
async (tokenizedBody) => {
|
|
2226
2563
|
setProcessing(true);
|
|
@@ -2267,6 +2604,7 @@ function CheckoutFormInner({
|
|
|
2267
2604
|
if (result.error) {
|
|
2268
2605
|
updateError(result.error.message);
|
|
2269
2606
|
onError?.(result.error);
|
|
2607
|
+
emitDecline(result.error);
|
|
2270
2608
|
return;
|
|
2271
2609
|
}
|
|
2272
2610
|
if (result.status === "succeeded" || result.status === "processing") {
|
|
@@ -2301,13 +2639,17 @@ function CheckoutFormInner({
|
|
|
2301
2639
|
}
|
|
2302
2640
|
const errorMessage = json?.message ?? "Payment failed. Please try again.";
|
|
2303
2641
|
updateError(errorMessage);
|
|
2642
|
+
emitDecline(errorMessage, {
|
|
2643
|
+
code: json?.code,
|
|
2644
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
2645
|
+
});
|
|
2304
2646
|
} catch (err) {
|
|
2305
2647
|
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
2306
2648
|
} finally {
|
|
2307
2649
|
setProcessing(false);
|
|
2308
2650
|
}
|
|
2309
2651
|
},
|
|
2310
|
-
[baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError]
|
|
2652
|
+
[baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError, emitDecline]
|
|
2311
2653
|
);
|
|
2312
2654
|
const dispatchTokenizedBody = (0, import_react8.useCallback)(
|
|
2313
2655
|
(tokenizedBody) => {
|
|
@@ -2331,6 +2673,7 @@ function CheckoutFormInner({
|
|
|
2331
2673
|
if (result.error) {
|
|
2332
2674
|
updateError(result.error.message);
|
|
2333
2675
|
onError?.(result.error);
|
|
2676
|
+
emitDecline(result.error);
|
|
2334
2677
|
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
2335
2678
|
dispatchTokenizedBody({
|
|
2336
2679
|
id: result.paymentIntentId,
|
|
@@ -2344,7 +2687,7 @@ function CheckoutFormInner({
|
|
|
2344
2687
|
setIs3DSActive(false);
|
|
2345
2688
|
}
|
|
2346
2689
|
}
|
|
2347
|
-
}), [flopay, dispatchTokenizedBody, onError, updateError]);
|
|
2690
|
+
}), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
|
|
2348
2691
|
(0, import_react8.useEffect)(() => {
|
|
2349
2692
|
if (typeof window === "undefined") return;
|
|
2350
2693
|
const stored = localStorage.getItem(WALLET_RESUME_KEY2);
|
|
@@ -2382,7 +2725,7 @@ function CheckoutFormInner({
|
|
|
2382
2725
|
return;
|
|
2383
2726
|
}
|
|
2384
2727
|
if (!sessionId || !email) {
|
|
2385
|
-
throw new
|
|
2728
|
+
throw new import_shared7.FloPayError("Missing sessionId or email", "validation_error");
|
|
2386
2729
|
}
|
|
2387
2730
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
2388
2731
|
method: "POST",
|
|
@@ -2394,16 +2737,18 @@ function CheckoutFormInner({
|
|
|
2394
2737
|
isPaypal: false
|
|
2395
2738
|
})
|
|
2396
2739
|
});
|
|
2397
|
-
if (!intentResponse.ok) throw new
|
|
2740
|
+
if (!intentResponse.ok) throw new import_shared7.FloPayError("Failed to create payment intent", "api_error");
|
|
2398
2741
|
const intentJson = await intentResponse.json();
|
|
2399
2742
|
const intentClientSecret = intentJson.data?.id;
|
|
2400
|
-
if (!intentClientSecret) throw new
|
|
2743
|
+
if (!intentClientSecret) throw new import_shared7.FloPayError("No client_secret in payment intent response", "api_error");
|
|
2401
2744
|
const confirmResult = await flopay.confirmCardPayment({
|
|
2402
2745
|
clientSecret: intentClientSecret,
|
|
2403
2746
|
paymentMethodId: pmResult.paymentMethodId
|
|
2404
2747
|
});
|
|
2405
2748
|
if (confirmResult.error) {
|
|
2406
2749
|
updateError(confirmResult.error.message);
|
|
2750
|
+
onError?.(confirmResult.error);
|
|
2751
|
+
emitDecline(confirmResult.error);
|
|
2407
2752
|
return;
|
|
2408
2753
|
}
|
|
2409
2754
|
dispatchTokenizedBody({
|
|
@@ -2420,7 +2765,7 @@ function CheckoutFormInner({
|
|
|
2420
2765
|
}
|
|
2421
2766
|
}
|
|
2422
2767
|
},
|
|
2423
|
-
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
|
|
2768
|
+
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError, emitDecline]
|
|
2424
2769
|
);
|
|
2425
2770
|
const isReady = flopay !== null && elements !== null;
|
|
2426
2771
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
@@ -2452,7 +2797,7 @@ function CheckoutFormInner({
|
|
|
2452
2797
|
}
|
|
2453
2798
|
|
|
2454
2799
|
// src/paypal-button.tsx
|
|
2455
|
-
var
|
|
2800
|
+
var import_shared8 = require("@flopay/shared");
|
|
2456
2801
|
var import_react9 = require("react");
|
|
2457
2802
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2458
2803
|
function PayPalButton({
|
|
@@ -2572,7 +2917,7 @@ function PayPalButton({
|
|
|
2572
2917
|
setSubmitting(true);
|
|
2573
2918
|
onErrorChange?.(null);
|
|
2574
2919
|
if (!sessionId || !email) {
|
|
2575
|
-
throw new
|
|
2920
|
+
throw new import_shared8.FloPayError("Missing sessionId or email for PayPal payment", "validation_error");
|
|
2576
2921
|
}
|
|
2577
2922
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
2578
2923
|
method: "POST",
|
|
@@ -2584,10 +2929,10 @@ function PayPalButton({
|
|
|
2584
2929
|
isPaypal: "true"
|
|
2585
2930
|
})
|
|
2586
2931
|
});
|
|
2587
|
-
if (!intentResponse.ok) throw new
|
|
2932
|
+
if (!intentResponse.ok) throw new import_shared8.FloPayError("Failed to create payment intent", "api_error");
|
|
2588
2933
|
const intentJson = await intentResponse.json();
|
|
2589
2934
|
const intentClientSecret = intentJson.data?.id;
|
|
2590
|
-
if (!intentClientSecret) throw new
|
|
2935
|
+
if (!intentClientSecret) throw new import_shared8.FloPayError("No client_secret in response", "api_error");
|
|
2591
2936
|
const result = await flopay.confirmPayment({
|
|
2592
2937
|
clientSecret: intentClientSecret,
|
|
2593
2938
|
returnUrl: window.location.href
|