@flopay/react 0.3.15 → 0.3.19
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 +14 -8
- package/dist/index.cjs +271 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -6
- package/dist/index.d.ts +14 -6
- package/dist/index.mjs +271 -64
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ Theme presets: `'default'`, `'minimal'`, `'rounded'`, `'dark'`. Custom styles vi
|
|
|
123
123
|
|
|
124
124
|
#### Button Hooks
|
|
125
125
|
|
|
126
|
-
Use `onButtonClick` to track button interactions, `onDecline` to track declines/cancellations, and `onBeforeButtonClick` to enrich
|
|
126
|
+
Use `onButtonClick` to track button interactions, `onDecline` to track declines/cancellations, and `onBeforeButtonClick` to enrich any buttons-layout payment method before it continues:
|
|
127
127
|
|
|
128
128
|
```tsx
|
|
129
129
|
<FloPayCheckout
|
|
@@ -131,14 +131,20 @@ Use `onButtonClick` to track button interactions, `onDecline` to track declines/
|
|
|
131
131
|
createSession={{
|
|
132
132
|
clientId: 'your-client-id',
|
|
133
133
|
items: [{ providerItemId: 'product-1', providerItemName: 'Widget', totalAmount: 29.99, currency: 'EUR' }],
|
|
134
|
-
account: { userId: 'user_1' },
|
|
134
|
+
account: { userId: 'user_1', email: 'test@email.com' },
|
|
135
135
|
successUrl: '/success',
|
|
136
136
|
cancelUrl: '/cancel',
|
|
137
137
|
}}
|
|
138
|
-
onBeforeButtonClick={async ({ method }) => {
|
|
139
|
-
if (method !== 'card')
|
|
140
|
-
|
|
141
|
-
|
|
138
|
+
onBeforeButtonClick={async ({ method, createSession }) => {
|
|
139
|
+
if (method !== 'card') {
|
|
140
|
+
return {
|
|
141
|
+
tagsData: { sessionId: `checkout_${method}_clicked` },
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const email = await openEmailCaptureModal({
|
|
146
|
+
initialEmail: createSession?.account.email ?? '',
|
|
147
|
+
});
|
|
142
148
|
if (!email) return false;
|
|
143
149
|
|
|
144
150
|
return {
|
|
@@ -156,7 +162,7 @@ Use `onButtonClick` to track button interactions, `onDecline` to track declines/
|
|
|
156
162
|
/>
|
|
157
163
|
```
|
|
158
164
|
|
|
159
|
-
`onBeforeButtonClick`
|
|
165
|
+
`onBeforeButtonClick` runs for every payment button in `layout="buttons"`. Branch on `method` if only some flows need extra work. For PayPal, Apple Pay, and Google Pay, keep the hook fast because it runs during the provider button handshake before the wallet sheet or PayPal window opens.
|
|
160
166
|
|
|
161
167
|
#### Inline Session Creation
|
|
162
168
|
|
|
@@ -178,7 +184,7 @@ Skip the backend API route — create the session directly in the component:
|
|
|
178
184
|
|
|
179
185
|
The component POSTs to the billing API, gets the full session back, and renders the form — zero backend code needed.
|
|
180
186
|
|
|
181
|
-
When you use `onBeforeButtonClick
|
|
187
|
+
When you use `onBeforeButtonClick` with `createSession`, any returned `InlineSessionPatch` is merged into the draft session params before the selected buttons-layout flow continues. That lets you add tracking data or update account fields just in time without pre-creating a separate backend session.
|
|
182
188
|
|
|
183
189
|
### Advanced: Manual Provider Setup
|
|
184
190
|
|
package/dist/index.cjs
CHANGED
|
@@ -394,6 +394,25 @@ var FLOPAY_KEYFRAMES = `
|
|
|
394
394
|
100% { opacity: 0; transform: translateX(16px) scale(0.97); }
|
|
395
395
|
}
|
|
396
396
|
`;
|
|
397
|
+
var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT = 44;
|
|
398
|
+
function getButtonMethodLabel(method) {
|
|
399
|
+
switch (method) {
|
|
400
|
+
case "paypal":
|
|
401
|
+
return "PayPal";
|
|
402
|
+
case "apple_pay":
|
|
403
|
+
return "Apple Pay";
|
|
404
|
+
case "google_pay":
|
|
405
|
+
return "Google Pay";
|
|
406
|
+
default:
|
|
407
|
+
return "Card";
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
function normalizeBeforeButtonClickError(method, err) {
|
|
411
|
+
return err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(
|
|
412
|
+
err instanceof Error ? err.message : `${getButtonMethodLabel(method)} before-click hook failed.`,
|
|
413
|
+
"validation_error"
|
|
414
|
+
);
|
|
415
|
+
}
|
|
397
416
|
function FloPayKeyframes() {
|
|
398
417
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: FLOPAY_KEYFRAMES });
|
|
399
418
|
}
|
|
@@ -405,6 +424,52 @@ function toCssWeight(value) {
|
|
|
405
424
|
if (typeof value === "number" || typeof value === "string") return value;
|
|
406
425
|
return void 0;
|
|
407
426
|
}
|
|
427
|
+
function resolveExpressCheckoutLoadState(event, methods) {
|
|
428
|
+
const available = event.availablePaymentMethods;
|
|
429
|
+
if (!available) return "unavailable";
|
|
430
|
+
return methods.some((method) => available[method]) ? "ready" : "unavailable";
|
|
431
|
+
}
|
|
432
|
+
function ExpressCheckoutReadySwap({
|
|
433
|
+
state,
|
|
434
|
+
placeholderTestId,
|
|
435
|
+
children
|
|
436
|
+
}) {
|
|
437
|
+
if (state === "unavailable") return null;
|
|
438
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { position: "relative", minHeight: 44 }, children: [
|
|
439
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
440
|
+
"div",
|
|
441
|
+
{
|
|
442
|
+
"data-testid": placeholderTestId,
|
|
443
|
+
"aria-hidden": state === "ready",
|
|
444
|
+
style: {
|
|
445
|
+
position: "absolute",
|
|
446
|
+
inset: 0,
|
|
447
|
+
height: 44,
|
|
448
|
+
borderRadius: 8,
|
|
449
|
+
background: "#e5e7eb",
|
|
450
|
+
animation: state === "ready" ? void 0 : "flopay-pulse 1.5s ease-in-out infinite",
|
|
451
|
+
opacity: state === "ready" ? 0 : 1,
|
|
452
|
+
transform: state === "ready" ? "scale(0.985)" : "scale(1)",
|
|
453
|
+
transition: "opacity 140ms ease-out, transform 140ms ease-out",
|
|
454
|
+
pointerEvents: "none"
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
),
|
|
458
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
459
|
+
"div",
|
|
460
|
+
{
|
|
461
|
+
style: {
|
|
462
|
+
minHeight: 44,
|
|
463
|
+
opacity: state === "ready" ? 1 : 0,
|
|
464
|
+
transform: state === "ready" ? "translateY(0)" : "translateY(2px)",
|
|
465
|
+
transition: "opacity 140ms ease-out, transform 140ms ease-out",
|
|
466
|
+
pointerEvents: state === "ready" ? "auto" : "none"
|
|
467
|
+
},
|
|
468
|
+
children
|
|
469
|
+
}
|
|
470
|
+
)
|
|
471
|
+
] });
|
|
472
|
+
}
|
|
408
473
|
function ProcessingOverlay({ status, errorMessage }) {
|
|
409
474
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
|
|
410
475
|
position: "fixed",
|
|
@@ -518,13 +583,15 @@ function PayPalButtonInner({
|
|
|
518
583
|
onErrorChange,
|
|
519
584
|
isProcessing = false,
|
|
520
585
|
onButtonClick,
|
|
521
|
-
onDecline
|
|
586
|
+
onDecline,
|
|
587
|
+
runBeforeButtonClick
|
|
522
588
|
}) {
|
|
523
589
|
const stripe = (0, import_react_stripe_js.useStripe)();
|
|
524
590
|
const elements = (0, import_react_stripe_js.useElements)();
|
|
525
|
-
const [
|
|
591
|
+
const [loadState, setLoadState] = (0, import_react6.useState)("loading");
|
|
526
592
|
const [submitting, setSubmitting] = (0, import_react6.useState)(false);
|
|
527
593
|
const paypalResumeAttempted = (0, import_react6.useRef)(false);
|
|
594
|
+
const beforeClickRef = (0, import_react6.useRef)(null);
|
|
528
595
|
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
529
596
|
(0, import_react6.useEffect)(() => {
|
|
530
597
|
if (!stripe || paypalResumeAttempted.current) return;
|
|
@@ -573,13 +640,50 @@ function PayPalButtonInner({
|
|
|
573
640
|
}
|
|
574
641
|
})();
|
|
575
642
|
}, [stripe, onTokenizedBody, onErrorChange, onDecline]);
|
|
576
|
-
const
|
|
643
|
+
const handlePayPalClick = (0, import_react6.useCallback)(async (event) => {
|
|
644
|
+
if (isProcessing || submitting) {
|
|
645
|
+
event.reject();
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick("paypal", { deferInlineSessionPatch: true }) : { proceed: true };
|
|
649
|
+
if (!beforeClick.proceed) {
|
|
650
|
+
beforeClickRef.current = null;
|
|
651
|
+
event.reject();
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
beforeClickRef.current = {
|
|
655
|
+
accountPatch: beforeClick.accountPatch,
|
|
656
|
+
pendingInlinePatch: beforeClick.pendingInlinePatch
|
|
657
|
+
};
|
|
658
|
+
event.resolve();
|
|
659
|
+
}, [isProcessing, runBeforeButtonClick, submitting]);
|
|
660
|
+
const handlePayPalConfirm = (0, import_react6.useCallback)(async (event) => {
|
|
577
661
|
if (!stripe || !elements) return;
|
|
662
|
+
let prepared = beforeClickRef.current;
|
|
663
|
+
beforeClickRef.current = null;
|
|
664
|
+
if (!prepared && runBeforeButtonClick) {
|
|
665
|
+
const beforeClick = await runBeforeButtonClick("paypal");
|
|
666
|
+
if (!beforeClick.proceed) {
|
|
667
|
+
event.paymentFailed({ reason: "fail", message: "PayPal checkout was cancelled." });
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
prepared = {
|
|
671
|
+
accountPatch: beforeClick.accountPatch,
|
|
672
|
+
pendingInlinePatch: beforeClick.pendingInlinePatch ?? Promise.resolve({ error: null, sessionId: beforeClick.sessionId ?? sessionId })
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
const patchResult = prepared?.pendingInlinePatch ? await prepared.pendingInlinePatch : { error: null, sessionId };
|
|
676
|
+
if (patchResult.error) {
|
|
677
|
+
event.paymentFailed({ reason: "fail", message: patchResult.error.message });
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const effectiveSessionId = patchResult.sessionId ?? sessionId;
|
|
681
|
+
const effectiveEmail = prepared?.accountPatch?.email ?? email;
|
|
578
682
|
onButtonClick?.("paypal");
|
|
579
683
|
try {
|
|
580
684
|
setSubmitting(true);
|
|
581
685
|
onErrorChange?.(null);
|
|
582
|
-
if (!
|
|
686
|
+
if (!effectiveSessionId || !effectiveEmail) {
|
|
583
687
|
throw new Error("Missing sessionId or email for PayPal payment");
|
|
584
688
|
}
|
|
585
689
|
const createPM = stripe.createPaymentMethod;
|
|
@@ -591,8 +695,8 @@ function PayPalButtonInner({
|
|
|
591
695
|
method: "POST",
|
|
592
696
|
headers: { "Content-Type": "application/json" },
|
|
593
697
|
body: JSON.stringify({
|
|
594
|
-
sessionId,
|
|
595
|
-
email,
|
|
698
|
+
sessionId: effectiveSessionId,
|
|
699
|
+
email: effectiveEmail,
|
|
596
700
|
paymentMethodType: paymentMethod?.id ?? "paypal",
|
|
597
701
|
isPaypal: "true"
|
|
598
702
|
})
|
|
@@ -626,22 +730,26 @@ function PayPalButtonInner({
|
|
|
626
730
|
type: "card",
|
|
627
731
|
threeDSecureActionResultTokenId: paymentIntent?.id,
|
|
628
732
|
isPaypal: true
|
|
733
|
+
}, {
|
|
734
|
+
accountPatch: prepared?.accountPatch,
|
|
735
|
+
sessionId: effectiveSessionId
|
|
629
736
|
});
|
|
630
737
|
} catch (err) {
|
|
631
738
|
onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
|
|
632
739
|
} finally {
|
|
633
740
|
setSubmitting(false);
|
|
634
741
|
}
|
|
635
|
-
}, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]);
|
|
742
|
+
}, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, onButtonClick, runBeforeButtonClick]);
|
|
636
743
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
637
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("
|
|
744
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-paypal-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
638
745
|
import_react_stripe_js.ExpressCheckoutElement,
|
|
639
746
|
{
|
|
640
|
-
onReady: () =>
|
|
641
|
-
onLoadError: () =>
|
|
642
|
-
|
|
747
|
+
onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["paypal"])),
|
|
748
|
+
onLoadError: () => setLoadState("unavailable"),
|
|
749
|
+
onClick: handlePayPalClick,
|
|
643
750
|
onConfirm: handlePayPalConfirm,
|
|
644
751
|
onCancel: () => {
|
|
752
|
+
beforeClickRef.current = null;
|
|
645
753
|
onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
|
|
646
754
|
},
|
|
647
755
|
options: {
|
|
@@ -667,18 +775,43 @@ function WalletButtonInner({
|
|
|
667
775
|
onTokenizedBody,
|
|
668
776
|
onErrorChange,
|
|
669
777
|
onButtonClick,
|
|
670
|
-
onDecline
|
|
778
|
+
onDecline,
|
|
779
|
+
runBeforeButtonClick
|
|
671
780
|
}) {
|
|
672
781
|
const stripe = (0, import_react_stripe_js.useStripe)();
|
|
673
782
|
const elements = (0, import_react_stripe_js.useElements)();
|
|
674
|
-
const [
|
|
783
|
+
const [loadState, setLoadState] = (0, import_react6.useState)("loading");
|
|
675
784
|
const [submitting, setSubmitting] = (0, import_react6.useState)(false);
|
|
676
785
|
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
677
786
|
const lastWalletMethodRef = (0, import_react6.useRef)("card");
|
|
787
|
+
const beforeClickRef = (0, import_react6.useRef)(null);
|
|
678
788
|
const handleWalletConfirm = (0, import_react6.useCallback)(
|
|
679
|
-
async (
|
|
789
|
+
async (event) => {
|
|
680
790
|
if (!stripe || !elements) return;
|
|
681
|
-
const walletType =
|
|
791
|
+
const walletType = event.expressPaymentType;
|
|
792
|
+
let prepared = beforeClickRef.current;
|
|
793
|
+
beforeClickRef.current = null;
|
|
794
|
+
if (!prepared && runBeforeButtonClick) {
|
|
795
|
+
const beforeClick = await runBeforeButtonClick(
|
|
796
|
+
walletType === "apple_pay" ? "apple_pay" : "google_pay"
|
|
797
|
+
);
|
|
798
|
+
if (!beforeClick.proceed) {
|
|
799
|
+
event.paymentFailed({ reason: "fail", message: "Wallet checkout was cancelled." });
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
prepared = {
|
|
803
|
+
accountPatch: beforeClick.accountPatch,
|
|
804
|
+
pendingInlinePatch: beforeClick.pendingInlinePatch ?? Promise.resolve({ error: null, sessionId: beforeClick.sessionId ?? sessionId })
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
const patchResult = prepared?.pendingInlinePatch ? await prepared.pendingInlinePatch : { error: null, sessionId };
|
|
808
|
+
if (patchResult.error) {
|
|
809
|
+
event.paymentFailed({ reason: "fail", message: patchResult.error.message });
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
|
|
813
|
+
const effectiveSessionId = patchResult.sessionId ?? sessionId;
|
|
814
|
+
const effectiveEmail = prepared?.accountPatch?.email ?? email;
|
|
682
815
|
onButtonClick?.(walletType === "apple_pay" ? "apple_pay" : "google_pay");
|
|
683
816
|
try {
|
|
684
817
|
setSubmitting(true);
|
|
@@ -693,15 +826,15 @@ function WalletButtonInner({
|
|
|
693
826
|
onErrorChange?.(pmError?.message ?? "Failed to create payment method.");
|
|
694
827
|
return;
|
|
695
828
|
}
|
|
696
|
-
if (!
|
|
829
|
+
if (!effectiveSessionId || !effectiveEmail) {
|
|
697
830
|
throw new Error("Missing sessionId or email for wallet payment");
|
|
698
831
|
}
|
|
699
832
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
700
833
|
method: "POST",
|
|
701
834
|
headers: { "Content-Type": "application/json" },
|
|
702
835
|
body: JSON.stringify({
|
|
703
|
-
sessionId,
|
|
704
|
-
email,
|
|
836
|
+
sessionId: effectiveSessionId,
|
|
837
|
+
email: effectiveEmail,
|
|
705
838
|
paymentMethodType: paymentMethod.id,
|
|
706
839
|
isPaypal: false
|
|
707
840
|
})
|
|
@@ -715,7 +848,6 @@ function WalletButtonInner({
|
|
|
715
848
|
{ payment_method: paymentMethod.id }
|
|
716
849
|
);
|
|
717
850
|
if (confirmError) {
|
|
718
|
-
const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
|
|
719
851
|
const message = confirmError.message ?? "Wallet payment failed.";
|
|
720
852
|
onErrorChange?.(message);
|
|
721
853
|
onDecline?.(buildDeclineEvent(method, message, {
|
|
@@ -727,6 +859,9 @@ function WalletButtonInner({
|
|
|
727
859
|
id: paymentMethod.id,
|
|
728
860
|
type: "card",
|
|
729
861
|
threeDSecureActionResultTokenId: paymentIntent?.id
|
|
862
|
+
}, {
|
|
863
|
+
accountPatch: prepared?.accountPatch,
|
|
864
|
+
sessionId: effectiveSessionId
|
|
730
865
|
});
|
|
731
866
|
} catch (err) {
|
|
732
867
|
onErrorChange?.(err instanceof Error ? err.message : "Wallet payment failed. Please try again.");
|
|
@@ -734,21 +869,31 @@ function WalletButtonInner({
|
|
|
734
869
|
setSubmitting(false);
|
|
735
870
|
}
|
|
736
871
|
},
|
|
737
|
-
[stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]
|
|
872
|
+
[stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline, onButtonClick, runBeforeButtonClick]
|
|
738
873
|
);
|
|
739
874
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
740
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("
|
|
875
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ExpressCheckoutReadySwap, { state: loadState, placeholderTestId: "flopay-wallet-placeholder", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
741
876
|
import_react_stripe_js.ExpressCheckoutElement,
|
|
742
877
|
{
|
|
743
|
-
onReady: () =>
|
|
744
|
-
onLoadError: () =>
|
|
745
|
-
|
|
746
|
-
onClick: (event) => {
|
|
878
|
+
onReady: (event) => setLoadState(resolveExpressCheckoutLoadState(event, ["applePay", "googlePay"])),
|
|
879
|
+
onLoadError: () => setLoadState("unavailable"),
|
|
880
|
+
onClick: async (event) => {
|
|
747
881
|
lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
|
|
882
|
+
const beforeClick = runBeforeButtonClick ? await runBeforeButtonClick(lastWalletMethodRef.current, { deferInlineSessionPatch: true }) : { proceed: true };
|
|
883
|
+
if (!beforeClick.proceed) {
|
|
884
|
+
beforeClickRef.current = null;
|
|
885
|
+
event.reject();
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
beforeClickRef.current = {
|
|
889
|
+
accountPatch: beforeClick.accountPatch,
|
|
890
|
+
pendingInlinePatch: beforeClick.pendingInlinePatch
|
|
891
|
+
};
|
|
748
892
|
event.resolve();
|
|
749
893
|
},
|
|
750
894
|
onConfirm: handleWalletConfirm,
|
|
751
895
|
onCancel: () => {
|
|
896
|
+
beforeClickRef.current = null;
|
|
752
897
|
onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
|
|
753
898
|
},
|
|
754
899
|
options: {
|
|
@@ -786,6 +931,7 @@ function SplitCardFormInner({
|
|
|
786
931
|
isProcessing: externalProcessing,
|
|
787
932
|
error: externalError,
|
|
788
933
|
onErrorChange,
|
|
934
|
+
onFullNameChange,
|
|
789
935
|
onFirstNameChange,
|
|
790
936
|
onLastNameChange,
|
|
791
937
|
showPayPal = true,
|
|
@@ -904,59 +1050,96 @@ function SplitCardFormInner({
|
|
|
904
1050
|
const showWallets = showApplePay || showGooglePay;
|
|
905
1051
|
const handleNameChange = (0, import_react6.useCallback)((value) => {
|
|
906
1052
|
setFullName(value);
|
|
1053
|
+
onFullNameChange?.(value);
|
|
907
1054
|
const parts = value.trim().split(/\s+/);
|
|
908
1055
|
onFirstNameChange?.(parts[0] ?? "");
|
|
909
1056
|
onLastNameChange?.(parts.length > 1 ? parts.slice(1).join(" ") : "");
|
|
910
|
-
}, [onFirstNameChange, onLastNameChange]);
|
|
911
|
-
const
|
|
912
|
-
|
|
1057
|
+
}, [onFullNameChange, onFirstNameChange, onLastNameChange]);
|
|
1058
|
+
const applyInlineSessionPatch = (0, import_react6.useCallback)(
|
|
1059
|
+
(patch, method) => {
|
|
1060
|
+
if (!checkout.applyInlineSessionPatch) {
|
|
1061
|
+
return Promise.resolve({ error: null, sessionId });
|
|
1062
|
+
}
|
|
1063
|
+
return checkout.applyInlineSessionPatch(patch).then((result) => ({
|
|
1064
|
+
error: null,
|
|
1065
|
+
sessionId: result.sessionId || sessionId
|
|
1066
|
+
})).catch((err) => {
|
|
1067
|
+
const floPayErr = normalizeBeforeButtonClickError(method, err);
|
|
1068
|
+
updateError(floPayErr.message);
|
|
1069
|
+
onError?.(floPayErr);
|
|
1070
|
+
return { error: floPayErr, sessionId };
|
|
1071
|
+
});
|
|
1072
|
+
},
|
|
1073
|
+
[checkout.applyInlineSessionPatch, onError, sessionId, updateError]
|
|
1074
|
+
);
|
|
1075
|
+
const runBeforeButtonClick = (0, import_react6.useCallback)(async (method, options) => {
|
|
1076
|
+
if (!onBeforeButtonClick) return { proceed: true };
|
|
913
1077
|
try {
|
|
914
1078
|
const result = await onBeforeButtonClick({
|
|
915
|
-
method
|
|
916
|
-
sessionId: sessionId || void 0
|
|
1079
|
+
method,
|
|
1080
|
+
sessionId: sessionId || void 0,
|
|
1081
|
+
createSession: checkout.inlineSessionDraft
|
|
917
1082
|
});
|
|
918
1083
|
if (result === false) {
|
|
919
|
-
return false;
|
|
1084
|
+
return { proceed: false };
|
|
920
1085
|
}
|
|
921
1086
|
if (result && typeof result === "object") {
|
|
922
|
-
await checkout.applyInlineSessionPatch?.(result);
|
|
923
1087
|
if (result.account) {
|
|
924
1088
|
setAccountPatch((prev) => ({ ...prev ?? {}, ...result.account }));
|
|
925
1089
|
}
|
|
1090
|
+
const pendingInlinePatch = applyInlineSessionPatch(result, method);
|
|
1091
|
+
if (options?.deferInlineSessionPatch) {
|
|
1092
|
+
return {
|
|
1093
|
+
proceed: true,
|
|
1094
|
+
accountPatch: result.account,
|
|
1095
|
+
pendingInlinePatch
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
const patchResult = await pendingInlinePatch;
|
|
1099
|
+
if (patchResult.error) {
|
|
1100
|
+
return {
|
|
1101
|
+
proceed: false,
|
|
1102
|
+
accountPatch: result.account
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
return {
|
|
1106
|
+
proceed: true,
|
|
1107
|
+
accountPatch: result.account,
|
|
1108
|
+
sessionId: patchResult.sessionId
|
|
1109
|
+
};
|
|
926
1110
|
}
|
|
927
|
-
return true;
|
|
1111
|
+
return { proceed: true };
|
|
928
1112
|
} catch (err) {
|
|
929
|
-
const floPayErr =
|
|
930
|
-
err instanceof Error ? err.message : "Card before-click hook failed.",
|
|
931
|
-
"validation_error"
|
|
932
|
-
);
|
|
1113
|
+
const floPayErr = normalizeBeforeButtonClickError(method, err);
|
|
933
1114
|
updateError(floPayErr.message);
|
|
934
1115
|
onError?.(floPayErr);
|
|
935
|
-
return false;
|
|
1116
|
+
return { proceed: false };
|
|
936
1117
|
}
|
|
937
|
-
}, [checkout.
|
|
1118
|
+
}, [applyInlineSessionPatch, checkout.inlineSessionDraft, onBeforeButtonClick, onError, sessionId, updateError]);
|
|
938
1119
|
const processPaymentInternal = (0, import_react6.useCallback)(
|
|
939
|
-
async (tokenizedBody) => {
|
|
1120
|
+
async (tokenizedBody, overrides) => {
|
|
940
1121
|
if (processingRef.current) return;
|
|
941
1122
|
processingRef.current = true;
|
|
942
1123
|
setProcessing(true);
|
|
943
1124
|
setOverlayStatus("processing");
|
|
944
1125
|
updateError(null);
|
|
1126
|
+
const effectiveSessionId = overrides?.sessionId ?? sessionId;
|
|
1127
|
+
const effectiveAccount = mergeAccountPatch(resolvedAccount, overrides?.accountPatch);
|
|
945
1128
|
try {
|
|
946
1129
|
const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
|
|
947
1130
|
method: "POST",
|
|
948
1131
|
headers: {
|
|
949
1132
|
"Content-Type": "application/json",
|
|
950
|
-
"x-user-id":
|
|
1133
|
+
"x-user-id": effectiveAccount.userId ?? ""
|
|
951
1134
|
},
|
|
952
1135
|
body: JSON.stringify({
|
|
953
|
-
sessionId,
|
|
1136
|
+
sessionId: effectiveSessionId,
|
|
954
1137
|
tokenizedData: tokenizedBody,
|
|
955
1138
|
accountData: {
|
|
956
|
-
userId:
|
|
957
|
-
email:
|
|
958
|
-
firstName:
|
|
959
|
-
lastName:
|
|
1139
|
+
userId: effectiveAccount.userId ?? "",
|
|
1140
|
+
email: effectiveAccount.email ?? "",
|
|
1141
|
+
firstName: effectiveAccount.firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
|
|
1142
|
+
lastName: effectiveAccount.lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
|
|
960
1143
|
...enableAVS ? { zip: zipCodeRef.current, country: selectedCountryRef.current } : {}
|
|
961
1144
|
},
|
|
962
1145
|
chv
|
|
@@ -1065,11 +1248,11 @@ function SplitCardFormInner({
|
|
|
1065
1248
|
[baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, onComplete, onError, updateError, emitDecline]
|
|
1066
1249
|
);
|
|
1067
1250
|
const dispatchTokenizedBody = (0, import_react6.useCallback)(
|
|
1068
|
-
(tokenizedBody) => {
|
|
1251
|
+
(tokenizedBody, overrides) => {
|
|
1069
1252
|
if (onTokenizedBody) {
|
|
1070
1253
|
onTokenizedBody(tokenizedBody);
|
|
1071
1254
|
} else {
|
|
1072
|
-
processPaymentInternal(tokenizedBody);
|
|
1255
|
+
processPaymentInternal(tokenizedBody, overrides);
|
|
1073
1256
|
}
|
|
1074
1257
|
},
|
|
1075
1258
|
[onTokenizedBody, processPaymentInternal]
|
|
@@ -1496,6 +1679,7 @@ function SplitCardFormInner({
|
|
|
1496
1679
|
if (layout === "buttons") {
|
|
1497
1680
|
const isButtonsView = viewState === "buttons" || viewState === "expanding";
|
|
1498
1681
|
const isCardView = viewState === "expanding" || viewState === "card" || viewState === "collapsing";
|
|
1682
|
+
const cardButtonSizing = cardButtonContent === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
|
|
1499
1683
|
const buttonsAnim = viewState === "expanding" ? `flopay-buttons-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : viewState === "collapsing" ? `flopay-buttons-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : void 0;
|
|
1500
1684
|
const cardAnim = viewState === "expanding" ? `flopay-card-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : viewState === "collapsing" ? `flopay-card-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : void 0;
|
|
1501
1685
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
@@ -1521,9 +1705,10 @@ function SplitCardFormInner({
|
|
|
1521
1705
|
onErrorChange: updateError,
|
|
1522
1706
|
isProcessing: isSubmitting,
|
|
1523
1707
|
onButtonClick,
|
|
1524
|
-
onDecline
|
|
1708
|
+
onDecline,
|
|
1709
|
+
runBeforeButtonClick
|
|
1525
1710
|
}
|
|
1526
|
-
) }) : showPayPal ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height:
|
|
1711
|
+
) }) : showPayPal ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
|
|
1527
1712
|
showWallets && stripeInstance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1528
1713
|
WalletButtonInner,
|
|
1529
1714
|
{
|
|
@@ -1535,24 +1720,25 @@ function SplitCardFormInner({
|
|
|
1535
1720
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1536
1721
|
onErrorChange: updateError,
|
|
1537
1722
|
onButtonClick,
|
|
1538
|
-
onDecline
|
|
1723
|
+
onDecline,
|
|
1724
|
+
runBeforeButtonClick
|
|
1539
1725
|
}
|
|
1540
|
-
) }) : showWallets ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height:
|
|
1726
|
+
) }) : showWallets ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
|
|
1541
1727
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1542
1728
|
"button",
|
|
1543
1729
|
{
|
|
1544
1730
|
type: "button",
|
|
1545
1731
|
onClick: async () => {
|
|
1546
1732
|
if (isSubmitting) return;
|
|
1547
|
-
const
|
|
1548
|
-
if (!
|
|
1733
|
+
const beforeClick = await runBeforeButtonClick("card");
|
|
1734
|
+
if (!beforeClick.proceed) return;
|
|
1549
1735
|
onButtonClick?.("card");
|
|
1550
1736
|
expandToCard();
|
|
1551
1737
|
},
|
|
1552
1738
|
disabled: isSubmitting,
|
|
1553
1739
|
style: {
|
|
1554
1740
|
width: "100%",
|
|
1555
|
-
|
|
1741
|
+
...cardButtonSizing,
|
|
1556
1742
|
backgroundColor: "white",
|
|
1557
1743
|
color: "#262833",
|
|
1558
1744
|
border: "1px solid #d1d5db",
|
|
@@ -1651,6 +1837,7 @@ function SplitCardFormInner({
|
|
|
1651
1837
|
|
|
1652
1838
|
// src/flopay-checkout.tsx
|
|
1653
1839
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1840
|
+
var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2 = 44;
|
|
1654
1841
|
function hasInlineSessionPatchData(patch) {
|
|
1655
1842
|
if (!patch) return false;
|
|
1656
1843
|
return Boolean(
|
|
@@ -1669,6 +1856,9 @@ function FloPayCheckout({
|
|
|
1669
1856
|
onComplete,
|
|
1670
1857
|
onError,
|
|
1671
1858
|
onDecline,
|
|
1859
|
+
onFullNameChange,
|
|
1860
|
+
onCountryChange,
|
|
1861
|
+
onZipChange,
|
|
1672
1862
|
showPayPal = true,
|
|
1673
1863
|
showApplePay = true,
|
|
1674
1864
|
showGooglePay = true,
|
|
@@ -1954,16 +2144,27 @@ function FloPayCheckout({
|
|
|
1954
2144
|
[fallbackPublishableKey, locale, resolvedBillingUrl]
|
|
1955
2145
|
);
|
|
1956
2146
|
const handleInlineSessionPatch = (0, import_react7.useCallback)(async (patch) => {
|
|
1957
|
-
if (!hasInlineSessionPatchData(patch) || cardBootstrapPending)
|
|
2147
|
+
if (!hasInlineSessionPatchData(patch) || cardBootstrapPending) {
|
|
2148
|
+
return {
|
|
2149
|
+
sessionId: resolvedSessionId,
|
|
2150
|
+
session
|
|
2151
|
+
};
|
|
2152
|
+
}
|
|
1958
2153
|
setCardBootstrapPending(true);
|
|
1959
2154
|
try {
|
|
1960
|
-
await bootstrapInlineSession(patch);
|
|
2155
|
+
const resolved = await bootstrapInlineSession(patch);
|
|
2156
|
+
return {
|
|
2157
|
+
sessionId: resolved.sid,
|
|
2158
|
+
session: resolved.result.data.session ?? null
|
|
2159
|
+
};
|
|
1961
2160
|
} finally {
|
|
1962
2161
|
setCardBootstrapPending(false);
|
|
1963
2162
|
}
|
|
1964
2163
|
}, [
|
|
1965
2164
|
bootstrapInlineSession,
|
|
1966
|
-
cardBootstrapPending
|
|
2165
|
+
cardBootstrapPending,
|
|
2166
|
+
resolvedSessionId,
|
|
2167
|
+
session
|
|
1967
2168
|
]);
|
|
1968
2169
|
(0, import_react7.useEffect)(() => {
|
|
1969
2170
|
let cancelled = false;
|
|
@@ -2116,6 +2317,7 @@ function FloPayCheckout({
|
|
|
2116
2317
|
loading: isLoading,
|
|
2117
2318
|
error: loadError,
|
|
2118
2319
|
checkoutMode: currentMode,
|
|
2320
|
+
inlineSessionDraft: effectiveCreateSession,
|
|
2119
2321
|
applyInlineSessionPatch: shouldHandleInlineSessionPatch ? handleInlineSessionPatch : void 0,
|
|
2120
2322
|
inlineSessionPatchProcessing: cardBootstrapPending
|
|
2121
2323
|
}),
|
|
@@ -2124,6 +2326,7 @@ function FloPayCheckout({
|
|
|
2124
2326
|
isLoading,
|
|
2125
2327
|
loadError,
|
|
2126
2328
|
currentMode,
|
|
2329
|
+
effectiveCreateSession,
|
|
2127
2330
|
shouldHandleInlineSessionPatch,
|
|
2128
2331
|
handleInlineSessionPatch,
|
|
2129
2332
|
cardBootstrapPending
|
|
@@ -2140,9 +2343,9 @@ function FloPayCheckout({
|
|
|
2140
2343
|
animation: "flopay-loading-pulse 1.5s ease-in-out infinite"
|
|
2141
2344
|
} });
|
|
2142
2345
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
2143
|
-
showPayPal && skeletonBar(
|
|
2144
|
-
(showApplePay || showGooglePay) && skeletonBar(
|
|
2145
|
-
skeletonBar(
|
|
2346
|
+
showPayPal && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
2347
|
+
(showApplePay || showGooglePay) && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
2348
|
+
skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
2146
2349
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes flopay-loading-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
|
|
2147
2350
|
] });
|
|
2148
2351
|
}
|
|
@@ -2267,6 +2470,7 @@ function FloPayCheckout({
|
|
|
2267
2470
|
onComplete,
|
|
2268
2471
|
onError,
|
|
2269
2472
|
onDecline,
|
|
2473
|
+
onFullNameChange,
|
|
2270
2474
|
showPayPal,
|
|
2271
2475
|
showApplePay,
|
|
2272
2476
|
showGooglePay,
|
|
@@ -2281,6 +2485,8 @@ function FloPayCheckout({
|
|
|
2281
2485
|
enableAVS,
|
|
2282
2486
|
avsLayout,
|
|
2283
2487
|
country: session?.customer?.country,
|
|
2488
|
+
onCountryChange,
|
|
2489
|
+
onZipChange,
|
|
2284
2490
|
submitLabel,
|
|
2285
2491
|
className
|
|
2286
2492
|
}
|
|
@@ -2353,6 +2559,7 @@ function InterimButtonsView({
|
|
|
2353
2559
|
background: "#e5e7eb",
|
|
2354
2560
|
animation: "flopay-interim-pulse 1.5s ease-in-out infinite"
|
|
2355
2561
|
} });
|
|
2562
|
+
const cardButtonSizing = cardButtonContent === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
|
|
2356
2563
|
if (showCardForm) {
|
|
2357
2564
|
const inputBorder = bStyles.cardInputBorder ?? "#e5e7eb";
|
|
2358
2565
|
const inputBg = bStyles.cardInputBackground ?? "white";
|
|
@@ -2443,8 +2650,8 @@ function InterimButtonsView({
|
|
|
2443
2650
|
] });
|
|
2444
2651
|
}
|
|
2445
2652
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
2446
|
-
showPayPal && skeleton(
|
|
2447
|
-
(showApplePay || showGooglePay) && skeleton(
|
|
2653
|
+
showPayPal && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
2654
|
+
(showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
2448
2655
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
2449
2656
|
"button",
|
|
2450
2657
|
{
|
|
@@ -2461,7 +2668,7 @@ function InterimButtonsView({
|
|
|
2461
2668
|
disabled: cardLoading,
|
|
2462
2669
|
style: {
|
|
2463
2670
|
width: "100%",
|
|
2464
|
-
|
|
2671
|
+
...cardButtonSizing,
|
|
2465
2672
|
backgroundColor: "white",
|
|
2466
2673
|
color: "#262833",
|
|
2467
2674
|
border: "1px solid #d1d5db",
|