@flopay/react 0.3.5 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/index.cjs +574 -207
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -5
- package/dist/index.d.ts +35 -5
- package/dist/index.mjs +565 -198
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -69,7 +69,7 @@ function FloPayProvider({
|
|
|
69
69
|
// src/flopay-checkout.tsx
|
|
70
70
|
import React5, { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
|
|
71
71
|
import { loadFloPay, PaymentAPI } from "@flopay/js";
|
|
72
|
-
import { FloPayError as
|
|
72
|
+
import { FloPayError as FloPayError3, resolveBillingApiUrl as resolveBillingApiUrl3, buildCheckoutDisplayData, resolveButtonsLayoutTheme as resolveButtonsLayoutTheme2 } from "@flopay/shared";
|
|
73
73
|
|
|
74
74
|
// src/card-button-content.tsx
|
|
75
75
|
import "react";
|
|
@@ -113,10 +113,29 @@ function DefaultCardButtonContent() {
|
|
|
113
113
|
)
|
|
114
114
|
] });
|
|
115
115
|
}
|
|
116
|
+
function DefaultBackButtonContent() {
|
|
117
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: "Go back" });
|
|
118
|
+
}
|
|
119
|
+
function DefaultTitleContent() {
|
|
120
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: "Secure card checkout" });
|
|
121
|
+
}
|
|
122
|
+
function isEmptySlotContent(content) {
|
|
123
|
+
return content !== void 0 && (content === "" || content === null || content === false);
|
|
124
|
+
}
|
|
116
125
|
function CardButtonContentSlot({
|
|
117
126
|
content
|
|
118
127
|
}) {
|
|
119
|
-
return /* @__PURE__ */ jsx2(Fragment, { children: content
|
|
128
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: content === void 0 ? /* @__PURE__ */ jsx2(DefaultCardButtonContent, {}) : content });
|
|
129
|
+
}
|
|
130
|
+
function BackButtonContentSlot({
|
|
131
|
+
content
|
|
132
|
+
}) {
|
|
133
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: content === void 0 ? /* @__PURE__ */ jsx2(DefaultBackButtonContent, {}) : content });
|
|
134
|
+
}
|
|
135
|
+
function TitleContentSlot({
|
|
136
|
+
content
|
|
137
|
+
}) {
|
|
138
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: content === void 0 ? /* @__PURE__ */ jsx2(DefaultTitleContent, {}) : content });
|
|
120
139
|
}
|
|
121
140
|
|
|
122
141
|
// src/elements.tsx
|
|
@@ -208,8 +227,116 @@ function useBillingApiUrl() {
|
|
|
208
227
|
return ctx.billingApiUrl || resolveBillingApiUrl2();
|
|
209
228
|
}
|
|
210
229
|
|
|
211
|
-
// src/
|
|
230
|
+
// src/checkout-utils.ts
|
|
212
231
|
import { FloPayError } from "@flopay/shared";
|
|
232
|
+
function mergeInlineSessionPatches(base, patch) {
|
|
233
|
+
if (!patch) return base;
|
|
234
|
+
if (!base) return patch;
|
|
235
|
+
return {
|
|
236
|
+
account: { ...base.account ?? {}, ...patch.account ?? {} },
|
|
237
|
+
couponCodes: patch.couponCodes ?? base.couponCodes,
|
|
238
|
+
tagsData: {
|
|
239
|
+
...base.tagsData ?? {},
|
|
240
|
+
...patch.tagsData ?? {}
|
|
241
|
+
},
|
|
242
|
+
utmMetadata: patch.utmMetadata ?? base.utmMetadata
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function mergeInlineSessionPatch(params, patch) {
|
|
246
|
+
if (!patch) return params;
|
|
247
|
+
return {
|
|
248
|
+
...params,
|
|
249
|
+
account: {
|
|
250
|
+
...params.account,
|
|
251
|
+
...patch.account ?? {}
|
|
252
|
+
},
|
|
253
|
+
couponCodes: patch.couponCodes ?? params.couponCodes,
|
|
254
|
+
tagsData: {
|
|
255
|
+
...params.tagsData ?? {},
|
|
256
|
+
...patch.tagsData ?? {}
|
|
257
|
+
},
|
|
258
|
+
utmMetadata: patch.utmMetadata ?? params.utmMetadata
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function buildSyntheticSession(params, checkoutModeOverride) {
|
|
262
|
+
const totalAmount = [
|
|
263
|
+
...(params.items ?? []).map((item) => item.overrideAmount ?? item.totalAmount ?? 0),
|
|
264
|
+
...(params.subscriptions ?? []).map((subscription) => subscription.overrideAmount ?? subscription.totalAmount ?? 0)
|
|
265
|
+
].reduce((sum, value) => sum + value, 0);
|
|
266
|
+
const currency = params.items?.[0]?.currency ?? params.subscriptions?.[0]?.currency ?? "USD";
|
|
267
|
+
return {
|
|
268
|
+
id: "",
|
|
269
|
+
clientSecret: "",
|
|
270
|
+
mode: "payment",
|
|
271
|
+
amount: Math.round(totalAmount * 100),
|
|
272
|
+
currency,
|
|
273
|
+
status: "open",
|
|
274
|
+
customer: {
|
|
275
|
+
id: params.account.userId,
|
|
276
|
+
email: params.account.email ?? "",
|
|
277
|
+
firstName: params.account.firstName,
|
|
278
|
+
lastName: params.account.lastName,
|
|
279
|
+
gender: params.account.gender ?? void 0,
|
|
280
|
+
city: params.account.city ?? void 0,
|
|
281
|
+
state: params.account.state ?? void 0,
|
|
282
|
+
country: params.account.country ?? void 0,
|
|
283
|
+
zip: params.account.zip ?? void 0
|
|
284
|
+
},
|
|
285
|
+
successUrl: params.successUrl,
|
|
286
|
+
cancelUrl: params.cancelUrl,
|
|
287
|
+
checkoutMode: params.checkoutMode ?? checkoutModeOverride ?? "full",
|
|
288
|
+
items: (params.items ?? []).map((item, idx) => ({
|
|
289
|
+
uuid: `synthetic-item-${idx}`,
|
|
290
|
+
checkoutSessionId: "",
|
|
291
|
+
providerItemId: item.providerItemId,
|
|
292
|
+
providerItemName: item.providerItemName ?? item.providerItemId,
|
|
293
|
+
quantity: item.quantity ?? 1,
|
|
294
|
+
totalAmount: item.totalAmount,
|
|
295
|
+
overrideAmount: item.overrideAmount ?? null,
|
|
296
|
+
currency: item.currency ?? currency
|
|
297
|
+
})),
|
|
298
|
+
subscriptions: (params.subscriptions ?? []).map((subscription, idx) => ({
|
|
299
|
+
uuid: `synthetic-sub-${idx}`,
|
|
300
|
+
checkoutSessionId: "",
|
|
301
|
+
providerPlanId: subscription.providerPlanId,
|
|
302
|
+
providerPlanName: subscription.providerPlanName ?? subscription.providerPlanId,
|
|
303
|
+
quantity: subscription.quantity ?? 1,
|
|
304
|
+
totalAmount: subscription.totalAmount,
|
|
305
|
+
overrideAmount: subscription.overrideAmount ?? null,
|
|
306
|
+
currency: subscription.currency ?? currency
|
|
307
|
+
}))
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function ensureInlineSessionReady(params) {
|
|
311
|
+
if (!params.account.email?.trim()) {
|
|
312
|
+
throw new FloPayError(
|
|
313
|
+
"Email is required before continuing with card checkout.",
|
|
314
|
+
"validation_error",
|
|
315
|
+
{ param: "createSession.account.email" }
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function mergeAccountPatch(base, patch) {
|
|
320
|
+
if (!patch) return base;
|
|
321
|
+
return {
|
|
322
|
+
...base,
|
|
323
|
+
...patch
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function buildDeclineEvent(method, input, overrides) {
|
|
327
|
+
const message = typeof input === "string" ? input : input.message;
|
|
328
|
+
const code = typeof input === "string" ? overrides?.code : overrides?.code ?? input.code;
|
|
329
|
+
const declineCode = typeof input === "string" ? overrides?.declineCode : overrides?.declineCode ?? input.declineCode;
|
|
330
|
+
return {
|
|
331
|
+
method,
|
|
332
|
+
message,
|
|
333
|
+
...code ? { code } : {},
|
|
334
|
+
...declineCode ? { declineCode } : {}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/split-card-form.tsx
|
|
339
|
+
import { FloPayError as FloPayError2 } from "@flopay/shared";
|
|
213
340
|
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
214
341
|
var WALLET_RESUME_KEY = "flopay_wallet_resume";
|
|
215
342
|
var FLOPAY_KEYFRAMES = `
|
|
@@ -347,7 +474,8 @@ function PayPalButtonInner({
|
|
|
347
474
|
onTokenizedBody,
|
|
348
475
|
onErrorChange,
|
|
349
476
|
isProcessing = false,
|
|
350
|
-
onButtonClick
|
|
477
|
+
onButtonClick,
|
|
478
|
+
onDecline
|
|
351
479
|
}) {
|
|
352
480
|
const stripe = useStripeRaw();
|
|
353
481
|
const elements = useStripeElements();
|
|
@@ -367,7 +495,9 @@ function PayPalButtonInner({
|
|
|
367
495
|
try {
|
|
368
496
|
setSubmitting(true);
|
|
369
497
|
if (redirectStatus === "failed") {
|
|
370
|
-
|
|
498
|
+
const message = "PayPal payment was declined. Please try again.";
|
|
499
|
+
onErrorChange?.(message);
|
|
500
|
+
onDecline?.(buildDeclineEvent("paypal", message));
|
|
371
501
|
return;
|
|
372
502
|
}
|
|
373
503
|
const { paymentIntent, error } = await stripe.retrievePaymentIntent(clientSecret);
|
|
@@ -389,7 +519,9 @@ function PayPalButtonInner({
|
|
|
389
519
|
url.searchParams.delete("redirect_status");
|
|
390
520
|
window.history.replaceState({}, "", url.toString());
|
|
391
521
|
} else {
|
|
392
|
-
|
|
522
|
+
const message = "PayPal payment was not completed. Please try again.";
|
|
523
|
+
onErrorChange?.(message);
|
|
524
|
+
onDecline?.(buildDeclineEvent("paypal", message));
|
|
393
525
|
}
|
|
394
526
|
} catch (err) {
|
|
395
527
|
onErrorChange?.(err instanceof Error ? err.message : "Failed to complete PayPal payment.");
|
|
@@ -397,7 +529,7 @@ function PayPalButtonInner({
|
|
|
397
529
|
setSubmitting(false);
|
|
398
530
|
}
|
|
399
531
|
})();
|
|
400
|
-
}, [stripe, onTokenizedBody, onErrorChange]);
|
|
532
|
+
}, [stripe, onTokenizedBody, onErrorChange, onDecline]);
|
|
401
533
|
const handlePayPalConfirm = useCallback(async (_event) => {
|
|
402
534
|
if (!stripe || !elements) return;
|
|
403
535
|
onButtonClick?.("paypal");
|
|
@@ -438,7 +570,11 @@ function PayPalButtonInner({
|
|
|
438
570
|
redirect: "if_required"
|
|
439
571
|
});
|
|
440
572
|
if (confirmError) {
|
|
441
|
-
|
|
573
|
+
const message = confirmError.message ?? "PayPal payment failed.";
|
|
574
|
+
onErrorChange?.(message);
|
|
575
|
+
onDecline?.(buildDeclineEvent("paypal", message, {
|
|
576
|
+
code: confirmError.code
|
|
577
|
+
}));
|
|
442
578
|
return;
|
|
443
579
|
}
|
|
444
580
|
const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
|
|
@@ -453,7 +589,7 @@ function PayPalButtonInner({
|
|
|
453
589
|
} finally {
|
|
454
590
|
setSubmitting(false);
|
|
455
591
|
}
|
|
456
|
-
}, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]);
|
|
592
|
+
}, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]);
|
|
457
593
|
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
458
594
|
/* @__PURE__ */ jsx4("div", { children: /* @__PURE__ */ jsx4(
|
|
459
595
|
ExpressCheckoutElement,
|
|
@@ -462,6 +598,9 @@ function PayPalButtonInner({
|
|
|
462
598
|
onLoadError: () => {
|
|
463
599
|
},
|
|
464
600
|
onConfirm: handlePayPalConfirm,
|
|
601
|
+
onCancel: () => {
|
|
602
|
+
onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
|
|
603
|
+
},
|
|
465
604
|
options: {
|
|
466
605
|
buttonType: { paypal: "paypal" },
|
|
467
606
|
paymentMethods: {
|
|
@@ -484,13 +623,15 @@ function WalletButtonInner({
|
|
|
484
623
|
showGooglePay = true,
|
|
485
624
|
onTokenizedBody,
|
|
486
625
|
onErrorChange,
|
|
487
|
-
onButtonClick
|
|
626
|
+
onButtonClick,
|
|
627
|
+
onDecline
|
|
488
628
|
}) {
|
|
489
629
|
const stripe = useStripeRaw();
|
|
490
630
|
const elements = useStripeElements();
|
|
491
631
|
const [ready, setReady] = useState2(false);
|
|
492
632
|
const [submitting, setSubmitting] = useState2(false);
|
|
493
633
|
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
634
|
+
const lastWalletMethodRef = useRef2("card");
|
|
494
635
|
const handleWalletConfirm = useCallback(
|
|
495
636
|
async (_event) => {
|
|
496
637
|
if (!stripe || !elements) return;
|
|
@@ -531,7 +672,12 @@ function WalletButtonInner({
|
|
|
531
672
|
{ payment_method: paymentMethod.id }
|
|
532
673
|
);
|
|
533
674
|
if (confirmError) {
|
|
534
|
-
|
|
675
|
+
const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
|
|
676
|
+
const message = confirmError.message ?? "Wallet payment failed.";
|
|
677
|
+
onErrorChange?.(message);
|
|
678
|
+
onDecline?.(buildDeclineEvent(method, message, {
|
|
679
|
+
code: confirmError.code
|
|
680
|
+
}));
|
|
535
681
|
return;
|
|
536
682
|
}
|
|
537
683
|
onTokenizedBody({
|
|
@@ -545,7 +691,7 @@ function WalletButtonInner({
|
|
|
545
691
|
setSubmitting(false);
|
|
546
692
|
}
|
|
547
693
|
},
|
|
548
|
-
[stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]
|
|
694
|
+
[stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]
|
|
549
695
|
);
|
|
550
696
|
return /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
551
697
|
/* @__PURE__ */ jsx4("div", { children: /* @__PURE__ */ jsx4(
|
|
@@ -554,7 +700,14 @@ function WalletButtonInner({
|
|
|
554
700
|
onReady: () => setReady(true),
|
|
555
701
|
onLoadError: () => {
|
|
556
702
|
},
|
|
703
|
+
onClick: (event) => {
|
|
704
|
+
lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
|
|
705
|
+
event.resolve();
|
|
706
|
+
},
|
|
557
707
|
onConfirm: handleWalletConfirm,
|
|
708
|
+
onCancel: () => {
|
|
709
|
+
onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
|
|
710
|
+
},
|
|
558
711
|
options: {
|
|
559
712
|
buttonType: { applePay: "plain", googlePay: "plain" },
|
|
560
713
|
paymentMethods: {
|
|
@@ -579,6 +732,7 @@ function SplitCardFormInner({
|
|
|
579
732
|
userId,
|
|
580
733
|
onComplete,
|
|
581
734
|
onError,
|
|
735
|
+
onDecline,
|
|
582
736
|
onTokenizedBody,
|
|
583
737
|
firstName,
|
|
584
738
|
lastName,
|
|
@@ -598,7 +752,10 @@ function SplitCardFormInner({
|
|
|
598
752
|
buttonsTheme,
|
|
599
753
|
buttonsStyles: buttonsStylesOverride,
|
|
600
754
|
cardButtonContent,
|
|
755
|
+
cardBackButtonContent,
|
|
756
|
+
cardTitleContent,
|
|
601
757
|
onButtonClick,
|
|
758
|
+
onBeforeButtonClick,
|
|
602
759
|
enableAVS = false,
|
|
603
760
|
avsLayout: avsLayoutProp = "row",
|
|
604
761
|
country: countryProp,
|
|
@@ -607,6 +764,7 @@ function SplitCardFormInner({
|
|
|
607
764
|
onZipChange,
|
|
608
765
|
totalAmount = 0,
|
|
609
766
|
currency = "usd",
|
|
767
|
+
initialCardOpen = false,
|
|
610
768
|
innerRef
|
|
611
769
|
}) {
|
|
612
770
|
const flopay = useFloPay();
|
|
@@ -617,9 +775,10 @@ function SplitCardFormInner({
|
|
|
617
775
|
const [is3DSActive, setIs3DSActive] = useState2(false);
|
|
618
776
|
const [selectedCountry, setSelectedCountry] = useState2(countryProp ?? "US");
|
|
619
777
|
const [zipCode, setZipCode] = useState2(zipProp ?? "");
|
|
778
|
+
const [accountPatch, setAccountPatch] = useState2({});
|
|
620
779
|
const zipCodeRef = useRef2(zipProp ?? "");
|
|
621
780
|
const selectedCountryRef = useRef2(countryProp ?? "US");
|
|
622
|
-
const [viewState, setViewState] = useState2("buttons");
|
|
781
|
+
const [viewState, setViewState] = useState2(initialCardOpen ? "card" : "buttons");
|
|
623
782
|
const showCardForm = viewState === "expanding" || viewState === "card";
|
|
624
783
|
const TRANSITION_MS = 280;
|
|
625
784
|
const expandToCard = useCallback(() => {
|
|
@@ -630,6 +789,11 @@ function SplitCardFormInner({
|
|
|
630
789
|
setViewState("collapsing");
|
|
631
790
|
setTimeout(() => setViewState("buttons"), TRANSITION_MS);
|
|
632
791
|
}, []);
|
|
792
|
+
useEffect3(() => {
|
|
793
|
+
if (layout === "buttons" && initialCardOpen) {
|
|
794
|
+
setViewState("card");
|
|
795
|
+
}
|
|
796
|
+
}, [layout, initialCardOpen]);
|
|
633
797
|
const [fullName, setFullName] = useState2("");
|
|
634
798
|
const [formReady, setFormReady] = useState2(false);
|
|
635
799
|
const [overlayStatus, setOverlayStatus] = useState2(null);
|
|
@@ -653,6 +817,14 @@ function SplitCardFormInner({
|
|
|
653
817
|
const isSubmitting = externalProcessing ?? processing;
|
|
654
818
|
const isSelfContained = !onTokenizedBody;
|
|
655
819
|
const baseUrl = resolvedBillingApiUrl.replace(/\/+$/, "");
|
|
820
|
+
const resolvedAccount = useMemo2(() => mergeAccountPatch({
|
|
821
|
+
userId,
|
|
822
|
+
email,
|
|
823
|
+
firstName,
|
|
824
|
+
lastName,
|
|
825
|
+
country: countryProp,
|
|
826
|
+
zip: zipProp
|
|
827
|
+
}, accountPatch), [userId, email, firstName, lastName, countryProp, zipProp, accountPatch]);
|
|
656
828
|
const stripeInstance = useMemo2(() => {
|
|
657
829
|
if (!flopay) return null;
|
|
658
830
|
return flopay.getRawProvider();
|
|
@@ -678,6 +850,12 @@ function SplitCardFormInner({
|
|
|
678
850
|
},
|
|
679
851
|
[onErrorChange]
|
|
680
852
|
);
|
|
853
|
+
const emitDecline = useCallback(
|
|
854
|
+
(method, input, overrides) => {
|
|
855
|
+
onDecline?.(buildDeclineEvent(method, input, overrides));
|
|
856
|
+
},
|
|
857
|
+
[onDecline]
|
|
858
|
+
);
|
|
681
859
|
const showWallets = showApplePay || showGooglePay;
|
|
682
860
|
const handleNameChange = useCallback((value) => {
|
|
683
861
|
setFullName(value);
|
|
@@ -685,6 +863,30 @@ function SplitCardFormInner({
|
|
|
685
863
|
onFirstNameChange?.(parts[0] ?? "");
|
|
686
864
|
onLastNameChange?.(parts.length > 1 ? parts.slice(1).join(" ") : "");
|
|
687
865
|
}, [onFirstNameChange, onLastNameChange]);
|
|
866
|
+
const runBeforeCardButtonClick = useCallback(async () => {
|
|
867
|
+
if (!onBeforeButtonClick) return true;
|
|
868
|
+
try {
|
|
869
|
+
const result = await onBeforeButtonClick({
|
|
870
|
+
method: "card",
|
|
871
|
+
sessionId: sessionId || void 0
|
|
872
|
+
});
|
|
873
|
+
if (result === false) {
|
|
874
|
+
return false;
|
|
875
|
+
}
|
|
876
|
+
if (result?.account) {
|
|
877
|
+
setAccountPatch((prev) => ({ ...prev ?? {}, ...result.account }));
|
|
878
|
+
}
|
|
879
|
+
return true;
|
|
880
|
+
} catch (err) {
|
|
881
|
+
const floPayErr = err instanceof FloPayError2 ? err : new FloPayError2(
|
|
882
|
+
err instanceof Error ? err.message : "Card before-click hook failed.",
|
|
883
|
+
"validation_error"
|
|
884
|
+
);
|
|
885
|
+
updateError(floPayErr.message);
|
|
886
|
+
onError?.(floPayErr);
|
|
887
|
+
return false;
|
|
888
|
+
}
|
|
889
|
+
}, [onBeforeButtonClick, sessionId, updateError, onError]);
|
|
688
890
|
const processPaymentInternal = useCallback(
|
|
689
891
|
async (tokenizedBody) => {
|
|
690
892
|
if (processingRef.current) return;
|
|
@@ -697,16 +899,16 @@ function SplitCardFormInner({
|
|
|
697
899
|
method: "POST",
|
|
698
900
|
headers: {
|
|
699
901
|
"Content-Type": "application/json",
|
|
700
|
-
"x-user-id": userId ?? ""
|
|
902
|
+
"x-user-id": resolvedAccount.userId ?? ""
|
|
701
903
|
},
|
|
702
904
|
body: JSON.stringify({
|
|
703
905
|
sessionId,
|
|
704
906
|
tokenizedData: tokenizedBody,
|
|
705
907
|
accountData: {
|
|
706
|
-
userId: userId ?? "",
|
|
707
|
-
email: email ?? "",
|
|
708
|
-
firstName: firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
|
|
709
|
-
lastName: lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
|
|
908
|
+
userId: resolvedAccount.userId ?? "",
|
|
909
|
+
email: resolvedAccount.email ?? "",
|
|
910
|
+
firstName: resolvedAccount.firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
|
|
911
|
+
lastName: resolvedAccount.lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
|
|
710
912
|
...enableAVS ? { zip: zipCodeRef.current, country: selectedCountryRef.current } : {}
|
|
711
913
|
},
|
|
712
914
|
chv
|
|
@@ -739,6 +941,7 @@ function SplitCardFormInner({
|
|
|
739
941
|
setOverlayStatus("error");
|
|
740
942
|
updateError(result.error.message);
|
|
741
943
|
onError?.(result.error);
|
|
944
|
+
emitDecline("card", result.error);
|
|
742
945
|
return;
|
|
743
946
|
}
|
|
744
947
|
if (result.status === "succeeded" || result.status === "processing") {
|
|
@@ -781,7 +984,11 @@ function SplitCardFormInner({
|
|
|
781
984
|
});
|
|
782
985
|
if (confirmError) {
|
|
783
986
|
setOverlayStatus("error");
|
|
784
|
-
|
|
987
|
+
const message2 = confirmError.message ?? "PayPal payment failed.";
|
|
988
|
+
updateError(message2);
|
|
989
|
+
emitDecline("paypal", message2, {
|
|
990
|
+
code: confirmError.code
|
|
991
|
+
});
|
|
785
992
|
}
|
|
786
993
|
} catch (err) {
|
|
787
994
|
setOverlayStatus("error");
|
|
@@ -790,7 +997,12 @@ function SplitCardFormInner({
|
|
|
790
997
|
return;
|
|
791
998
|
}
|
|
792
999
|
setOverlayStatus("error");
|
|
793
|
-
|
|
1000
|
+
const message = json?.message ?? "Payment failed. Please try again.";
|
|
1001
|
+
updateError(message);
|
|
1002
|
+
emitDecline(tokenizedBody.isPaypal ? "paypal" : "card", message, {
|
|
1003
|
+
code: json?.code ?? json?.gatewayErrorCode,
|
|
1004
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
1005
|
+
});
|
|
794
1006
|
await new Promise((r) => setTimeout(r, 1500));
|
|
795
1007
|
} catch (err) {
|
|
796
1008
|
setOverlayStatus("error");
|
|
@@ -802,7 +1014,7 @@ function SplitCardFormInner({
|
|
|
802
1014
|
processingRef.current = false;
|
|
803
1015
|
}
|
|
804
1016
|
},
|
|
805
|
-
[baseUrl, sessionId,
|
|
1017
|
+
[baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, onComplete, onError, updateError, emitDecline]
|
|
806
1018
|
);
|
|
807
1019
|
const dispatchTokenizedBody = useCallback(
|
|
808
1020
|
(tokenizedBody) => {
|
|
@@ -826,6 +1038,7 @@ function SplitCardFormInner({
|
|
|
826
1038
|
if (result.error) {
|
|
827
1039
|
updateError(result.error.message);
|
|
828
1040
|
onError?.(result.error);
|
|
1041
|
+
emitDecline("card", result.error);
|
|
829
1042
|
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
830
1043
|
dispatchTokenizedBody({
|
|
831
1044
|
id: result.paymentIntentId,
|
|
@@ -839,7 +1052,7 @@ function SplitCardFormInner({
|
|
|
839
1052
|
setIs3DSActive(false);
|
|
840
1053
|
}
|
|
841
1054
|
}
|
|
842
|
-
}), [flopay, dispatchTokenizedBody, onError, updateError]);
|
|
1055
|
+
}), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
|
|
843
1056
|
useEffect3(() => {
|
|
844
1057
|
if (typeof window === "undefined") return;
|
|
845
1058
|
const stored = localStorage.getItem(WALLET_RESUME_KEY);
|
|
@@ -862,7 +1075,9 @@ function SplitCardFormInner({
|
|
|
862
1075
|
async (e) => {
|
|
863
1076
|
e.preventDefault();
|
|
864
1077
|
if (!flopay || !elements || isSubmitting || processingRef.current) return;
|
|
865
|
-
|
|
1078
|
+
if (layout !== "buttons") {
|
|
1079
|
+
onButtonClick?.("card");
|
|
1080
|
+
}
|
|
866
1081
|
setProcessing(true);
|
|
867
1082
|
setOverlayStatus("processing");
|
|
868
1083
|
updateError(null);
|
|
@@ -890,23 +1105,23 @@ function SplitCardFormInner({
|
|
|
890
1105
|
updateError(pmResult.error?.message ?? "Failed to create payment method.");
|
|
891
1106
|
return;
|
|
892
1107
|
}
|
|
893
|
-
if (!sessionId || !email) {
|
|
894
|
-
throw new
|
|
1108
|
+
if (!sessionId || !resolvedAccount.email) {
|
|
1109
|
+
throw new FloPayError2("Missing sessionId or email", "validation_error");
|
|
895
1110
|
}
|
|
896
1111
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
897
1112
|
method: "POST",
|
|
898
1113
|
headers: { "Content-Type": "application/json" },
|
|
899
1114
|
body: JSON.stringify({
|
|
900
1115
|
sessionId,
|
|
901
|
-
email,
|
|
1116
|
+
email: resolvedAccount.email,
|
|
902
1117
|
paymentMethodType: pmResult.paymentMethodId,
|
|
903
1118
|
isPaypal: false
|
|
904
1119
|
})
|
|
905
1120
|
});
|
|
906
|
-
if (!intentResponse.ok) throw new
|
|
1121
|
+
if (!intentResponse.ok) throw new FloPayError2("Failed to create payment intent", "api_error");
|
|
907
1122
|
const intentJson = await intentResponse.json();
|
|
908
1123
|
const intentClientSecret = intentJson.data?.id;
|
|
909
|
-
if (!intentClientSecret) throw new
|
|
1124
|
+
if (!intentClientSecret) throw new FloPayError2("No client_secret in payment intent response", "api_error");
|
|
910
1125
|
const confirmResult = await flopay.confirmCardPayment({
|
|
911
1126
|
clientSecret: intentClientSecret,
|
|
912
1127
|
paymentMethodId: pmResult.paymentMethodId
|
|
@@ -914,6 +1129,8 @@ function SplitCardFormInner({
|
|
|
914
1129
|
if (confirmResult.error) {
|
|
915
1130
|
setOverlayStatus("error");
|
|
916
1131
|
updateError(confirmResult.error.message);
|
|
1132
|
+
onError?.(confirmResult.error);
|
|
1133
|
+
emitDecline("card", confirmResult.error);
|
|
917
1134
|
await new Promise((r) => setTimeout(r, 1500));
|
|
918
1135
|
return;
|
|
919
1136
|
}
|
|
@@ -934,7 +1151,7 @@ function SplitCardFormInner({
|
|
|
934
1151
|
}
|
|
935
1152
|
}
|
|
936
1153
|
},
|
|
937
|
-
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
|
|
1154
|
+
[flopay, elements, isSubmitting, sessionId, resolvedAccount.email, baseUrl, isSelfContained, dispatchTokenizedBody, onButtonClick, onError, updateError, emitDecline, layout]
|
|
938
1155
|
);
|
|
939
1156
|
const isReady = flopay !== null && elements !== null;
|
|
940
1157
|
if (!isReady) {
|
|
@@ -944,6 +1161,8 @@ function SplitCardFormInner({
|
|
|
944
1161
|
const resolvedBorder = isButtons ? bStyles.cardInputBorder ?? "#e5e7eb" : "#A4A4FF";
|
|
945
1162
|
const cardBg = isButtons ? bStyles.cardFormContainer?.backgroundColor ?? "white" : "#EDEDFF";
|
|
946
1163
|
const cardInputBg = isButtons ? bStyles.cardInputBackground ?? "white" : "white";
|
|
1164
|
+
const hideBackButtonLabel = isEmptySlotContent(cardBackButtonContent);
|
|
1165
|
+
const hideTitle = isEmptySlotContent(cardTitleContent);
|
|
947
1166
|
const stripeElementStyle = isButtons && (bStyles.cardInputColor || bStyles.cardInputPlaceholderColor || bStyles.cardInputFontSize) ? {
|
|
948
1167
|
style: {
|
|
949
1168
|
base: {
|
|
@@ -974,7 +1193,7 @@ function SplitCardFormInner({
|
|
|
974
1193
|
style: {
|
|
975
1194
|
display: "inline-flex",
|
|
976
1195
|
alignItems: "center",
|
|
977
|
-
gap: "0.5rem",
|
|
1196
|
+
gap: hideBackButtonLabel ? 0 : "0.5rem",
|
|
978
1197
|
background: "none",
|
|
979
1198
|
border: "none",
|
|
980
1199
|
cursor: "pointer",
|
|
@@ -999,11 +1218,11 @@ function SplitCardFormInner({
|
|
|
999
1218
|
transition: "background-color 0.15s",
|
|
1000
1219
|
...bStyles.backButtonIcon
|
|
1001
1220
|
}, children: /* @__PURE__ */ jsx4("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx4("path", { d: "M15 18l-6-6 6-6" }) }) }),
|
|
1002
|
-
|
|
1221
|
+
/* @__PURE__ */ jsx4(BackButtonContentSlot, { content: cardBackButtonContent })
|
|
1003
1222
|
]
|
|
1004
1223
|
}
|
|
1005
1224
|
),
|
|
1006
|
-
/* @__PURE__ */ jsx4("div", { style: {
|
|
1225
|
+
hideTitle ? /* @__PURE__ */ jsx4("div", { style: { flex: 1 } }) : /* @__PURE__ */ jsx4("div", { style: {
|
|
1007
1226
|
flex: 1,
|
|
1008
1227
|
textAlign: "center",
|
|
1009
1228
|
fontWeight: 600,
|
|
@@ -1011,9 +1230,9 @@ function SplitCardFormInner({
|
|
|
1011
1230
|
color: "#262833",
|
|
1012
1231
|
paddingRight: 80,
|
|
1013
1232
|
...bStyles.title
|
|
1014
|
-
}, children:
|
|
1233
|
+
}, children: /* @__PURE__ */ jsx4(TitleContentSlot, { content: cardTitleContent }) })
|
|
1015
1234
|
] }),
|
|
1016
|
-
!isButtons && /* @__PURE__ */ jsx4("div", { style: { textAlign: "center", fontWeight: 600, fontSize: "1.1rem", padding: "0.5rem 0", color: "#262833" }, children:
|
|
1235
|
+
!isButtons && !hideTitle && /* @__PURE__ */ jsx4("div", { style: { textAlign: "center", fontWeight: 600, fontSize: "1.1rem", padding: "0.5rem 0", color: "#262833" }, children: /* @__PURE__ */ jsx4(TitleContentSlot, { content: cardTitleContent }) }),
|
|
1017
1236
|
/* @__PURE__ */ jsx4("div", { style: {
|
|
1018
1237
|
backgroundColor: cardInputBg,
|
|
1019
1238
|
border: `1px solid ${resolvedBorder}`,
|
|
@@ -1217,32 +1436,36 @@ function SplitCardFormInner({
|
|
|
1217
1436
|
PayPalButtonInner,
|
|
1218
1437
|
{
|
|
1219
1438
|
sessionId,
|
|
1220
|
-
email,
|
|
1439
|
+
email: resolvedAccount.email,
|
|
1221
1440
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1222
1441
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1223
1442
|
onErrorChange: updateError,
|
|
1224
1443
|
isProcessing: isSubmitting,
|
|
1225
|
-
onButtonClick
|
|
1444
|
+
onButtonClick,
|
|
1445
|
+
onDecline
|
|
1226
1446
|
}
|
|
1227
1447
|
) }) : showPayPal ? /* @__PURE__ */ jsx4("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
|
|
1228
1448
|
showWallets && stripeInstance ? /* @__PURE__ */ jsx4(StripeElements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ jsx4(
|
|
1229
1449
|
WalletButtonInner,
|
|
1230
1450
|
{
|
|
1231
1451
|
sessionId,
|
|
1232
|
-
email,
|
|
1452
|
+
email: resolvedAccount.email,
|
|
1233
1453
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1234
1454
|
showApplePay,
|
|
1235
1455
|
showGooglePay,
|
|
1236
1456
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1237
1457
|
onErrorChange: updateError,
|
|
1238
|
-
onButtonClick
|
|
1458
|
+
onButtonClick,
|
|
1459
|
+
onDecline
|
|
1239
1460
|
}
|
|
1240
1461
|
) }) : showWallets ? /* @__PURE__ */ jsx4("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
|
|
1241
1462
|
/* @__PURE__ */ jsx4(
|
|
1242
1463
|
"button",
|
|
1243
1464
|
{
|
|
1244
1465
|
type: "button",
|
|
1245
|
-
onClick: () => {
|
|
1466
|
+
onClick: async () => {
|
|
1467
|
+
const shouldContinue = await runBeforeCardButtonClick();
|
|
1468
|
+
if (!shouldContinue) return;
|
|
1246
1469
|
onButtonClick?.("card");
|
|
1247
1470
|
expandToCard();
|
|
1248
1471
|
},
|
|
@@ -1307,23 +1530,25 @@ function SplitCardFormInner({
|
|
|
1307
1530
|
WalletButtonInner,
|
|
1308
1531
|
{
|
|
1309
1532
|
sessionId,
|
|
1310
|
-
email,
|
|
1533
|
+
email: resolvedAccount.email,
|
|
1311
1534
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1312
1535
|
showApplePay,
|
|
1313
1536
|
showGooglePay,
|
|
1314
1537
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1315
|
-
onErrorChange: updateError
|
|
1538
|
+
onErrorChange: updateError,
|
|
1539
|
+
onDecline
|
|
1316
1540
|
}
|
|
1317
1541
|
) }),
|
|
1318
1542
|
showPayPal && stripeInstance && /* @__PURE__ */ jsx4(StripeElements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ jsx4(
|
|
1319
1543
|
PayPalButtonInner,
|
|
1320
1544
|
{
|
|
1321
1545
|
sessionId,
|
|
1322
|
-
email,
|
|
1546
|
+
email: resolvedAccount.email,
|
|
1323
1547
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1324
1548
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1325
1549
|
onErrorChange: updateError,
|
|
1326
|
-
isProcessing: isSubmitting
|
|
1550
|
+
isProcessing: isSubmitting,
|
|
1551
|
+
onDecline
|
|
1327
1552
|
}
|
|
1328
1553
|
) }),
|
|
1329
1554
|
(showWallets && stripeInstance || showPayPal && stripeInstance) && /* @__PURE__ */ jsxs2("div", { style: {
|
|
@@ -1355,6 +1580,7 @@ function FloPayCheckout({
|
|
|
1355
1580
|
error: errorNode,
|
|
1356
1581
|
onComplete,
|
|
1357
1582
|
onError,
|
|
1583
|
+
onDecline,
|
|
1358
1584
|
showPayPal = true,
|
|
1359
1585
|
showApplePay = true,
|
|
1360
1586
|
showGooglePay = true,
|
|
@@ -1362,7 +1588,10 @@ function FloPayCheckout({
|
|
|
1362
1588
|
buttonsTheme,
|
|
1363
1589
|
buttonsStyles,
|
|
1364
1590
|
cardButtonContent,
|
|
1591
|
+
cardBackButtonContent,
|
|
1592
|
+
cardTitleContent,
|
|
1365
1593
|
onButtonClick,
|
|
1594
|
+
onBeforeButtonClick,
|
|
1366
1595
|
enableAVS,
|
|
1367
1596
|
avsLayout,
|
|
1368
1597
|
submitLabel,
|
|
@@ -1384,13 +1613,28 @@ function FloPayCheckout({
|
|
|
1384
1613
|
const [currentMode, setCurrentMode] = useState3("full");
|
|
1385
1614
|
const [confirmProcessing, setConfirmProcessing] = useState3(false);
|
|
1386
1615
|
const [modeError, setModeError] = useState3(null);
|
|
1616
|
+
const [createSessionPatch, setCreateSessionPatch] = useState3(void 0);
|
|
1617
|
+
const [cardBootstrapPending, setCardBootstrapPending] = useState3(false);
|
|
1618
|
+
const [deferredCardOpen, setDeferredCardOpen] = useState3(false);
|
|
1387
1619
|
const autoCheckoutAttempted = useRef3(false);
|
|
1388
1620
|
const onCompleteRef = useRef3(onComplete);
|
|
1389
1621
|
onCompleteRef.current = onComplete;
|
|
1390
1622
|
const onErrorRef = useRef3(onError);
|
|
1391
1623
|
onErrorRef.current = onError;
|
|
1624
|
+
const onDeclineRef = useRef3(onDecline);
|
|
1625
|
+
onDeclineRef.current = onDecline;
|
|
1392
1626
|
const onSessionCompletedRef = useRef3(onSessionCompleted);
|
|
1393
1627
|
onSessionCompletedRef.current = onSessionCompleted;
|
|
1628
|
+
const effectiveCreateSession = useMemo3(
|
|
1629
|
+
() => createSessionParams ? mergeInlineSessionPatch(createSessionParams, createSessionPatch) : void 0,
|
|
1630
|
+
[createSessionParams, createSessionPatch]
|
|
1631
|
+
);
|
|
1632
|
+
const emitDecline = useCallback2(
|
|
1633
|
+
(method, input, overrides) => {
|
|
1634
|
+
onDeclineRef.current?.(buildDeclineEvent(method, input, overrides));
|
|
1635
|
+
},
|
|
1636
|
+
[]
|
|
1637
|
+
);
|
|
1394
1638
|
const processPaymentForMode = useCallback2(
|
|
1395
1639
|
async (sess) => {
|
|
1396
1640
|
const baseUrl = resolvedBillingUrl.replace(/\/+$/, "");
|
|
@@ -1430,9 +1674,13 @@ function FloPayCheckout({
|
|
|
1430
1674
|
// No client secret available
|
|
1431
1675
|
};
|
|
1432
1676
|
}
|
|
1433
|
-
throw new
|
|
1677
|
+
throw new FloPayError3(
|
|
1434
1678
|
json?.message ?? "Payment failed. Please try again.",
|
|
1435
|
-
"api_error"
|
|
1679
|
+
"api_error",
|
|
1680
|
+
{
|
|
1681
|
+
code: json?.code ?? json?.gatewayErrorCode,
|
|
1682
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
1683
|
+
}
|
|
1436
1684
|
);
|
|
1437
1685
|
},
|
|
1438
1686
|
[resolvedBillingUrl, resolvedSessionId]
|
|
@@ -1451,6 +1699,9 @@ function FloPayCheckout({
|
|
|
1451
1699
|
});
|
|
1452
1700
|
if (nextActionError) {
|
|
1453
1701
|
setModeError(nextActionError.message ?? "3DS authentication failed.");
|
|
1702
|
+
emitDecline("card", nextActionError.message ?? "3DS authentication failed.", {
|
|
1703
|
+
code: nextActionError.code
|
|
1704
|
+
});
|
|
1454
1705
|
return false;
|
|
1455
1706
|
}
|
|
1456
1707
|
if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded")) {
|
|
@@ -1473,6 +1724,9 @@ function FloPayCheckout({
|
|
|
1473
1724
|
});
|
|
1474
1725
|
if (error) {
|
|
1475
1726
|
setModeError(error.message ?? "PayPal authorization failed.");
|
|
1727
|
+
emitDecline("paypal", error.message ?? "PayPal authorization failed.", {
|
|
1728
|
+
code: error.code
|
|
1729
|
+
});
|
|
1476
1730
|
return false;
|
|
1477
1731
|
}
|
|
1478
1732
|
onCompleteRef.current?.({ status: "succeeded" });
|
|
@@ -1480,16 +1734,24 @@ function FloPayCheckout({
|
|
|
1480
1734
|
}
|
|
1481
1735
|
return false;
|
|
1482
1736
|
},
|
|
1483
|
-
[resolvedBillingUrl, resolvedSessionId]
|
|
1737
|
+
[resolvedBillingUrl, resolvedSessionId, emitDecline]
|
|
1484
1738
|
);
|
|
1485
1739
|
const inflightRef = useRef3(/* @__PURE__ */ new Map());
|
|
1486
1740
|
const initializedHashRef = useRef3(null);
|
|
1741
|
+
const deferInlineSessionUntilCardClick = Boolean(
|
|
1742
|
+
effectiveCreateSession && !children && layout === "buttons" && onBeforeButtonClick && (effectiveCreateSession.checkoutMode ?? checkoutModeProp ?? "full") === "full"
|
|
1743
|
+
);
|
|
1487
1744
|
function hashCreateParams(params) {
|
|
1488
1745
|
const key = JSON.stringify({
|
|
1489
1746
|
c: params?.clientId,
|
|
1747
|
+
successUrl: params?.successUrl,
|
|
1748
|
+
cancelUrl: params?.cancelUrl,
|
|
1490
1749
|
i: params?.items?.map((x) => `${x.providerItemId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
|
|
1491
1750
|
s: params?.subscriptions?.map((x) => `${x.providerPlanId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
|
|
1492
|
-
|
|
1751
|
+
account: params?.account,
|
|
1752
|
+
couponCodes: params?.couponCodes,
|
|
1753
|
+
tagsData: params?.tagsData,
|
|
1754
|
+
utmMetadata: params?.utmMetadata,
|
|
1493
1755
|
m: params?.checkoutMode ?? "full"
|
|
1494
1756
|
});
|
|
1495
1757
|
let h = 0;
|
|
@@ -1499,138 +1761,167 @@ function FloPayCheckout({
|
|
|
1499
1761
|
return `flopay_session_${Math.abs(h).toString(36)}`;
|
|
1500
1762
|
}
|
|
1501
1763
|
const createSessionHash = useMemo3(
|
|
1502
|
-
() =>
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1764
|
+
() => effectiveCreateSession ? hashCreateParams(effectiveCreateSession) : "",
|
|
1765
|
+
[effectiveCreateSession]
|
|
1766
|
+
);
|
|
1767
|
+
const createSessionParamsRef = useRef3(effectiveCreateSession);
|
|
1768
|
+
createSessionParamsRef.current = effectiveCreateSession;
|
|
1769
|
+
useEffect4(() => {
|
|
1770
|
+
setResolvedSessionId(sessionIdProp ?? "");
|
|
1771
|
+
}, [sessionIdProp]);
|
|
1772
|
+
async function resolveInlineSession(params, cacheKey) {
|
|
1773
|
+
ensureInlineSessionReady(params);
|
|
1774
|
+
const api = new PaymentAPI(resolvedBillingUrl);
|
|
1775
|
+
let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
|
|
1776
|
+
let realResult = null;
|
|
1777
|
+
if (sid) {
|
|
1778
|
+
try {
|
|
1779
|
+
realResult = await api.getUnifiedCheckoutSession(sid);
|
|
1780
|
+
if (realResult.data.session?.status === "complete") {
|
|
1781
|
+
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1782
|
+
sid = null;
|
|
1783
|
+
realResult = null;
|
|
1784
|
+
}
|
|
1785
|
+
} catch {
|
|
1786
|
+
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1787
|
+
sid = null;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
if (!sid) {
|
|
1791
|
+
realResult = await api.createAndFetchSession(params);
|
|
1792
|
+
sid = realResult.data.session?.id ?? "";
|
|
1793
|
+
if (sid && typeof window !== "undefined") {
|
|
1794
|
+
window.sessionStorage.setItem(cacheKey, sid);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return { sid: sid ?? "", result: realResult };
|
|
1798
|
+
}
|
|
1799
|
+
const bootstrapInlineSession = useCallback2(
|
|
1800
|
+
async (patch) => {
|
|
1801
|
+
const baseParams = createSessionParamsRef.current;
|
|
1802
|
+
if (!baseParams) {
|
|
1803
|
+
throw new FloPayError3("createSession is required to bootstrap checkout.", "validation_error");
|
|
1804
|
+
}
|
|
1805
|
+
const mergedParams = mergeInlineSessionPatch(baseParams, patch);
|
|
1806
|
+
const cacheKey = hashCreateParams(mergedParams);
|
|
1807
|
+
let promise = inflightRef.current.get(cacheKey);
|
|
1808
|
+
if (!promise) {
|
|
1809
|
+
promise = resolveInlineSession(mergedParams, cacheKey);
|
|
1810
|
+
inflightRef.current.set(cacheKey, promise);
|
|
1811
|
+
}
|
|
1812
|
+
let resolved;
|
|
1813
|
+
try {
|
|
1814
|
+
resolved = await promise;
|
|
1815
|
+
} finally {
|
|
1816
|
+
inflightRef.current.delete(cacheKey);
|
|
1817
|
+
}
|
|
1818
|
+
if (patch) {
|
|
1819
|
+
setCreateSessionPatch((prev) => mergeInlineSessionPatches(prev, patch));
|
|
1820
|
+
}
|
|
1821
|
+
const { sid, result: realResult } = resolved;
|
|
1822
|
+
setUnified(realResult);
|
|
1823
|
+
if (realResult.data.session) {
|
|
1824
|
+
setSession(realResult.data.session);
|
|
1825
|
+
}
|
|
1826
|
+
if (sid) {
|
|
1827
|
+
setResolvedSessionId(sid);
|
|
1828
|
+
}
|
|
1829
|
+
let publishableKey;
|
|
1830
|
+
if (realResult.provider === "stripe") {
|
|
1831
|
+
publishableKey = realResult.data.stripe?.publishableKey;
|
|
1832
|
+
}
|
|
1833
|
+
if (!publishableKey) publishableKey = fallbackPublishableKey;
|
|
1834
|
+
if (!publishableKey) {
|
|
1835
|
+
throw new FloPayError3(
|
|
1836
|
+
"No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
|
|
1837
|
+
"validation_error"
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
const instance = await loadFloPay(publishableKey, {
|
|
1841
|
+
billingApiUrl: resolvedBillingUrl,
|
|
1842
|
+
locale
|
|
1843
|
+
});
|
|
1844
|
+
flopayRef.current = instance;
|
|
1845
|
+
setFloPay(instance);
|
|
1846
|
+
initializedHashRef.current = cacheKey;
|
|
1847
|
+
setIsLoading(false);
|
|
1848
|
+
return resolved;
|
|
1849
|
+
},
|
|
1850
|
+
[fallbackPublishableKey, locale, resolvedBillingUrl]
|
|
1511
1851
|
);
|
|
1512
|
-
const
|
|
1513
|
-
|
|
1852
|
+
const handleDeferredCardButtonClick = useCallback2(async () => {
|
|
1853
|
+
if (cardBootstrapPending) return;
|
|
1854
|
+
setLoadError(null);
|
|
1855
|
+
setCardBootstrapPending(true);
|
|
1856
|
+
try {
|
|
1857
|
+
const beforeClickResult = await onBeforeButtonClick?.({
|
|
1858
|
+
method: "card",
|
|
1859
|
+
createSession: effectiveCreateSession
|
|
1860
|
+
});
|
|
1861
|
+
if (beforeClickResult === false) {
|
|
1862
|
+
setDeferredCardOpen(false);
|
|
1863
|
+
return;
|
|
1864
|
+
}
|
|
1865
|
+
const patch = beforeClickResult && typeof beforeClickResult === "object" ? beforeClickResult : void 0;
|
|
1866
|
+
const baseParams = createSessionParamsRef.current;
|
|
1867
|
+
if (!baseParams) {
|
|
1868
|
+
throw new FloPayError3(
|
|
1869
|
+
"createSession is required to bootstrap checkout.",
|
|
1870
|
+
"validation_error"
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
ensureInlineSessionReady(mergeInlineSessionPatch(baseParams, patch));
|
|
1874
|
+
setDeferredCardOpen(true);
|
|
1875
|
+
onButtonClick?.("card");
|
|
1876
|
+
await bootstrapInlineSession(patch);
|
|
1877
|
+
} catch (err) {
|
|
1878
|
+
setDeferredCardOpen(false);
|
|
1879
|
+
const floPayErr = err instanceof FloPayError3 ? err : new FloPayError3(
|
|
1880
|
+
err instanceof Error ? err.message : "Failed to start card checkout.",
|
|
1881
|
+
"api_error"
|
|
1882
|
+
);
|
|
1883
|
+
setLoadError(floPayErr);
|
|
1884
|
+
onErrorRef.current?.(floPayErr);
|
|
1885
|
+
} finally {
|
|
1886
|
+
setCardBootstrapPending(false);
|
|
1887
|
+
}
|
|
1888
|
+
}, [
|
|
1889
|
+
bootstrapInlineSession,
|
|
1890
|
+
cardBootstrapPending,
|
|
1891
|
+
effectiveCreateSession,
|
|
1892
|
+
onButtonClick,
|
|
1893
|
+
onBeforeButtonClick
|
|
1894
|
+
]);
|
|
1514
1895
|
useEffect4(() => {
|
|
1515
1896
|
let cancelled = false;
|
|
1516
1897
|
setLoadError(null);
|
|
1517
1898
|
if (createSessionHash) {
|
|
1899
|
+
if (deferInlineSessionUntilCardClick) {
|
|
1900
|
+
if (initializedHashRef.current !== createSessionHash) {
|
|
1901
|
+
setSession(null);
|
|
1902
|
+
setUnified(null);
|
|
1903
|
+
setFloPay(null);
|
|
1904
|
+
setResolvedSessionId("");
|
|
1905
|
+
setDeferredCardOpen(false);
|
|
1906
|
+
flopayRef.current = null;
|
|
1907
|
+
}
|
|
1908
|
+
setCurrentMode(createSessionParamsRef.current?.checkoutMode ?? checkoutModeProp ?? "full");
|
|
1909
|
+
setIsLoading(false);
|
|
1910
|
+
return () => {
|
|
1911
|
+
cancelled = true;
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1518
1914
|
if (initializedHashRef.current === createSessionHash) return;
|
|
1519
1915
|
const params = createSessionParamsRef.current;
|
|
1520
|
-
|
|
1521
|
-
...(params.items ?? []).map((i) => i.overrideAmount ?? i.totalAmount ?? 0),
|
|
1522
|
-
...(params.subscriptions ?? []).map((s) => s.overrideAmount ?? s.totalAmount ?? 0)
|
|
1523
|
-
].reduce((sum, v) => sum + v, 0);
|
|
1524
|
-
const currency = params.items?.[0]?.currency ?? params.subscriptions?.[0]?.currency ?? "USD";
|
|
1525
|
-
const syntheticSession = {
|
|
1526
|
-
id: "",
|
|
1527
|
-
clientSecret: "",
|
|
1528
|
-
mode: "payment",
|
|
1529
|
-
amount: Math.round(totalAmount * 100),
|
|
1530
|
-
currency,
|
|
1531
|
-
status: "open",
|
|
1532
|
-
customer: {
|
|
1533
|
-
id: params.account.userId,
|
|
1534
|
-
email: params.account.email,
|
|
1535
|
-
firstName: params.account.firstName,
|
|
1536
|
-
lastName: params.account.lastName
|
|
1537
|
-
},
|
|
1538
|
-
successUrl: params.successUrl,
|
|
1539
|
-
cancelUrl: params.cancelUrl,
|
|
1540
|
-
checkoutMode: params.checkoutMode ?? "full",
|
|
1541
|
-
items: (params.items ?? []).map((i, idx) => ({
|
|
1542
|
-
uuid: `synthetic-item-${idx}`,
|
|
1543
|
-
checkoutSessionId: "",
|
|
1544
|
-
providerItemId: i.providerItemId,
|
|
1545
|
-
providerItemName: i.providerItemName ?? i.providerItemId,
|
|
1546
|
-
quantity: i.quantity ?? 1,
|
|
1547
|
-
totalAmount: i.totalAmount,
|
|
1548
|
-
overrideAmount: i.overrideAmount ?? null,
|
|
1549
|
-
currency: i.currency ?? currency
|
|
1550
|
-
})),
|
|
1551
|
-
subscriptions: (params.subscriptions ?? []).map((s, idx) => ({
|
|
1552
|
-
uuid: `synthetic-sub-${idx}`,
|
|
1553
|
-
checkoutSessionId: "",
|
|
1554
|
-
providerPlanId: s.providerPlanId,
|
|
1555
|
-
providerPlanName: s.providerPlanName ?? s.providerPlanId,
|
|
1556
|
-
quantity: s.quantity ?? 1,
|
|
1557
|
-
totalAmount: s.totalAmount,
|
|
1558
|
-
overrideAmount: s.overrideAmount ?? null,
|
|
1559
|
-
currency: s.currency ?? currency
|
|
1560
|
-
}))
|
|
1561
|
-
};
|
|
1562
|
-
setSession(syntheticSession);
|
|
1916
|
+
setSession(buildSyntheticSession(params, checkoutModeProp));
|
|
1563
1917
|
setCurrentMode(params.checkoutMode ?? checkoutModeProp ?? "full");
|
|
1564
1918
|
setIsLoading(false);
|
|
1565
|
-
const cacheKey = createSessionHash;
|
|
1566
|
-
async function resolveSession() {
|
|
1567
|
-
const api = new PaymentAPI(resolvedBillingUrl);
|
|
1568
|
-
let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
|
|
1569
|
-
let realResult = null;
|
|
1570
|
-
if (sid) {
|
|
1571
|
-
try {
|
|
1572
|
-
realResult = await api.getUnifiedCheckoutSession(sid);
|
|
1573
|
-
if (realResult.data.session?.status === "complete") {
|
|
1574
|
-
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1575
|
-
sid = null;
|
|
1576
|
-
realResult = null;
|
|
1577
|
-
}
|
|
1578
|
-
} catch {
|
|
1579
|
-
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1580
|
-
sid = null;
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
if (!sid) {
|
|
1584
|
-
realResult = await api.createAndFetchSession(createSessionParamsRef.current);
|
|
1585
|
-
sid = realResult.data.session?.id ?? "";
|
|
1586
|
-
if (sid && typeof window !== "undefined") {
|
|
1587
|
-
window.sessionStorage.setItem(cacheKey, sid);
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
return { sid: sid ?? "", result: realResult };
|
|
1591
|
-
}
|
|
1592
1919
|
(async () => {
|
|
1593
1920
|
try {
|
|
1594
|
-
|
|
1595
|
-
if (!promise) {
|
|
1596
|
-
promise = resolveSession();
|
|
1597
|
-
inflightRef.current.set(cacheKey, promise);
|
|
1598
|
-
}
|
|
1599
|
-
let resolved;
|
|
1600
|
-
try {
|
|
1601
|
-
resolved = await promise;
|
|
1602
|
-
} finally {
|
|
1603
|
-
inflightRef.current.delete(cacheKey);
|
|
1604
|
-
}
|
|
1605
|
-
if (cancelled) return;
|
|
1606
|
-
const { sid, result: realResult } = resolved;
|
|
1607
|
-
if (realResult) {
|
|
1608
|
-
setUnified(realResult);
|
|
1609
|
-
if (realResult.data.session) setSession(realResult.data.session);
|
|
1610
|
-
}
|
|
1611
|
-
if (sid) {
|
|
1612
|
-
setResolvedSessionId(sid);
|
|
1613
|
-
}
|
|
1614
|
-
let pk;
|
|
1615
|
-
if (realResult?.provider === "stripe") {
|
|
1616
|
-
pk = realResult.data.stripe?.publishableKey;
|
|
1617
|
-
}
|
|
1618
|
-
if (!pk) pk = fallbackPublishableKey;
|
|
1619
|
-
if (!pk) {
|
|
1620
|
-
throw new FloPayError2(
|
|
1621
|
-
"No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
|
|
1622
|
-
"validation_error"
|
|
1623
|
-
);
|
|
1624
|
-
}
|
|
1625
|
-
const instance = await loadFloPay(pk, { billingApiUrl: resolvedBillingUrl, locale });
|
|
1626
|
-
if (!cancelled) {
|
|
1627
|
-
flopayRef.current = instance;
|
|
1628
|
-
setFloPay(instance);
|
|
1629
|
-
initializedHashRef.current = cacheKey;
|
|
1630
|
-
}
|
|
1921
|
+
await bootstrapInlineSession();
|
|
1631
1922
|
} catch (err) {
|
|
1632
1923
|
if (cancelled) return;
|
|
1633
|
-
const floPayErr = err instanceof
|
|
1924
|
+
const floPayErr = err instanceof FloPayError3 ? err : new FloPayError3(err instanceof Error ? err.message : "Failed to create session", "api_error");
|
|
1634
1925
|
setLoadError(floPayErr);
|
|
1635
1926
|
}
|
|
1636
1927
|
})();
|
|
@@ -1648,7 +1939,7 @@ function FloPayCheckout({
|
|
|
1648
1939
|
const sess = result.data.session ?? null;
|
|
1649
1940
|
setSession(sess);
|
|
1650
1941
|
if (!sess) {
|
|
1651
|
-
throw new
|
|
1942
|
+
throw new FloPayError3("No session data returned", "api_error");
|
|
1652
1943
|
}
|
|
1653
1944
|
if (sess.status === "complete") {
|
|
1654
1945
|
setIsLoading(false);
|
|
@@ -1687,7 +1978,7 @@ function FloPayCheckout({
|
|
|
1687
1978
|
if (!cancelled) setIsLoading(false);
|
|
1688
1979
|
} catch (err) {
|
|
1689
1980
|
if (cancelled) return;
|
|
1690
|
-
const floPayErr = err instanceof
|
|
1981
|
+
const floPayErr = err instanceof FloPayError3 ? err : new FloPayError3(err instanceof Error ? err.message : "Failed to initialize checkout", "api_error");
|
|
1691
1982
|
setLoadError(floPayErr);
|
|
1692
1983
|
setIsLoading(false);
|
|
1693
1984
|
}
|
|
@@ -1699,7 +1990,7 @@ function FloPayCheckout({
|
|
|
1699
1990
|
}
|
|
1700
1991
|
if (!publishableKey) publishableKey = fallbackPublishableKey;
|
|
1701
1992
|
if (!publishableKey) {
|
|
1702
|
-
throw new
|
|
1993
|
+
throw new FloPayError3(
|
|
1703
1994
|
"No publishable key found in session response. Provide a fallbackPublishableKey prop or ensure the session includes gatewayData.publishableKey.",
|
|
1704
1995
|
"validation_error"
|
|
1705
1996
|
);
|
|
@@ -1715,7 +2006,7 @@ function FloPayCheckout({
|
|
|
1715
2006
|
return () => {
|
|
1716
2007
|
cancelled = true;
|
|
1717
2008
|
};
|
|
1718
|
-
}, [resolvedSessionId, createSessionHash,
|
|
2009
|
+
}, [resolvedSessionId, createSessionHash, checkoutModeProp, deferInlineSessionUntilCardClick, bootstrapInlineSession]);
|
|
1719
2010
|
const handleConfirmCheckout = useCallback2(async () => {
|
|
1720
2011
|
if (confirmProcessing || !session) return;
|
|
1721
2012
|
setConfirmProcessing(true);
|
|
@@ -1730,17 +2021,18 @@ function FloPayCheckout({
|
|
|
1730
2021
|
setCurrentMode("full");
|
|
1731
2022
|
}
|
|
1732
2023
|
} catch (err) {
|
|
1733
|
-
const floPayErr = err instanceof
|
|
2024
|
+
const floPayErr = err instanceof FloPayError3 ? err : new FloPayError3(
|
|
1734
2025
|
err instanceof Error ? err.message : "Payment failed",
|
|
1735
2026
|
"api_error"
|
|
1736
2027
|
);
|
|
1737
2028
|
setModeError(floPayErr.message);
|
|
1738
2029
|
onError?.(floPayErr);
|
|
2030
|
+
emitDecline("card", floPayErr);
|
|
1739
2031
|
setCurrentMode("full");
|
|
1740
2032
|
} finally {
|
|
1741
2033
|
setConfirmProcessing(false);
|
|
1742
2034
|
}
|
|
1743
|
-
}, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError]);
|
|
2035
|
+
}, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError, emitDecline]);
|
|
1744
2036
|
const providerOptions = useMemo3(() => {
|
|
1745
2037
|
if (!unified || !session) return void 0;
|
|
1746
2038
|
const opts = {
|
|
@@ -1766,6 +2058,8 @@ function FloPayCheckout({
|
|
|
1766
2058
|
}),
|
|
1767
2059
|
[session, isLoading, loadError, currentMode]
|
|
1768
2060
|
);
|
|
2061
|
+
const shouldShowInterimButtons = Boolean(createSessionParams) && layout === "buttons" && (!flopay || !providerOptions);
|
|
2062
|
+
const shouldKeepDeferredInterimVisible = shouldShowInterimButtons && deferInlineSessionUntilCardClick;
|
|
1769
2063
|
if (isLoading) {
|
|
1770
2064
|
if (loadingNode) return /* @__PURE__ */ jsx5(Fragment3, { children: loadingNode });
|
|
1771
2065
|
if (layout === "buttons") {
|
|
@@ -1794,7 +2088,7 @@ function FloPayCheckout({
|
|
|
1794
2088
|
/* @__PURE__ */ jsx5("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
|
|
1795
2089
|
] });
|
|
1796
2090
|
}
|
|
1797
|
-
if (loadError) {
|
|
2091
|
+
if (loadError && !shouldKeepDeferredInterimVisible) {
|
|
1798
2092
|
if (errorNode) return /* @__PURE__ */ jsx5(Fragment3, { children: errorNode(loadError) });
|
|
1799
2093
|
return /* @__PURE__ */ jsx5(
|
|
1800
2094
|
"div",
|
|
@@ -1809,17 +2103,23 @@ function FloPayCheckout({
|
|
|
1809
2103
|
}
|
|
1810
2104
|
);
|
|
1811
2105
|
}
|
|
1812
|
-
if (
|
|
2106
|
+
if (shouldShowInterimButtons) {
|
|
1813
2107
|
return /* @__PURE__ */ jsx5(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx5(
|
|
1814
2108
|
InterimButtonsView,
|
|
1815
2109
|
{
|
|
1816
2110
|
onButtonClick,
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2111
|
+
onCardButtonClick: deferInlineSessionUntilCardClick ? handleDeferredCardButtonClick : void 0,
|
|
2112
|
+
cardLoading: cardBootstrapPending,
|
|
2113
|
+
cardOpen: deferInlineSessionUntilCardClick ? deferredCardOpen : void 0,
|
|
2114
|
+
errorMessage: shouldKeepDeferredInterimVisible ? loadError?.message ?? null : null,
|
|
2115
|
+
showPayPal: deferInlineSessionUntilCardClick ? false : showPayPal,
|
|
2116
|
+
showApplePay: deferInlineSessionUntilCardClick ? false : showApplePay,
|
|
2117
|
+
showGooglePay: deferInlineSessionUntilCardClick ? false : showGooglePay,
|
|
1820
2118
|
buttonsTheme,
|
|
1821
2119
|
buttonsStyles,
|
|
1822
|
-
cardButtonContent
|
|
2120
|
+
cardButtonContent,
|
|
2121
|
+
cardBackButtonContent,
|
|
2122
|
+
cardTitleContent
|
|
1823
2123
|
}
|
|
1824
2124
|
) });
|
|
1825
2125
|
}
|
|
@@ -1900,6 +2200,7 @@ function FloPayCheckout({
|
|
|
1900
2200
|
currency: session?.currency?.toLowerCase() ?? "usd",
|
|
1901
2201
|
onComplete,
|
|
1902
2202
|
onError,
|
|
2203
|
+
onDecline,
|
|
1903
2204
|
showPayPal,
|
|
1904
2205
|
showApplePay,
|
|
1905
2206
|
showGooglePay,
|
|
@@ -1907,11 +2208,15 @@ function FloPayCheckout({
|
|
|
1907
2208
|
buttonsTheme,
|
|
1908
2209
|
buttonsStyles,
|
|
1909
2210
|
cardButtonContent,
|
|
2211
|
+
cardBackButtonContent,
|
|
2212
|
+
cardTitleContent,
|
|
1910
2213
|
onButtonClick,
|
|
2214
|
+
onBeforeButtonClick,
|
|
1911
2215
|
enableAVS,
|
|
1912
2216
|
avsLayout,
|
|
1913
2217
|
country: session?.customer?.country,
|
|
1914
2218
|
zip: session?.customer?.zip,
|
|
2219
|
+
initialCardOpen: deferredCardOpen,
|
|
1915
2220
|
submitLabel,
|
|
1916
2221
|
className
|
|
1917
2222
|
}
|
|
@@ -1944,14 +2249,26 @@ function SessionInjector({
|
|
|
1944
2249
|
}
|
|
1945
2250
|
function InterimButtonsView({
|
|
1946
2251
|
onButtonClick,
|
|
2252
|
+
onCardButtonClick,
|
|
2253
|
+
cardLoading = false,
|
|
2254
|
+
cardOpen,
|
|
2255
|
+
errorMessage,
|
|
1947
2256
|
showPayPal,
|
|
1948
2257
|
showApplePay,
|
|
1949
2258
|
showGooglePay,
|
|
1950
2259
|
buttonsTheme,
|
|
1951
2260
|
buttonsStyles: stylesOverride,
|
|
1952
|
-
cardButtonContent
|
|
2261
|
+
cardButtonContent,
|
|
2262
|
+
cardBackButtonContent,
|
|
2263
|
+
cardTitleContent
|
|
1953
2264
|
}) {
|
|
1954
2265
|
const [showCardForm, setShowCardForm] = useState3(false);
|
|
2266
|
+
const isCardOpenControlled = typeof cardOpen === "boolean";
|
|
2267
|
+
useEffect4(() => {
|
|
2268
|
+
if (isCardOpenControlled) {
|
|
2269
|
+
setShowCardForm(cardOpen);
|
|
2270
|
+
}
|
|
2271
|
+
}, [cardOpen, isCardOpenControlled]);
|
|
1955
2272
|
const bStyles = useMemo3(() => {
|
|
1956
2273
|
const base = resolveButtonsLayoutTheme2(buttonsTheme);
|
|
1957
2274
|
if (!stylesOverride) return base;
|
|
@@ -1960,6 +2277,8 @@ function InterimButtonsView({
|
|
|
1960
2277
|
...stylesOverride,
|
|
1961
2278
|
cardButton: { ...base.cardButton, ...stylesOverride.cardButton },
|
|
1962
2279
|
cardFormContainer: { ...base.cardFormContainer, ...stylesOverride.cardFormContainer },
|
|
2280
|
+
backButton: { ...base.backButton, ...stylesOverride.backButton },
|
|
2281
|
+
backButtonIcon: { ...base.backButtonIcon, ...stylesOverride.backButtonIcon },
|
|
1963
2282
|
submitButton: { ...base.submitButton, ...stylesOverride.submitButton },
|
|
1964
2283
|
title: { ...base.title, ...stylesOverride.title }
|
|
1965
2284
|
};
|
|
@@ -1973,6 +2292,8 @@ function InterimButtonsView({
|
|
|
1973
2292
|
if (showCardForm) {
|
|
1974
2293
|
const inputBorder = bStyles.cardInputBorder ?? "#e5e7eb";
|
|
1975
2294
|
const inputBg = bStyles.cardInputBackground ?? "white";
|
|
2295
|
+
const hideBackButtonLabel = isEmptySlotContent(cardBackButtonContent);
|
|
2296
|
+
const hideTitle = isEmptySlotContent(cardTitleContent);
|
|
1976
2297
|
return /* @__PURE__ */ jsxs3("div", { style: {
|
|
1977
2298
|
backgroundColor: bStyles.cardFormContainer?.backgroundColor ?? "white",
|
|
1978
2299
|
borderRadius: "8px",
|
|
@@ -1985,19 +2306,26 @@ function InterimButtonsView({
|
|
|
1985
2306
|
"button",
|
|
1986
2307
|
{
|
|
1987
2308
|
type: "button",
|
|
1988
|
-
onClick: () =>
|
|
2309
|
+
onClick: () => {
|
|
2310
|
+
if (!isCardOpenControlled) {
|
|
2311
|
+
setShowCardForm(false);
|
|
2312
|
+
}
|
|
2313
|
+
},
|
|
2314
|
+
"aria-label": "Back to payment methods",
|
|
2315
|
+
disabled: isCardOpenControlled || cardLoading,
|
|
1989
2316
|
style: {
|
|
1990
2317
|
display: "inline-flex",
|
|
1991
2318
|
alignItems: "center",
|
|
1992
|
-
gap: "0.5rem",
|
|
2319
|
+
gap: hideBackButtonLabel ? 0 : "0.5rem",
|
|
1993
2320
|
background: "none",
|
|
1994
2321
|
border: "none",
|
|
1995
|
-
cursor: "pointer",
|
|
1996
2322
|
color: "#4b5563",
|
|
1997
2323
|
fontSize: "0.85rem",
|
|
1998
2324
|
fontWeight: 500,
|
|
1999
2325
|
padding: 0,
|
|
2000
2326
|
flexShrink: 0,
|
|
2327
|
+
opacity: isCardOpenControlled || cardLoading ? 0.6 : 1,
|
|
2328
|
+
cursor: isCardOpenControlled || cardLoading ? "not-allowed" : "pointer",
|
|
2001
2329
|
...bStyles.backButton
|
|
2002
2330
|
},
|
|
2003
2331
|
children: [
|
|
@@ -2011,11 +2339,11 @@ function InterimButtonsView({
|
|
|
2011
2339
|
backgroundColor: "#f3f4f6",
|
|
2012
2340
|
...bStyles.backButtonIcon
|
|
2013
2341
|
}, children: /* @__PURE__ */ jsx5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx5("path", { d: "M15 18l-6-6 6-6" }) }) }),
|
|
2014
|
-
|
|
2342
|
+
/* @__PURE__ */ jsx5(BackButtonContentSlot, { content: cardBackButtonContent })
|
|
2015
2343
|
]
|
|
2016
2344
|
}
|
|
2017
2345
|
),
|
|
2018
|
-
/* @__PURE__ */ jsx5("div", { style: {
|
|
2346
|
+
hideTitle ? /* @__PURE__ */ jsx5("div", { style: { flex: 1 } }) : /* @__PURE__ */ jsx5("div", { style: {
|
|
2019
2347
|
flex: 1,
|
|
2020
2348
|
textAlign: "center",
|
|
2021
2349
|
fontWeight: 600,
|
|
@@ -2023,7 +2351,7 @@ function InterimButtonsView({
|
|
|
2023
2351
|
color: "#262833",
|
|
2024
2352
|
paddingRight: 80,
|
|
2025
2353
|
...bStyles.title
|
|
2026
|
-
}, children:
|
|
2354
|
+
}, children: /* @__PURE__ */ jsx5(TitleContentSlot, { content: cardTitleContent }) })
|
|
2027
2355
|
] }),
|
|
2028
2356
|
/* @__PURE__ */ jsx5("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTopLeftRadius: 8, borderTopRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx5("div", { style: { width: "60%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
|
|
2029
2357
|
/* @__PURE__ */ jsxs3("div", { style: { display: "flex" }, children: [
|
|
@@ -2057,10 +2385,16 @@ function InterimButtonsView({
|
|
|
2057
2385
|
"button",
|
|
2058
2386
|
{
|
|
2059
2387
|
type: "button",
|
|
2060
|
-
onClick: () => {
|
|
2388
|
+
onClick: async () => {
|
|
2389
|
+
if (cardLoading) return;
|
|
2390
|
+
if (onCardButtonClick) {
|
|
2391
|
+
await onCardButtonClick();
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2061
2394
|
onButtonClick?.("card");
|
|
2062
2395
|
setShowCardForm(true);
|
|
2063
2396
|
},
|
|
2397
|
+
disabled: cardLoading,
|
|
2064
2398
|
style: {
|
|
2065
2399
|
width: "100%",
|
|
2066
2400
|
padding: "0.9rem 1rem",
|
|
@@ -2070,7 +2404,7 @@ function InterimButtonsView({
|
|
|
2070
2404
|
borderRadius: "8px",
|
|
2071
2405
|
fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
|
|
2072
2406
|
fontWeight: 600,
|
|
2073
|
-
cursor: "pointer",
|
|
2407
|
+
cursor: cardLoading ? "not-allowed" : "pointer",
|
|
2074
2408
|
display: "flex",
|
|
2075
2409
|
alignItems: "center",
|
|
2076
2410
|
justifyContent: "center",
|
|
@@ -2078,6 +2412,7 @@ function InterimButtonsView({
|
|
|
2078
2412
|
boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
|
|
2079
2413
|
transition: "transform 0.1s",
|
|
2080
2414
|
position: "relative",
|
|
2415
|
+
opacity: cardLoading ? 0.6 : 1,
|
|
2081
2416
|
...bStyles.cardButton
|
|
2082
2417
|
},
|
|
2083
2418
|
onMouseDown: (e) => {
|
|
@@ -2089,12 +2424,29 @@ function InterimButtonsView({
|
|
|
2089
2424
|
children: /* @__PURE__ */ jsx5(CardButtonContentSlot, { content: cardButtonContent })
|
|
2090
2425
|
}
|
|
2091
2426
|
),
|
|
2427
|
+
errorMessage && /* @__PURE__ */ jsxs3("div", { style: {
|
|
2428
|
+
margin: "0.25rem 0",
|
|
2429
|
+
padding: "0.625rem 0.875rem",
|
|
2430
|
+
background: "#FEF2F2",
|
|
2431
|
+
border: "1px solid #FECACA",
|
|
2432
|
+
borderRadius: "8px",
|
|
2433
|
+
color: "#991B1B",
|
|
2434
|
+
fontSize: "0.85rem",
|
|
2435
|
+
fontWeight: 600,
|
|
2436
|
+
display: "flex",
|
|
2437
|
+
alignItems: "center",
|
|
2438
|
+
gap: "0.5rem",
|
|
2439
|
+
...bStyles.errorBanner
|
|
2440
|
+
}, children: [
|
|
2441
|
+
/* @__PURE__ */ jsx5("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx5("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" }) }),
|
|
2442
|
+
errorMessage
|
|
2443
|
+
] }),
|
|
2092
2444
|
/* @__PURE__ */ jsx5("style", { children: `@keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
|
|
2093
2445
|
] });
|
|
2094
2446
|
}
|
|
2095
2447
|
|
|
2096
2448
|
// src/checkout-form.tsx
|
|
2097
|
-
import { FloPayError as
|
|
2449
|
+
import { FloPayError as FloPayError4 } from "@flopay/shared";
|
|
2098
2450
|
import { forwardRef as forwardRef2, useCallback as useCallback3, useEffect as useEffect5, useImperativeHandle as useImperativeHandle2, useState as useState4 } from "react";
|
|
2099
2451
|
import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2100
2452
|
var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
|
|
@@ -2110,6 +2462,7 @@ function CheckoutFormInner({
|
|
|
2110
2462
|
userId,
|
|
2111
2463
|
onComplete,
|
|
2112
2464
|
onError,
|
|
2465
|
+
onDecline,
|
|
2113
2466
|
onTokenizedBody,
|
|
2114
2467
|
layout = "auto",
|
|
2115
2468
|
submitLabel = "Pay",
|
|
@@ -2141,6 +2494,12 @@ function CheckoutFormInner({
|
|
|
2141
2494
|
},
|
|
2142
2495
|
[onErrorChange]
|
|
2143
2496
|
);
|
|
2497
|
+
const emitDecline = useCallback3(
|
|
2498
|
+
(input, overrides) => {
|
|
2499
|
+
onDecline?.(buildDeclineEvent("card", input, overrides));
|
|
2500
|
+
},
|
|
2501
|
+
[onDecline]
|
|
2502
|
+
);
|
|
2144
2503
|
const processPaymentInternal = useCallback3(
|
|
2145
2504
|
async (tokenizedBody) => {
|
|
2146
2505
|
setProcessing(true);
|
|
@@ -2187,6 +2546,7 @@ function CheckoutFormInner({
|
|
|
2187
2546
|
if (result.error) {
|
|
2188
2547
|
updateError(result.error.message);
|
|
2189
2548
|
onError?.(result.error);
|
|
2549
|
+
emitDecline(result.error);
|
|
2190
2550
|
return;
|
|
2191
2551
|
}
|
|
2192
2552
|
if (result.status === "succeeded" || result.status === "processing") {
|
|
@@ -2221,13 +2581,17 @@ function CheckoutFormInner({
|
|
|
2221
2581
|
}
|
|
2222
2582
|
const errorMessage = json?.message ?? "Payment failed. Please try again.";
|
|
2223
2583
|
updateError(errorMessage);
|
|
2584
|
+
emitDecline(errorMessage, {
|
|
2585
|
+
code: json?.code,
|
|
2586
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
2587
|
+
});
|
|
2224
2588
|
} catch (err) {
|
|
2225
2589
|
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
2226
2590
|
} finally {
|
|
2227
2591
|
setProcessing(false);
|
|
2228
2592
|
}
|
|
2229
2593
|
},
|
|
2230
|
-
[baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError]
|
|
2594
|
+
[baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError, emitDecline]
|
|
2231
2595
|
);
|
|
2232
2596
|
const dispatchTokenizedBody = useCallback3(
|
|
2233
2597
|
(tokenizedBody) => {
|
|
@@ -2251,6 +2615,7 @@ function CheckoutFormInner({
|
|
|
2251
2615
|
if (result.error) {
|
|
2252
2616
|
updateError(result.error.message);
|
|
2253
2617
|
onError?.(result.error);
|
|
2618
|
+
emitDecline(result.error);
|
|
2254
2619
|
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
2255
2620
|
dispatchTokenizedBody({
|
|
2256
2621
|
id: result.paymentIntentId,
|
|
@@ -2264,7 +2629,7 @@ function CheckoutFormInner({
|
|
|
2264
2629
|
setIs3DSActive(false);
|
|
2265
2630
|
}
|
|
2266
2631
|
}
|
|
2267
|
-
}), [flopay, dispatchTokenizedBody, onError, updateError]);
|
|
2632
|
+
}), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
|
|
2268
2633
|
useEffect5(() => {
|
|
2269
2634
|
if (typeof window === "undefined") return;
|
|
2270
2635
|
const stored = localStorage.getItem(WALLET_RESUME_KEY2);
|
|
@@ -2302,7 +2667,7 @@ function CheckoutFormInner({
|
|
|
2302
2667
|
return;
|
|
2303
2668
|
}
|
|
2304
2669
|
if (!sessionId || !email) {
|
|
2305
|
-
throw new
|
|
2670
|
+
throw new FloPayError4("Missing sessionId or email", "validation_error");
|
|
2306
2671
|
}
|
|
2307
2672
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
2308
2673
|
method: "POST",
|
|
@@ -2314,16 +2679,18 @@ function CheckoutFormInner({
|
|
|
2314
2679
|
isPaypal: false
|
|
2315
2680
|
})
|
|
2316
2681
|
});
|
|
2317
|
-
if (!intentResponse.ok) throw new
|
|
2682
|
+
if (!intentResponse.ok) throw new FloPayError4("Failed to create payment intent", "api_error");
|
|
2318
2683
|
const intentJson = await intentResponse.json();
|
|
2319
2684
|
const intentClientSecret = intentJson.data?.id;
|
|
2320
|
-
if (!intentClientSecret) throw new
|
|
2685
|
+
if (!intentClientSecret) throw new FloPayError4("No client_secret in payment intent response", "api_error");
|
|
2321
2686
|
const confirmResult = await flopay.confirmCardPayment({
|
|
2322
2687
|
clientSecret: intentClientSecret,
|
|
2323
2688
|
paymentMethodId: pmResult.paymentMethodId
|
|
2324
2689
|
});
|
|
2325
2690
|
if (confirmResult.error) {
|
|
2326
2691
|
updateError(confirmResult.error.message);
|
|
2692
|
+
onError?.(confirmResult.error);
|
|
2693
|
+
emitDecline(confirmResult.error);
|
|
2327
2694
|
return;
|
|
2328
2695
|
}
|
|
2329
2696
|
dispatchTokenizedBody({
|
|
@@ -2340,7 +2707,7 @@ function CheckoutFormInner({
|
|
|
2340
2707
|
}
|
|
2341
2708
|
}
|
|
2342
2709
|
},
|
|
2343
|
-
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
|
|
2710
|
+
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError, emitDecline]
|
|
2344
2711
|
);
|
|
2345
2712
|
const isReady = flopay !== null && elements !== null;
|
|
2346
2713
|
return /* @__PURE__ */ jsxs4("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
@@ -2372,7 +2739,7 @@ function CheckoutFormInner({
|
|
|
2372
2739
|
}
|
|
2373
2740
|
|
|
2374
2741
|
// src/paypal-button.tsx
|
|
2375
|
-
import { FloPayError as
|
|
2742
|
+
import { FloPayError as FloPayError5 } from "@flopay/shared";
|
|
2376
2743
|
import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef4, useState as useState5 } from "react";
|
|
2377
2744
|
import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2378
2745
|
function PayPalButton({
|
|
@@ -2492,7 +2859,7 @@ function PayPalButton({
|
|
|
2492
2859
|
setSubmitting(true);
|
|
2493
2860
|
onErrorChange?.(null);
|
|
2494
2861
|
if (!sessionId || !email) {
|
|
2495
|
-
throw new
|
|
2862
|
+
throw new FloPayError5("Missing sessionId or email for PayPal payment", "validation_error");
|
|
2496
2863
|
}
|
|
2497
2864
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
2498
2865
|
method: "POST",
|
|
@@ -2504,10 +2871,10 @@ function PayPalButton({
|
|
|
2504
2871
|
isPaypal: "true"
|
|
2505
2872
|
})
|
|
2506
2873
|
});
|
|
2507
|
-
if (!intentResponse.ok) throw new
|
|
2874
|
+
if (!intentResponse.ok) throw new FloPayError5("Failed to create payment intent", "api_error");
|
|
2508
2875
|
const intentJson = await intentResponse.json();
|
|
2509
2876
|
const intentClientSecret = intentJson.data?.id;
|
|
2510
|
-
if (!intentClientSecret) throw new
|
|
2877
|
+
if (!intentClientSecret) throw new FloPayError5("No client_secret in response", "api_error");
|
|
2511
2878
|
const result = await flopay.confirmPayment({
|
|
2512
2879
|
clientSecret: intentClientSecret,
|
|
2513
2880
|
returnUrl: window.location.href
|