@flopay/react 0.3.11 → 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/README.md +39 -0
- package/dist/index.cjs +526 -195
- 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 +517 -186
- 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: {
|
|
@@ -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,28 @@ 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 [cardBootstrapPending, setCardBootstrapPending] = (0, import_react7.useState)(false);
|
|
1662
|
+
const [deferredCardOpen, setDeferredCardOpen] = (0, import_react7.useState)(false);
|
|
1456
1663
|
const autoCheckoutAttempted = (0, import_react7.useRef)(false);
|
|
1457
1664
|
const onCompleteRef = (0, import_react7.useRef)(onComplete);
|
|
1458
1665
|
onCompleteRef.current = onComplete;
|
|
1459
1666
|
const onErrorRef = (0, import_react7.useRef)(onError);
|
|
1460
1667
|
onErrorRef.current = onError;
|
|
1668
|
+
const onDeclineRef = (0, import_react7.useRef)(onDecline);
|
|
1669
|
+
onDeclineRef.current = onDecline;
|
|
1461
1670
|
const onSessionCompletedRef = (0, import_react7.useRef)(onSessionCompleted);
|
|
1462
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
|
+
);
|
|
1463
1682
|
const processPaymentForMode = (0, import_react7.useCallback)(
|
|
1464
1683
|
async (sess) => {
|
|
1465
1684
|
const baseUrl = resolvedBillingUrl.replace(/\/+$/, "");
|
|
@@ -1499,9 +1718,13 @@ function FloPayCheckout({
|
|
|
1499
1718
|
// No client secret available
|
|
1500
1719
|
};
|
|
1501
1720
|
}
|
|
1502
|
-
throw new
|
|
1721
|
+
throw new import_shared6.FloPayError(
|
|
1503
1722
|
json?.message ?? "Payment failed. Please try again.",
|
|
1504
|
-
"api_error"
|
|
1723
|
+
"api_error",
|
|
1724
|
+
{
|
|
1725
|
+
code: json?.code ?? json?.gatewayErrorCode,
|
|
1726
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
1727
|
+
}
|
|
1505
1728
|
);
|
|
1506
1729
|
},
|
|
1507
1730
|
[resolvedBillingUrl, resolvedSessionId]
|
|
@@ -1520,6 +1743,9 @@ function FloPayCheckout({
|
|
|
1520
1743
|
});
|
|
1521
1744
|
if (nextActionError) {
|
|
1522
1745
|
setModeError(nextActionError.message ?? "3DS authentication failed.");
|
|
1746
|
+
emitDecline("card", nextActionError.message ?? "3DS authentication failed.", {
|
|
1747
|
+
code: nextActionError.code
|
|
1748
|
+
});
|
|
1523
1749
|
return false;
|
|
1524
1750
|
}
|
|
1525
1751
|
if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded")) {
|
|
@@ -1542,6 +1768,9 @@ function FloPayCheckout({
|
|
|
1542
1768
|
});
|
|
1543
1769
|
if (error) {
|
|
1544
1770
|
setModeError(error.message ?? "PayPal authorization failed.");
|
|
1771
|
+
emitDecline("paypal", error.message ?? "PayPal authorization failed.", {
|
|
1772
|
+
code: error.code
|
|
1773
|
+
});
|
|
1545
1774
|
return false;
|
|
1546
1775
|
}
|
|
1547
1776
|
onCompleteRef.current?.({ status: "succeeded" });
|
|
@@ -1549,16 +1778,24 @@ function FloPayCheckout({
|
|
|
1549
1778
|
}
|
|
1550
1779
|
return false;
|
|
1551
1780
|
},
|
|
1552
|
-
[resolvedBillingUrl, resolvedSessionId]
|
|
1781
|
+
[resolvedBillingUrl, resolvedSessionId, emitDecline]
|
|
1553
1782
|
);
|
|
1554
1783
|
const inflightRef = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
|
|
1555
1784
|
const initializedHashRef = (0, import_react7.useRef)(null);
|
|
1785
|
+
const deferInlineSessionUntilCardClick = Boolean(
|
|
1786
|
+
effectiveCreateSession && !children && layout === "buttons" && onBeforeButtonClick && (effectiveCreateSession.checkoutMode ?? checkoutModeProp ?? "full") === "full"
|
|
1787
|
+
);
|
|
1556
1788
|
function hashCreateParams(params) {
|
|
1557
1789
|
const key = JSON.stringify({
|
|
1558
1790
|
c: params?.clientId,
|
|
1791
|
+
successUrl: params?.successUrl,
|
|
1792
|
+
cancelUrl: params?.cancelUrl,
|
|
1559
1793
|
i: params?.items?.map((x) => `${x.providerItemId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
|
|
1560
1794
|
s: params?.subscriptions?.map((x) => `${x.providerPlanId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
|
|
1561
|
-
|
|
1795
|
+
account: params?.account,
|
|
1796
|
+
couponCodes: params?.couponCodes,
|
|
1797
|
+
tagsData: params?.tagsData,
|
|
1798
|
+
utmMetadata: params?.utmMetadata,
|
|
1562
1799
|
m: params?.checkoutMode ?? "full"
|
|
1563
1800
|
});
|
|
1564
1801
|
let h = 0;
|
|
@@ -1568,138 +1805,167 @@ function FloPayCheckout({
|
|
|
1568
1805
|
return `flopay_session_${Math.abs(h).toString(36)}`;
|
|
1569
1806
|
}
|
|
1570
1807
|
const createSessionHash = (0, import_react7.useMemo)(
|
|
1571
|
-
() =>
|
|
1572
|
-
|
|
1573
|
-
[
|
|
1574
|
-
createSessionParams?.clientId,
|
|
1575
|
-
createSessionParams?.account?.email,
|
|
1576
|
-
createSessionParams?.checkoutMode,
|
|
1577
|
-
JSON.stringify(createSessionParams?.items),
|
|
1578
|
-
JSON.stringify(createSessionParams?.subscriptions)
|
|
1579
|
-
]
|
|
1808
|
+
() => effectiveCreateSession ? hashCreateParams(effectiveCreateSession) : "",
|
|
1809
|
+
[effectiveCreateSession]
|
|
1580
1810
|
);
|
|
1581
|
-
const createSessionParamsRef = (0, import_react7.useRef)(
|
|
1582
|
-
createSessionParamsRef.current =
|
|
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]
|
|
1895
|
+
);
|
|
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
|
+
]);
|
|
1583
1939
|
(0, import_react7.useEffect)(() => {
|
|
1584
1940
|
let cancelled = false;
|
|
1585
1941
|
setLoadError(null);
|
|
1586
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
|
+
}
|
|
1587
1958
|
if (initializedHashRef.current === createSessionHash) return;
|
|
1588
1959
|
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);
|
|
1960
|
+
setSession(buildSyntheticSession(params, checkoutModeProp));
|
|
1632
1961
|
setCurrentMode(params.checkoutMode ?? checkoutModeProp ?? "full");
|
|
1633
1962
|
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
1963
|
(async () => {
|
|
1662
1964
|
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
|
-
}
|
|
1965
|
+
await bootstrapInlineSession();
|
|
1700
1966
|
} catch (err) {
|
|
1701
1967
|
if (cancelled) return;
|
|
1702
|
-
const floPayErr = err instanceof
|
|
1968
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(err instanceof Error ? err.message : "Failed to create session", "api_error");
|
|
1703
1969
|
setLoadError(floPayErr);
|
|
1704
1970
|
}
|
|
1705
1971
|
})();
|
|
@@ -1717,7 +1983,7 @@ function FloPayCheckout({
|
|
|
1717
1983
|
const sess = result.data.session ?? null;
|
|
1718
1984
|
setSession(sess);
|
|
1719
1985
|
if (!sess) {
|
|
1720
|
-
throw new
|
|
1986
|
+
throw new import_shared6.FloPayError("No session data returned", "api_error");
|
|
1721
1987
|
}
|
|
1722
1988
|
if (sess.status === "complete") {
|
|
1723
1989
|
setIsLoading(false);
|
|
@@ -1756,7 +2022,7 @@ function FloPayCheckout({
|
|
|
1756
2022
|
if (!cancelled) setIsLoading(false);
|
|
1757
2023
|
} catch (err) {
|
|
1758
2024
|
if (cancelled) return;
|
|
1759
|
-
const floPayErr = err instanceof
|
|
2025
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(err instanceof Error ? err.message : "Failed to initialize checkout", "api_error");
|
|
1760
2026
|
setLoadError(floPayErr);
|
|
1761
2027
|
setIsLoading(false);
|
|
1762
2028
|
}
|
|
@@ -1768,7 +2034,7 @@ function FloPayCheckout({
|
|
|
1768
2034
|
}
|
|
1769
2035
|
if (!publishableKey) publishableKey = fallbackPublishableKey;
|
|
1770
2036
|
if (!publishableKey) {
|
|
1771
|
-
throw new
|
|
2037
|
+
throw new import_shared6.FloPayError(
|
|
1772
2038
|
"No publishable key found in session response. Provide a fallbackPublishableKey prop or ensure the session includes gatewayData.publishableKey.",
|
|
1773
2039
|
"validation_error"
|
|
1774
2040
|
);
|
|
@@ -1784,7 +2050,7 @@ function FloPayCheckout({
|
|
|
1784
2050
|
return () => {
|
|
1785
2051
|
cancelled = true;
|
|
1786
2052
|
};
|
|
1787
|
-
}, [resolvedSessionId, createSessionHash,
|
|
2053
|
+
}, [resolvedSessionId, createSessionHash, checkoutModeProp, deferInlineSessionUntilCardClick, bootstrapInlineSession]);
|
|
1788
2054
|
const handleConfirmCheckout = (0, import_react7.useCallback)(async () => {
|
|
1789
2055
|
if (confirmProcessing || !session) return;
|
|
1790
2056
|
setConfirmProcessing(true);
|
|
@@ -1799,17 +2065,18 @@ function FloPayCheckout({
|
|
|
1799
2065
|
setCurrentMode("full");
|
|
1800
2066
|
}
|
|
1801
2067
|
} catch (err) {
|
|
1802
|
-
const floPayErr = err instanceof
|
|
2068
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(
|
|
1803
2069
|
err instanceof Error ? err.message : "Payment failed",
|
|
1804
2070
|
"api_error"
|
|
1805
2071
|
);
|
|
1806
2072
|
setModeError(floPayErr.message);
|
|
1807
2073
|
onError?.(floPayErr);
|
|
2074
|
+
emitDecline("card", floPayErr);
|
|
1808
2075
|
setCurrentMode("full");
|
|
1809
2076
|
} finally {
|
|
1810
2077
|
setConfirmProcessing(false);
|
|
1811
2078
|
}
|
|
1812
|
-
}, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError]);
|
|
2079
|
+
}, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError, emitDecline]);
|
|
1813
2080
|
const providerOptions = (0, import_react7.useMemo)(() => {
|
|
1814
2081
|
if (!unified || !session) return void 0;
|
|
1815
2082
|
const opts = {
|
|
@@ -1820,7 +2087,7 @@ function FloPayCheckout({
|
|
|
1820
2087
|
if (unified.provider === "stripe" && unified.data.stripe?.clientSecret) {
|
|
1821
2088
|
opts.clientSecret = unified.data.stripe.clientSecret;
|
|
1822
2089
|
} else {
|
|
1823
|
-
const displayTotal = (0,
|
|
2090
|
+
const displayTotal = (0, import_shared6.buildCheckoutDisplayData)(session).total;
|
|
1824
2091
|
opts.amount = Math.round(displayTotal * 100) || session.amount;
|
|
1825
2092
|
opts.currency = session.currency?.toLowerCase();
|
|
1826
2093
|
}
|
|
@@ -1835,6 +2102,8 @@ function FloPayCheckout({
|
|
|
1835
2102
|
}),
|
|
1836
2103
|
[session, isLoading, loadError, currentMode]
|
|
1837
2104
|
);
|
|
2105
|
+
const shouldShowInterimButtons = Boolean(createSessionParams) && layout === "buttons" && (!flopay || !providerOptions);
|
|
2106
|
+
const shouldKeepDeferredInterimVisible = shouldShowInterimButtons && deferInlineSessionUntilCardClick;
|
|
1838
2107
|
if (isLoading) {
|
|
1839
2108
|
if (loadingNode) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: loadingNode });
|
|
1840
2109
|
if (layout === "buttons") {
|
|
@@ -1863,7 +2132,7 @@ function FloPayCheckout({
|
|
|
1863
2132
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
|
|
1864
2133
|
] });
|
|
1865
2134
|
}
|
|
1866
|
-
if (loadError) {
|
|
2135
|
+
if (loadError && !shouldKeepDeferredInterimVisible) {
|
|
1867
2136
|
if (errorNode) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: errorNode(loadError) });
|
|
1868
2137
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1869
2138
|
"div",
|
|
@@ -1878,14 +2147,18 @@ function FloPayCheckout({
|
|
|
1878
2147
|
}
|
|
1879
2148
|
);
|
|
1880
2149
|
}
|
|
1881
|
-
if (
|
|
2150
|
+
if (shouldShowInterimButtons) {
|
|
1882
2151
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1883
2152
|
InterimButtonsView,
|
|
1884
2153
|
{
|
|
1885
2154
|
onButtonClick,
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
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,
|
|
1889
2162
|
buttonsTheme,
|
|
1890
2163
|
buttonsStyles,
|
|
1891
2164
|
cardButtonContent,
|
|
@@ -1967,10 +2240,11 @@ function FloPayCheckout({
|
|
|
1967
2240
|
userId: session?.customer?.id,
|
|
1968
2241
|
firstName: session?.customer?.firstName,
|
|
1969
2242
|
lastName: session?.customer?.lastName,
|
|
1970
|
-
totalAmount: session ? Math.round((0,
|
|
2243
|
+
totalAmount: session ? Math.round((0, import_shared6.buildCheckoutDisplayData)(session).total * 100) : 0,
|
|
1971
2244
|
currency: session?.currency?.toLowerCase() ?? "usd",
|
|
1972
2245
|
onComplete,
|
|
1973
2246
|
onError,
|
|
2247
|
+
onDecline,
|
|
1974
2248
|
showPayPal,
|
|
1975
2249
|
showApplePay,
|
|
1976
2250
|
showGooglePay,
|
|
@@ -1981,10 +2255,12 @@ function FloPayCheckout({
|
|
|
1981
2255
|
cardBackButtonContent,
|
|
1982
2256
|
cardTitleContent,
|
|
1983
2257
|
onButtonClick,
|
|
2258
|
+
onBeforeButtonClick,
|
|
1984
2259
|
enableAVS,
|
|
1985
2260
|
avsLayout,
|
|
1986
2261
|
country: session?.customer?.country,
|
|
1987
2262
|
zip: session?.customer?.zip,
|
|
2263
|
+
initialCardOpen: deferredCardOpen,
|
|
1988
2264
|
submitLabel,
|
|
1989
2265
|
className
|
|
1990
2266
|
}
|
|
@@ -2017,6 +2293,10 @@ function SessionInjector({
|
|
|
2017
2293
|
}
|
|
2018
2294
|
function InterimButtonsView({
|
|
2019
2295
|
onButtonClick,
|
|
2296
|
+
onCardButtonClick,
|
|
2297
|
+
cardLoading = false,
|
|
2298
|
+
cardOpen,
|
|
2299
|
+
errorMessage,
|
|
2020
2300
|
showPayPal,
|
|
2021
2301
|
showApplePay,
|
|
2022
2302
|
showGooglePay,
|
|
@@ -2027,8 +2307,14 @@ function InterimButtonsView({
|
|
|
2027
2307
|
cardTitleContent
|
|
2028
2308
|
}) {
|
|
2029
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]);
|
|
2030
2316
|
const bStyles = (0, import_react7.useMemo)(() => {
|
|
2031
|
-
const base = (0,
|
|
2317
|
+
const base = (0, import_shared6.resolveButtonsLayoutTheme)(buttonsTheme);
|
|
2032
2318
|
if (!stylesOverride) return base;
|
|
2033
2319
|
return {
|
|
2034
2320
|
...base,
|
|
@@ -2064,20 +2350,26 @@ function InterimButtonsView({
|
|
|
2064
2350
|
"button",
|
|
2065
2351
|
{
|
|
2066
2352
|
type: "button",
|
|
2067
|
-
onClick: () =>
|
|
2353
|
+
onClick: () => {
|
|
2354
|
+
if (!isCardOpenControlled) {
|
|
2355
|
+
setShowCardForm(false);
|
|
2356
|
+
}
|
|
2357
|
+
},
|
|
2068
2358
|
"aria-label": "Back to payment methods",
|
|
2359
|
+
disabled: isCardOpenControlled || cardLoading,
|
|
2069
2360
|
style: {
|
|
2070
2361
|
display: "inline-flex",
|
|
2071
2362
|
alignItems: "center",
|
|
2072
2363
|
gap: hideBackButtonLabel ? 0 : "0.5rem",
|
|
2073
2364
|
background: "none",
|
|
2074
2365
|
border: "none",
|
|
2075
|
-
cursor: "pointer",
|
|
2076
2366
|
color: "#4b5563",
|
|
2077
2367
|
fontSize: "0.85rem",
|
|
2078
2368
|
fontWeight: 500,
|
|
2079
2369
|
padding: 0,
|
|
2080
2370
|
flexShrink: 0,
|
|
2371
|
+
opacity: isCardOpenControlled || cardLoading ? 0.6 : 1,
|
|
2372
|
+
cursor: isCardOpenControlled || cardLoading ? "not-allowed" : "pointer",
|
|
2081
2373
|
...bStyles.backButton
|
|
2082
2374
|
},
|
|
2083
2375
|
children: [
|
|
@@ -2137,10 +2429,16 @@ function InterimButtonsView({
|
|
|
2137
2429
|
"button",
|
|
2138
2430
|
{
|
|
2139
2431
|
type: "button",
|
|
2140
|
-
onClick: () => {
|
|
2432
|
+
onClick: async () => {
|
|
2433
|
+
if (cardLoading) return;
|
|
2434
|
+
if (onCardButtonClick) {
|
|
2435
|
+
await onCardButtonClick();
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2141
2438
|
onButtonClick?.("card");
|
|
2142
2439
|
setShowCardForm(true);
|
|
2143
2440
|
},
|
|
2441
|
+
disabled: cardLoading,
|
|
2144
2442
|
style: {
|
|
2145
2443
|
width: "100%",
|
|
2146
2444
|
padding: "0.9rem 1rem",
|
|
@@ -2150,7 +2448,7 @@ function InterimButtonsView({
|
|
|
2150
2448
|
borderRadius: "8px",
|
|
2151
2449
|
fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
|
|
2152
2450
|
fontWeight: 600,
|
|
2153
|
-
cursor: "pointer",
|
|
2451
|
+
cursor: cardLoading ? "not-allowed" : "pointer",
|
|
2154
2452
|
display: "flex",
|
|
2155
2453
|
alignItems: "center",
|
|
2156
2454
|
justifyContent: "center",
|
|
@@ -2158,6 +2456,7 @@ function InterimButtonsView({
|
|
|
2158
2456
|
boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
|
|
2159
2457
|
transition: "transform 0.1s",
|
|
2160
2458
|
position: "relative",
|
|
2459
|
+
opacity: cardLoading ? 0.6 : 1,
|
|
2161
2460
|
...bStyles.cardButton
|
|
2162
2461
|
},
|
|
2163
2462
|
onMouseDown: (e) => {
|
|
@@ -2169,12 +2468,29 @@ function InterimButtonsView({
|
|
|
2169
2468
|
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CardButtonContentSlot, { content: cardButtonContent })
|
|
2170
2469
|
}
|
|
2171
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
|
+
] }),
|
|
2172
2488
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
|
|
2173
2489
|
] });
|
|
2174
2490
|
}
|
|
2175
2491
|
|
|
2176
2492
|
// src/checkout-form.tsx
|
|
2177
|
-
var
|
|
2493
|
+
var import_shared7 = require("@flopay/shared");
|
|
2178
2494
|
var import_react8 = require("react");
|
|
2179
2495
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2180
2496
|
var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
|
|
@@ -2190,6 +2506,7 @@ function CheckoutFormInner({
|
|
|
2190
2506
|
userId,
|
|
2191
2507
|
onComplete,
|
|
2192
2508
|
onError,
|
|
2509
|
+
onDecline,
|
|
2193
2510
|
onTokenizedBody,
|
|
2194
2511
|
layout = "auto",
|
|
2195
2512
|
submitLabel = "Pay",
|
|
@@ -2221,6 +2538,12 @@ function CheckoutFormInner({
|
|
|
2221
2538
|
},
|
|
2222
2539
|
[onErrorChange]
|
|
2223
2540
|
);
|
|
2541
|
+
const emitDecline = (0, import_react8.useCallback)(
|
|
2542
|
+
(input, overrides) => {
|
|
2543
|
+
onDecline?.(buildDeclineEvent("card", input, overrides));
|
|
2544
|
+
},
|
|
2545
|
+
[onDecline]
|
|
2546
|
+
);
|
|
2224
2547
|
const processPaymentInternal = (0, import_react8.useCallback)(
|
|
2225
2548
|
async (tokenizedBody) => {
|
|
2226
2549
|
setProcessing(true);
|
|
@@ -2267,6 +2590,7 @@ function CheckoutFormInner({
|
|
|
2267
2590
|
if (result.error) {
|
|
2268
2591
|
updateError(result.error.message);
|
|
2269
2592
|
onError?.(result.error);
|
|
2593
|
+
emitDecline(result.error);
|
|
2270
2594
|
return;
|
|
2271
2595
|
}
|
|
2272
2596
|
if (result.status === "succeeded" || result.status === "processing") {
|
|
@@ -2301,13 +2625,17 @@ function CheckoutFormInner({
|
|
|
2301
2625
|
}
|
|
2302
2626
|
const errorMessage = json?.message ?? "Payment failed. Please try again.";
|
|
2303
2627
|
updateError(errorMessage);
|
|
2628
|
+
emitDecline(errorMessage, {
|
|
2629
|
+
code: json?.code,
|
|
2630
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
2631
|
+
});
|
|
2304
2632
|
} catch (err) {
|
|
2305
2633
|
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
2306
2634
|
} finally {
|
|
2307
2635
|
setProcessing(false);
|
|
2308
2636
|
}
|
|
2309
2637
|
},
|
|
2310
|
-
[baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError]
|
|
2638
|
+
[baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError, emitDecline]
|
|
2311
2639
|
);
|
|
2312
2640
|
const dispatchTokenizedBody = (0, import_react8.useCallback)(
|
|
2313
2641
|
(tokenizedBody) => {
|
|
@@ -2331,6 +2659,7 @@ function CheckoutFormInner({
|
|
|
2331
2659
|
if (result.error) {
|
|
2332
2660
|
updateError(result.error.message);
|
|
2333
2661
|
onError?.(result.error);
|
|
2662
|
+
emitDecline(result.error);
|
|
2334
2663
|
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
2335
2664
|
dispatchTokenizedBody({
|
|
2336
2665
|
id: result.paymentIntentId,
|
|
@@ -2344,7 +2673,7 @@ function CheckoutFormInner({
|
|
|
2344
2673
|
setIs3DSActive(false);
|
|
2345
2674
|
}
|
|
2346
2675
|
}
|
|
2347
|
-
}), [flopay, dispatchTokenizedBody, onError, updateError]);
|
|
2676
|
+
}), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
|
|
2348
2677
|
(0, import_react8.useEffect)(() => {
|
|
2349
2678
|
if (typeof window === "undefined") return;
|
|
2350
2679
|
const stored = localStorage.getItem(WALLET_RESUME_KEY2);
|
|
@@ -2382,7 +2711,7 @@ function CheckoutFormInner({
|
|
|
2382
2711
|
return;
|
|
2383
2712
|
}
|
|
2384
2713
|
if (!sessionId || !email) {
|
|
2385
|
-
throw new
|
|
2714
|
+
throw new import_shared7.FloPayError("Missing sessionId or email", "validation_error");
|
|
2386
2715
|
}
|
|
2387
2716
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
2388
2717
|
method: "POST",
|
|
@@ -2394,16 +2723,18 @@ function CheckoutFormInner({
|
|
|
2394
2723
|
isPaypal: false
|
|
2395
2724
|
})
|
|
2396
2725
|
});
|
|
2397
|
-
if (!intentResponse.ok) throw new
|
|
2726
|
+
if (!intentResponse.ok) throw new import_shared7.FloPayError("Failed to create payment intent", "api_error");
|
|
2398
2727
|
const intentJson = await intentResponse.json();
|
|
2399
2728
|
const intentClientSecret = intentJson.data?.id;
|
|
2400
|
-
if (!intentClientSecret) throw new
|
|
2729
|
+
if (!intentClientSecret) throw new import_shared7.FloPayError("No client_secret in payment intent response", "api_error");
|
|
2401
2730
|
const confirmResult = await flopay.confirmCardPayment({
|
|
2402
2731
|
clientSecret: intentClientSecret,
|
|
2403
2732
|
paymentMethodId: pmResult.paymentMethodId
|
|
2404
2733
|
});
|
|
2405
2734
|
if (confirmResult.error) {
|
|
2406
2735
|
updateError(confirmResult.error.message);
|
|
2736
|
+
onError?.(confirmResult.error);
|
|
2737
|
+
emitDecline(confirmResult.error);
|
|
2407
2738
|
return;
|
|
2408
2739
|
}
|
|
2409
2740
|
dispatchTokenizedBody({
|
|
@@ -2420,7 +2751,7 @@ function CheckoutFormInner({
|
|
|
2420
2751
|
}
|
|
2421
2752
|
}
|
|
2422
2753
|
},
|
|
2423
|
-
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
|
|
2754
|
+
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError, emitDecline]
|
|
2424
2755
|
);
|
|
2425
2756
|
const isReady = flopay !== null && elements !== null;
|
|
2426
2757
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
@@ -2452,7 +2783,7 @@ function CheckoutFormInner({
|
|
|
2452
2783
|
}
|
|
2453
2784
|
|
|
2454
2785
|
// src/paypal-button.tsx
|
|
2455
|
-
var
|
|
2786
|
+
var import_shared8 = require("@flopay/shared");
|
|
2456
2787
|
var import_react9 = require("react");
|
|
2457
2788
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2458
2789
|
function PayPalButton({
|
|
@@ -2572,7 +2903,7 @@ function PayPalButton({
|
|
|
2572
2903
|
setSubmitting(true);
|
|
2573
2904
|
onErrorChange?.(null);
|
|
2574
2905
|
if (!sessionId || !email) {
|
|
2575
|
-
throw new
|
|
2906
|
+
throw new import_shared8.FloPayError("Missing sessionId or email for PayPal payment", "validation_error");
|
|
2576
2907
|
}
|
|
2577
2908
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
2578
2909
|
method: "POST",
|
|
@@ -2584,10 +2915,10 @@ function PayPalButton({
|
|
|
2584
2915
|
isPaypal: "true"
|
|
2585
2916
|
})
|
|
2586
2917
|
});
|
|
2587
|
-
if (!intentResponse.ok) throw new
|
|
2918
|
+
if (!intentResponse.ok) throw new import_shared8.FloPayError("Failed to create payment intent", "api_error");
|
|
2588
2919
|
const intentJson = await intentResponse.json();
|
|
2589
2920
|
const intentClientSecret = intentJson.data?.id;
|
|
2590
|
-
if (!intentClientSecret) throw new
|
|
2921
|
+
if (!intentClientSecret) throw new import_shared8.FloPayError("No client_secret in response", "api_error");
|
|
2591
2922
|
const result = await flopay.confirmPayment({
|
|
2592
2923
|
clientSecret: intentClientSecret,
|
|
2593
2924
|
returnUrl: window.location.href
|