@flopay/react 0.1.0
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 +378 -0
- package/dist/index.cjs +1828 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +402 -0
- package/dist/index.d.ts +402 -0
- package/dist/index.mjs +1783 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +56 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1783 @@
|
|
|
1
|
+
// src/provider.tsx
|
|
2
|
+
import { useEffect, useState, useMemo } from "react";
|
|
3
|
+
import { resolveBillingApiUrl } from "@flopay/shared";
|
|
4
|
+
|
|
5
|
+
// src/context.ts
|
|
6
|
+
import { createContext } from "react";
|
|
7
|
+
var FloPayContext = createContext({
|
|
8
|
+
flopay: null,
|
|
9
|
+
elements: null,
|
|
10
|
+
billingApiUrl: ""
|
|
11
|
+
});
|
|
12
|
+
var CheckoutContext = createContext({
|
|
13
|
+
session: null,
|
|
14
|
+
loading: false,
|
|
15
|
+
error: null
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// src/provider.tsx
|
|
19
|
+
import { jsx } from "react/jsx-runtime";
|
|
20
|
+
function FloPayProvider({
|
|
21
|
+
flopay: floPayProp,
|
|
22
|
+
options,
|
|
23
|
+
children
|
|
24
|
+
}) {
|
|
25
|
+
const [flopay, setFloPay] = useState(
|
|
26
|
+
floPayProp instanceof Promise ? null : floPayProp
|
|
27
|
+
);
|
|
28
|
+
const [elements, setElements] = useState(null);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
let cancelled = false;
|
|
31
|
+
if (floPayProp instanceof Promise) {
|
|
32
|
+
floPayProp.then((instance) => {
|
|
33
|
+
if (!cancelled) {
|
|
34
|
+
setFloPay(instance);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
} else {
|
|
38
|
+
setFloPay(floPayProp);
|
|
39
|
+
}
|
|
40
|
+
return () => {
|
|
41
|
+
cancelled = true;
|
|
42
|
+
};
|
|
43
|
+
}, [floPayProp]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!flopay) {
|
|
46
|
+
setElements(null);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const els = flopay.elements({
|
|
50
|
+
appearance: options?.appearance,
|
|
51
|
+
clientSecret: options?.clientSecret,
|
|
52
|
+
amount: options?.amount,
|
|
53
|
+
currency: options?.currency,
|
|
54
|
+
paymentMethodCreation: options?.paymentMethodCreation
|
|
55
|
+
});
|
|
56
|
+
setElements(els);
|
|
57
|
+
return () => {
|
|
58
|
+
els.destroy();
|
|
59
|
+
};
|
|
60
|
+
}, [flopay, options?.appearance, options?.clientSecret, options?.amount, options?.currency]);
|
|
61
|
+
const resolvedBillingApiUrl = resolveBillingApiUrl(options?.billingApiUrl);
|
|
62
|
+
const value = useMemo(
|
|
63
|
+
() => ({ flopay, elements, billingApiUrl: resolvedBillingApiUrl }),
|
|
64
|
+
[flopay, elements, resolvedBillingApiUrl]
|
|
65
|
+
);
|
|
66
|
+
return /* @__PURE__ */ jsx(FloPayContext.Provider, { value, children });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/flopay-checkout.tsx
|
|
70
|
+
import React4, { useCallback as useCallback2, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
|
|
71
|
+
import { loadFloPay, PaymentAPI } from "@flopay/js";
|
|
72
|
+
import { FloPayError as FloPayError2, resolveBillingApiUrl as resolveBillingApiUrl3, buildCheckoutDisplayData } from "@flopay/shared";
|
|
73
|
+
|
|
74
|
+
// src/elements.tsx
|
|
75
|
+
import { useEffect as useEffect2, useRef, useContext } from "react";
|
|
76
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
77
|
+
function createElementComponent(elementType, displayName) {
|
|
78
|
+
function ElementComponent({
|
|
79
|
+
className,
|
|
80
|
+
id,
|
|
81
|
+
style,
|
|
82
|
+
options,
|
|
83
|
+
onChange,
|
|
84
|
+
onReady,
|
|
85
|
+
onFocus,
|
|
86
|
+
onBlur,
|
|
87
|
+
onEscape
|
|
88
|
+
}) {
|
|
89
|
+
const containerRef = useRef(null);
|
|
90
|
+
const elementRef = useRef(null);
|
|
91
|
+
const { elements } = useContext(FloPayContext);
|
|
92
|
+
useEffect2(() => {
|
|
93
|
+
if (!elements || !containerRef.current) return;
|
|
94
|
+
let mounted = true;
|
|
95
|
+
(async () => {
|
|
96
|
+
let element = elements.getElement(elementType);
|
|
97
|
+
if (!element) {
|
|
98
|
+
element = await elements.create(elementType, options);
|
|
99
|
+
}
|
|
100
|
+
if (!mounted || !containerRef.current) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
element.mount(containerRef.current);
|
|
104
|
+
elementRef.current = element;
|
|
105
|
+
if (onChange) element.on("change", onChange);
|
|
106
|
+
if (onReady) element.on("ready", onReady);
|
|
107
|
+
if (onFocus) element.on("focus", onFocus);
|
|
108
|
+
if (onBlur) element.on("blur", onBlur);
|
|
109
|
+
if (onEscape) element.on("escape", onEscape);
|
|
110
|
+
})();
|
|
111
|
+
return () => {
|
|
112
|
+
mounted = false;
|
|
113
|
+
if (elementRef.current) {
|
|
114
|
+
try {
|
|
115
|
+
elementRef.current.unmount();
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
elementRef.current = null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}, [elements]);
|
|
122
|
+
return /* @__PURE__ */ jsx2("div", { ref: containerRef, className, id, style });
|
|
123
|
+
}
|
|
124
|
+
ElementComponent.displayName = displayName;
|
|
125
|
+
return ElementComponent;
|
|
126
|
+
}
|
|
127
|
+
var PaymentElement = createElementComponent("payment", "PaymentElement");
|
|
128
|
+
var CardElement = createElementComponent("card", "CardElement");
|
|
129
|
+
var CardNumberElement = createElementComponent("cardNumber", "CardNumberElement");
|
|
130
|
+
var CardExpiryElement = createElementComponent("cardExpiry", "CardExpiryElement");
|
|
131
|
+
var CardCvcElement = createElementComponent("cardCvc", "CardCvcElement");
|
|
132
|
+
var AddressElement = createElementComponent("address", "AddressElement");
|
|
133
|
+
|
|
134
|
+
// src/split-card-form.tsx
|
|
135
|
+
import {
|
|
136
|
+
ExpressCheckoutElement,
|
|
137
|
+
Elements as StripeElements,
|
|
138
|
+
useElements as useStripeElements,
|
|
139
|
+
useStripe as useStripeRaw
|
|
140
|
+
} from "@stripe/react-stripe-js";
|
|
141
|
+
import { forwardRef, useCallback, useEffect as useEffect3, useImperativeHandle, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
|
|
142
|
+
|
|
143
|
+
// src/hooks.ts
|
|
144
|
+
import { useContext as useContext2 } from "react";
|
|
145
|
+
import { resolveBillingApiUrl as resolveBillingApiUrl2 } from "@flopay/shared";
|
|
146
|
+
function useFloPay() {
|
|
147
|
+
const ctx = useContext2(FloPayContext);
|
|
148
|
+
return ctx.flopay;
|
|
149
|
+
}
|
|
150
|
+
function useElements() {
|
|
151
|
+
const ctx = useContext2(FloPayContext);
|
|
152
|
+
return ctx.elements;
|
|
153
|
+
}
|
|
154
|
+
function useCheckout() {
|
|
155
|
+
return useContext2(CheckoutContext);
|
|
156
|
+
}
|
|
157
|
+
function useBillingApiUrl() {
|
|
158
|
+
const ctx = useContext2(FloPayContext);
|
|
159
|
+
return ctx.billingApiUrl || resolveBillingApiUrl2();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// src/split-card-form.tsx
|
|
163
|
+
import { FloPayError } from "@flopay/shared";
|
|
164
|
+
import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
|
|
165
|
+
var WALLET_RESUME_KEY = "flopay_wallet_resume";
|
|
166
|
+
function ProcessingOverlay({ status }) {
|
|
167
|
+
return /* @__PURE__ */ jsx3("div", { style: {
|
|
168
|
+
position: "fixed",
|
|
169
|
+
inset: 0,
|
|
170
|
+
background: "rgba(0,0,0,0.35)",
|
|
171
|
+
display: "flex",
|
|
172
|
+
alignItems: "center",
|
|
173
|
+
justifyContent: "center",
|
|
174
|
+
zIndex: 1e3,
|
|
175
|
+
backdropFilter: "blur(2px)"
|
|
176
|
+
}, children: /* @__PURE__ */ jsxs("div", { style: {
|
|
177
|
+
background: "white",
|
|
178
|
+
borderRadius: 12,
|
|
179
|
+
padding: "2rem 2.5rem",
|
|
180
|
+
textAlign: "center",
|
|
181
|
+
boxShadow: "0 8px 32px rgba(0,0,0,0.18)",
|
|
182
|
+
minWidth: 240,
|
|
183
|
+
display: "flex",
|
|
184
|
+
flexDirection: "column",
|
|
185
|
+
alignItems: "center",
|
|
186
|
+
gap: 16
|
|
187
|
+
}, children: [
|
|
188
|
+
/* @__PURE__ */ jsxs("div", { style: { width: 48, height: 48, position: "relative" }, children: [
|
|
189
|
+
status === "processing" && /* @__PURE__ */ jsxs(
|
|
190
|
+
"svg",
|
|
191
|
+
{
|
|
192
|
+
width: "48",
|
|
193
|
+
height: "48",
|
|
194
|
+
viewBox: "0 0 24 24",
|
|
195
|
+
fill: "none",
|
|
196
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
197
|
+
style: { animation: "flopay-spin 0.8s linear infinite" },
|
|
198
|
+
children: [
|
|
199
|
+
/* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "10", stroke: "#e5e7eb", strokeWidth: "3" }),
|
|
200
|
+
/* @__PURE__ */ jsx3("path", { d: "M4 12a8 8 0 018-8v3a5 5 0 00-5 5H4z", fill: "#4A49FF" })
|
|
201
|
+
]
|
|
202
|
+
}
|
|
203
|
+
),
|
|
204
|
+
status === "success" && /* @__PURE__ */ jsx3("div", { style: { animation: "flopay-pop 0.4s cubic-bezier(0.34,1.56,0.64,1)" }, children: /* @__PURE__ */ jsxs("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
205
|
+
/* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "11", fill: "#22c55e" }),
|
|
206
|
+
/* @__PURE__ */ jsx3(
|
|
207
|
+
"path",
|
|
208
|
+
{
|
|
209
|
+
d: "M7 12.5l3 3 7-7",
|
|
210
|
+
stroke: "white",
|
|
211
|
+
strokeWidth: "2.5",
|
|
212
|
+
strokeLinecap: "round",
|
|
213
|
+
strokeLinejoin: "round",
|
|
214
|
+
style: { strokeDasharray: 20, strokeDashoffset: 20, animation: "flopay-draw 0.4s 0.15s ease forwards" }
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
] }) }),
|
|
218
|
+
status === "error" && /* @__PURE__ */ jsx3("div", { style: { animation: "flopay-shake 0.4s ease" }, children: /* @__PURE__ */ jsxs("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
219
|
+
/* @__PURE__ */ jsx3("circle", { cx: "12", cy: "12", r: "11", fill: "#ef4444" }),
|
|
220
|
+
/* @__PURE__ */ jsx3(
|
|
221
|
+
"path",
|
|
222
|
+
{
|
|
223
|
+
d: "M8 8l8 8M16 8l-8 8",
|
|
224
|
+
stroke: "white",
|
|
225
|
+
strokeWidth: "2.5",
|
|
226
|
+
strokeLinecap: "round",
|
|
227
|
+
style: { strokeDasharray: 12, strokeDashoffset: 12, animation: "flopay-draw 0.3s 0.1s ease forwards" }
|
|
228
|
+
}
|
|
229
|
+
)
|
|
230
|
+
] }) })
|
|
231
|
+
] }),
|
|
232
|
+
/* @__PURE__ */ jsxs("span", { style: {
|
|
233
|
+
fontSize: 14,
|
|
234
|
+
fontWeight: 600,
|
|
235
|
+
letterSpacing: "0.05em",
|
|
236
|
+
color: status === "success" ? "#16a34a" : status === "error" ? "#dc2626" : "#374151"
|
|
237
|
+
}, children: [
|
|
238
|
+
status === "processing" && "PROCESSING...",
|
|
239
|
+
status === "success" && "PAYMENT SUCCESSFUL",
|
|
240
|
+
status === "error" && "PAYMENT FAILED"
|
|
241
|
+
] }),
|
|
242
|
+
/* @__PURE__ */ jsx3("style", { children: `
|
|
243
|
+
@keyframes flopay-spin { to { transform: rotate(360deg); } }
|
|
244
|
+
@keyframes flopay-pop { 0% { transform: scale(0); } 100% { transform: scale(1); } }
|
|
245
|
+
@keyframes flopay-draw { to { stroke-dashoffset: 0; } }
|
|
246
|
+
@keyframes flopay-shake { 0%,100% { transform: translateX(0); } 20%,60% { transform: translateX(-4px); } 40%,80% { transform: translateX(4px); } }
|
|
247
|
+
` })
|
|
248
|
+
] }) });
|
|
249
|
+
}
|
|
250
|
+
var SplitCardForm = forwardRef(
|
|
251
|
+
function SplitCardForm2(props, ref) {
|
|
252
|
+
return /* @__PURE__ */ jsx3(SplitCardFormInner, { ...props, innerRef: ref });
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
function PayPalButtonInner({
|
|
256
|
+
sessionId,
|
|
257
|
+
email,
|
|
258
|
+
billingApiUrl,
|
|
259
|
+
onTokenizedBody,
|
|
260
|
+
onErrorChange,
|
|
261
|
+
isProcessing = false
|
|
262
|
+
}) {
|
|
263
|
+
const stripe = useStripeRaw();
|
|
264
|
+
const elements = useStripeElements();
|
|
265
|
+
const [ready, setReady] = useState2(false);
|
|
266
|
+
const [submitting, setSubmitting] = useState2(false);
|
|
267
|
+
const paypalResumeAttempted = useRef2(false);
|
|
268
|
+
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
269
|
+
useEffect3(() => {
|
|
270
|
+
if (!stripe || paypalResumeAttempted.current) return;
|
|
271
|
+
const params = new URLSearchParams(window.location.search);
|
|
272
|
+
const paymentIntentId = params.get("payment_intent");
|
|
273
|
+
const clientSecret = params.get("payment_intent_client_secret");
|
|
274
|
+
const redirectStatus = params.get("redirect_status");
|
|
275
|
+
if (!paymentIntentId || !clientSecret) return;
|
|
276
|
+
paypalResumeAttempted.current = true;
|
|
277
|
+
(async () => {
|
|
278
|
+
try {
|
|
279
|
+
setSubmitting(true);
|
|
280
|
+
if (redirectStatus === "failed") {
|
|
281
|
+
onErrorChange?.("PayPal payment was declined. Please try again.");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const { paymentIntent, error } = await stripe.retrievePaymentIntent(clientSecret);
|
|
285
|
+
if (error) {
|
|
286
|
+
onErrorChange?.(error.message ?? "Failed to retrieve PayPal payment status.");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded")) {
|
|
290
|
+
const paymentMethodId = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
|
|
291
|
+
onTokenizedBody({
|
|
292
|
+
id: paymentMethodId ?? paymentIntent.id,
|
|
293
|
+
type: "card",
|
|
294
|
+
threeDSecureActionResultTokenId: paymentIntent.id,
|
|
295
|
+
isPaypal: true
|
|
296
|
+
});
|
|
297
|
+
const url = new URL(window.location.href);
|
|
298
|
+
url.searchParams.delete("payment_intent");
|
|
299
|
+
url.searchParams.delete("payment_intent_client_secret");
|
|
300
|
+
url.searchParams.delete("redirect_status");
|
|
301
|
+
window.history.replaceState({}, "", url.toString());
|
|
302
|
+
} else {
|
|
303
|
+
onErrorChange?.("PayPal payment was not completed. Please try again.");
|
|
304
|
+
}
|
|
305
|
+
} catch (err) {
|
|
306
|
+
onErrorChange?.(err instanceof Error ? err.message : "Failed to complete PayPal payment.");
|
|
307
|
+
} finally {
|
|
308
|
+
setSubmitting(false);
|
|
309
|
+
}
|
|
310
|
+
})();
|
|
311
|
+
}, [stripe, onTokenizedBody, onErrorChange]);
|
|
312
|
+
const handlePayPalConfirm = useCallback(async (_event) => {
|
|
313
|
+
if (!stripe || !elements) return;
|
|
314
|
+
try {
|
|
315
|
+
setSubmitting(true);
|
|
316
|
+
onErrorChange?.(null);
|
|
317
|
+
if (!sessionId || !email) {
|
|
318
|
+
throw new Error("Missing sessionId or email for PayPal payment");
|
|
319
|
+
}
|
|
320
|
+
const createPM = stripe.createPaymentMethod;
|
|
321
|
+
const { error: pmError, paymentMethod } = await createPM({ type: "paypal" });
|
|
322
|
+
if (pmError) {
|
|
323
|
+
console.warn("[FloPay] Could not create PayPal PM upfront:", pmError.message);
|
|
324
|
+
}
|
|
325
|
+
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
326
|
+
method: "POST",
|
|
327
|
+
headers: { "Content-Type": "application/json" },
|
|
328
|
+
body: JSON.stringify({
|
|
329
|
+
sessionId,
|
|
330
|
+
email,
|
|
331
|
+
paymentMethodType: paymentMethod?.id ?? "paypal",
|
|
332
|
+
isPaypal: "true"
|
|
333
|
+
})
|
|
334
|
+
});
|
|
335
|
+
if (!intentResponse.ok) throw new Error("Failed to create payment intent");
|
|
336
|
+
const intentJson = await intentResponse.json();
|
|
337
|
+
const intentClientSecret = intentJson.data?.id;
|
|
338
|
+
if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
|
|
339
|
+
const confirmParams = {
|
|
340
|
+
return_url: window.location.href
|
|
341
|
+
};
|
|
342
|
+
if (paymentMethod?.id) {
|
|
343
|
+
confirmParams["payment_method"] = paymentMethod.id;
|
|
344
|
+
}
|
|
345
|
+
const { error: confirmError, paymentIntent } = await stripe.confirmPayment({
|
|
346
|
+
clientSecret: intentClientSecret,
|
|
347
|
+
confirmParams,
|
|
348
|
+
redirect: "if_required"
|
|
349
|
+
});
|
|
350
|
+
if (confirmError) {
|
|
351
|
+
onErrorChange?.(confirmError.message ?? "PayPal payment failed.");
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
|
|
355
|
+
onTokenizedBody({
|
|
356
|
+
id: confirmedPmId ?? paymentIntent?.id,
|
|
357
|
+
type: "card",
|
|
358
|
+
threeDSecureActionResultTokenId: paymentIntent?.id,
|
|
359
|
+
isPaypal: true
|
|
360
|
+
});
|
|
361
|
+
} catch (err) {
|
|
362
|
+
onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
|
|
363
|
+
} finally {
|
|
364
|
+
setSubmitting(false);
|
|
365
|
+
}
|
|
366
|
+
}, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]);
|
|
367
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
368
|
+
/* @__PURE__ */ jsx3("div", { style: { marginBottom: ready ? "0.5rem" : 0 }, children: /* @__PURE__ */ jsx3(
|
|
369
|
+
ExpressCheckoutElement,
|
|
370
|
+
{
|
|
371
|
+
onReady: () => setReady(true),
|
|
372
|
+
onLoadError: () => {
|
|
373
|
+
},
|
|
374
|
+
onConfirm: handlePayPalConfirm,
|
|
375
|
+
options: {
|
|
376
|
+
buttonType: { paypal: "paypal" },
|
|
377
|
+
paymentMethods: {
|
|
378
|
+
applePay: "never",
|
|
379
|
+
googlePay: "never",
|
|
380
|
+
paypal: "auto",
|
|
381
|
+
link: "never"
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
) }),
|
|
386
|
+
submitting && /* @__PURE__ */ jsx3(ProcessingOverlay, { status: "processing" })
|
|
387
|
+
] });
|
|
388
|
+
}
|
|
389
|
+
function WalletButtonInner({
|
|
390
|
+
sessionId,
|
|
391
|
+
email,
|
|
392
|
+
billingApiUrl,
|
|
393
|
+
showApplePay = true,
|
|
394
|
+
showGooglePay = true,
|
|
395
|
+
onTokenizedBody,
|
|
396
|
+
onErrorChange
|
|
397
|
+
}) {
|
|
398
|
+
const stripe = useStripeRaw();
|
|
399
|
+
const elements = useStripeElements();
|
|
400
|
+
const [ready, setReady] = useState2(false);
|
|
401
|
+
const [submitting, setSubmitting] = useState2(false);
|
|
402
|
+
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
403
|
+
const handleWalletConfirm = useCallback(
|
|
404
|
+
async (_event) => {
|
|
405
|
+
if (!stripe || !elements) return;
|
|
406
|
+
try {
|
|
407
|
+
setSubmitting(true);
|
|
408
|
+
onErrorChange?.(null);
|
|
409
|
+
const { error: submitError } = await elements.submit();
|
|
410
|
+
if (submitError) {
|
|
411
|
+
onErrorChange?.(submitError.message ?? "Wallet payment failed.");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const { error: pmError, paymentMethod } = await stripe.createPaymentMethod({ elements });
|
|
415
|
+
if (pmError || !paymentMethod) {
|
|
416
|
+
onErrorChange?.(pmError?.message ?? "Failed to create payment method.");
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (!sessionId || !email) {
|
|
420
|
+
throw new Error("Missing sessionId or email for wallet payment");
|
|
421
|
+
}
|
|
422
|
+
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: { "Content-Type": "application/json" },
|
|
425
|
+
body: JSON.stringify({
|
|
426
|
+
sessionId,
|
|
427
|
+
email,
|
|
428
|
+
paymentMethodType: paymentMethod.id,
|
|
429
|
+
isPaypal: false
|
|
430
|
+
})
|
|
431
|
+
});
|
|
432
|
+
if (!intentResponse.ok) throw new Error("Failed to create payment intent");
|
|
433
|
+
const intentJson = await intentResponse.json();
|
|
434
|
+
const intentClientSecret = intentJson.data?.id;
|
|
435
|
+
if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
|
|
436
|
+
const { error: confirmError, paymentIntent } = await stripe.confirmCardPayment(
|
|
437
|
+
intentClientSecret,
|
|
438
|
+
{ payment_method: paymentMethod.id }
|
|
439
|
+
);
|
|
440
|
+
if (confirmError) {
|
|
441
|
+
onErrorChange?.(confirmError.message ?? "Wallet payment failed.");
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
onTokenizedBody({
|
|
445
|
+
id: paymentMethod.id,
|
|
446
|
+
type: "card",
|
|
447
|
+
threeDSecureActionResultTokenId: paymentIntent?.id
|
|
448
|
+
});
|
|
449
|
+
} catch (err) {
|
|
450
|
+
onErrorChange?.(err instanceof Error ? err.message : "Wallet payment failed. Please try again.");
|
|
451
|
+
} finally {
|
|
452
|
+
setSubmitting(false);
|
|
453
|
+
}
|
|
454
|
+
},
|
|
455
|
+
[stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]
|
|
456
|
+
);
|
|
457
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
458
|
+
/* @__PURE__ */ jsx3("div", { style: { marginBottom: ready ? "0.5rem" : 0 }, children: /* @__PURE__ */ jsx3(
|
|
459
|
+
ExpressCheckoutElement,
|
|
460
|
+
{
|
|
461
|
+
onReady: () => setReady(true),
|
|
462
|
+
onLoadError: () => {
|
|
463
|
+
},
|
|
464
|
+
onConfirm: handleWalletConfirm,
|
|
465
|
+
options: {
|
|
466
|
+
buttonType: { applePay: "plain", googlePay: "plain" },
|
|
467
|
+
paymentMethods: {
|
|
468
|
+
applePay: showApplePay ? "auto" : "never",
|
|
469
|
+
googlePay: showGooglePay ? "auto" : "never",
|
|
470
|
+
paypal: "never",
|
|
471
|
+
link: "never"
|
|
472
|
+
},
|
|
473
|
+
layout: { overflow: "never" }
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
) }),
|
|
477
|
+
submitting && /* @__PURE__ */ jsx3(ProcessingOverlay, { status: "processing" })
|
|
478
|
+
] });
|
|
479
|
+
}
|
|
480
|
+
function SplitCardFormInner({
|
|
481
|
+
sessionId,
|
|
482
|
+
billingApiUrl,
|
|
483
|
+
email,
|
|
484
|
+
userId,
|
|
485
|
+
onComplete,
|
|
486
|
+
onError,
|
|
487
|
+
onTokenizedBody,
|
|
488
|
+
firstName,
|
|
489
|
+
lastName,
|
|
490
|
+
chv,
|
|
491
|
+
submitLabel = "CONFIRM PAYMENT",
|
|
492
|
+
className,
|
|
493
|
+
children,
|
|
494
|
+
isProcessing: externalProcessing,
|
|
495
|
+
error: externalError,
|
|
496
|
+
onErrorChange,
|
|
497
|
+
onFirstNameChange,
|
|
498
|
+
onLastNameChange,
|
|
499
|
+
showPayPal = true,
|
|
500
|
+
showApplePay = true,
|
|
501
|
+
showGooglePay = true,
|
|
502
|
+
totalAmount = 0,
|
|
503
|
+
currency = "usd",
|
|
504
|
+
innerRef
|
|
505
|
+
}) {
|
|
506
|
+
const flopay = useFloPay();
|
|
507
|
+
const elements = useElements();
|
|
508
|
+
const contextBillingUrl = useBillingApiUrl();
|
|
509
|
+
const [processing, setProcessing] = useState2(false);
|
|
510
|
+
const [error, setError] = useState2(null);
|
|
511
|
+
const [is3DSActive, setIs3DSActive] = useState2(false);
|
|
512
|
+
const [fullName, setFullName] = useState2("");
|
|
513
|
+
const [formReady, setFormReady] = useState2(false);
|
|
514
|
+
const [overlayStatus, setOverlayStatus] = useState2(null);
|
|
515
|
+
const processingRef = useRef2(false);
|
|
516
|
+
const resolvedBillingApiUrl = billingApiUrl || contextBillingUrl;
|
|
517
|
+
const displayError = externalError ?? error;
|
|
518
|
+
const isSubmitting = externalProcessing ?? processing;
|
|
519
|
+
const isSelfContained = !onTokenizedBody;
|
|
520
|
+
const baseUrl = resolvedBillingApiUrl.replace(/\/+$/, "");
|
|
521
|
+
const stripeInstance = useMemo2(() => {
|
|
522
|
+
if (!flopay) return null;
|
|
523
|
+
return flopay.getRawProvider();
|
|
524
|
+
}, [flopay]);
|
|
525
|
+
const amountInCents = totalAmount || 100;
|
|
526
|
+
const walletOptions = useMemo2(() => ({
|
|
527
|
+
mode: "payment",
|
|
528
|
+
amount: amountInCents,
|
|
529
|
+
currency: currency.toLowerCase(),
|
|
530
|
+
paymentMethodCreation: "manual",
|
|
531
|
+
captureMethod: "manual"
|
|
532
|
+
}), [amountInCents, currency]);
|
|
533
|
+
const paypalOptions = useMemo2(() => ({
|
|
534
|
+
mode: "payment",
|
|
535
|
+
amount: amountInCents,
|
|
536
|
+
currency: currency.toLowerCase(),
|
|
537
|
+
captureMethod: "manual"
|
|
538
|
+
}), [amountInCents, currency]);
|
|
539
|
+
const updateError = useCallback(
|
|
540
|
+
(err) => {
|
|
541
|
+
setError(err);
|
|
542
|
+
onErrorChange?.(err);
|
|
543
|
+
},
|
|
544
|
+
[onErrorChange]
|
|
545
|
+
);
|
|
546
|
+
const showWallets = showApplePay || showGooglePay;
|
|
547
|
+
const handleNameChange = useCallback((value) => {
|
|
548
|
+
setFullName(value);
|
|
549
|
+
const parts = value.trim().split(/\s+/);
|
|
550
|
+
onFirstNameChange?.(parts[0] ?? "");
|
|
551
|
+
onLastNameChange?.(parts.length > 1 ? parts.slice(1).join(" ") : "");
|
|
552
|
+
}, [onFirstNameChange, onLastNameChange]);
|
|
553
|
+
const processPaymentInternal = useCallback(
|
|
554
|
+
async (tokenizedBody) => {
|
|
555
|
+
if (processingRef.current) return;
|
|
556
|
+
processingRef.current = true;
|
|
557
|
+
setProcessing(true);
|
|
558
|
+
setOverlayStatus("processing");
|
|
559
|
+
updateError(null);
|
|
560
|
+
try {
|
|
561
|
+
const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
|
|
562
|
+
method: "POST",
|
|
563
|
+
headers: {
|
|
564
|
+
"Content-Type": "application/json",
|
|
565
|
+
"x-user-id": userId ?? ""
|
|
566
|
+
},
|
|
567
|
+
body: JSON.stringify({
|
|
568
|
+
sessionId,
|
|
569
|
+
tokenizedData: tokenizedBody,
|
|
570
|
+
accountData: {
|
|
571
|
+
userId: userId ?? "",
|
|
572
|
+
email: email ?? "",
|
|
573
|
+
firstName: firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
|
|
574
|
+
lastName: lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? ""
|
|
575
|
+
},
|
|
576
|
+
chv
|
|
577
|
+
})
|
|
578
|
+
});
|
|
579
|
+
if (response.ok) {
|
|
580
|
+
setOverlayStatus("success");
|
|
581
|
+
await new Promise((r) => setTimeout(r, 1200));
|
|
582
|
+
onComplete?.({
|
|
583
|
+
status: "succeeded",
|
|
584
|
+
paymentIntentId: tokenizedBody.threeDSecureActionResultTokenId
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const json = await response.json().catch(() => null);
|
|
589
|
+
if (json?.type === "3ds_required") {
|
|
590
|
+
const secret = json["threeDSecureToken"];
|
|
591
|
+
if (!flopay || !secret) {
|
|
592
|
+
setOverlayStatus("error");
|
|
593
|
+
updateError("3DS authentication required but no token provided.");
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
setIs3DSActive(true);
|
|
597
|
+
try {
|
|
598
|
+
const result = await flopay.confirmPayment({
|
|
599
|
+
clientSecret: secret,
|
|
600
|
+
returnUrl: window.location.href
|
|
601
|
+
});
|
|
602
|
+
if (result.error) {
|
|
603
|
+
setOverlayStatus("error");
|
|
604
|
+
updateError(result.error.message);
|
|
605
|
+
onError?.(result.error);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (result.status === "succeeded" || result.status === "processing") {
|
|
609
|
+
processingRef.current = false;
|
|
610
|
+
await processPaymentInternal({
|
|
611
|
+
id: result.paymentIntentId,
|
|
612
|
+
type: "card",
|
|
613
|
+
threeDSecureActionResultTokenId: result.paymentIntentId
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
} finally {
|
|
617
|
+
setIs3DSActive(false);
|
|
618
|
+
}
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
if (json?.type === "paypal_redirect_required") {
|
|
622
|
+
const secret = json["threeDSecureToken"];
|
|
623
|
+
const savedPmId = json["paymentMethodId"] || tokenizedBody.id || "";
|
|
624
|
+
if (!flopay || !secret) {
|
|
625
|
+
setOverlayStatus("error");
|
|
626
|
+
updateError("PayPal authorization required but no token provided.");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
const stripeInstance2 = flopay.getRawProvider();
|
|
631
|
+
if (!stripeInstance2) {
|
|
632
|
+
updateError("Payment provider not available.");
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
const confirmParams = {
|
|
636
|
+
return_url: window.location.href
|
|
637
|
+
};
|
|
638
|
+
if (savedPmId) {
|
|
639
|
+
confirmParams["payment_method"] = savedPmId;
|
|
640
|
+
}
|
|
641
|
+
const { error: confirmError } = await stripeInstance2.confirmPayment({
|
|
642
|
+
clientSecret: secret,
|
|
643
|
+
confirmParams,
|
|
644
|
+
redirect: "if_required"
|
|
645
|
+
});
|
|
646
|
+
if (confirmError) {
|
|
647
|
+
setOverlayStatus("error");
|
|
648
|
+
updateError(confirmError.message ?? "PayPal payment failed.");
|
|
649
|
+
}
|
|
650
|
+
} catch (err) {
|
|
651
|
+
setOverlayStatus("error");
|
|
652
|
+
updateError(err instanceof Error ? err.message : "PayPal authorization failed.");
|
|
653
|
+
}
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
setOverlayStatus("error");
|
|
657
|
+
updateError(json?.message ?? "Payment failed. Please try again.");
|
|
658
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
659
|
+
} catch (err) {
|
|
660
|
+
setOverlayStatus("error");
|
|
661
|
+
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
662
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
663
|
+
} finally {
|
|
664
|
+
setProcessing(false);
|
|
665
|
+
setOverlayStatus(null);
|
|
666
|
+
processingRef.current = false;
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
[baseUrl, sessionId, userId, email, firstName, lastName, fullName, chv, flopay, onComplete, onError, updateError]
|
|
670
|
+
);
|
|
671
|
+
const dispatchTokenizedBody = useCallback(
|
|
672
|
+
(tokenizedBody) => {
|
|
673
|
+
if (onTokenizedBody) {
|
|
674
|
+
onTokenizedBody(tokenizedBody);
|
|
675
|
+
} else {
|
|
676
|
+
processPaymentInternal(tokenizedBody);
|
|
677
|
+
}
|
|
678
|
+
},
|
|
679
|
+
[onTokenizedBody, processPaymentInternal]
|
|
680
|
+
);
|
|
681
|
+
useImperativeHandle(innerRef, () => ({
|
|
682
|
+
async handleNextAction(secret) {
|
|
683
|
+
if (!flopay) return;
|
|
684
|
+
setIs3DSActive(true);
|
|
685
|
+
try {
|
|
686
|
+
const result = await flopay.confirmPayment({
|
|
687
|
+
clientSecret: secret,
|
|
688
|
+
returnUrl: window.location.href
|
|
689
|
+
});
|
|
690
|
+
if (result.error) {
|
|
691
|
+
updateError(result.error.message);
|
|
692
|
+
onError?.(result.error);
|
|
693
|
+
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
694
|
+
dispatchTokenizedBody({
|
|
695
|
+
id: result.paymentIntentId,
|
|
696
|
+
type: "card",
|
|
697
|
+
threeDSecureActionResultTokenId: result.paymentIntentId
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
} catch (err) {
|
|
701
|
+
updateError(err instanceof Error ? err.message : "Payment authentication failed.");
|
|
702
|
+
} finally {
|
|
703
|
+
setIs3DSActive(false);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}), [flopay, dispatchTokenizedBody, onError, updateError]);
|
|
707
|
+
useEffect3(() => {
|
|
708
|
+
if (typeof window === "undefined") return;
|
|
709
|
+
const stored = localStorage.getItem(WALLET_RESUME_KEY);
|
|
710
|
+
if (!stored) return;
|
|
711
|
+
try {
|
|
712
|
+
const payload = JSON.parse(stored);
|
|
713
|
+
if (payload.sessionId === sessionId) {
|
|
714
|
+
localStorage.removeItem(WALLET_RESUME_KEY);
|
|
715
|
+
dispatchTokenizedBody({
|
|
716
|
+
id: payload.tokenId,
|
|
717
|
+
type: payload.tokenType,
|
|
718
|
+
threeDSecureActionResultTokenId: payload.paymentIntentId
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
} catch {
|
|
722
|
+
localStorage.removeItem(WALLET_RESUME_KEY);
|
|
723
|
+
}
|
|
724
|
+
}, [sessionId, dispatchTokenizedBody]);
|
|
725
|
+
const handleSubmit = useCallback(
|
|
726
|
+
async (e) => {
|
|
727
|
+
e.preventDefault();
|
|
728
|
+
if (!flopay || !elements || isSubmitting || processingRef.current) return;
|
|
729
|
+
setProcessing(true);
|
|
730
|
+
setOverlayStatus("processing");
|
|
731
|
+
updateError(null);
|
|
732
|
+
let handedOff = false;
|
|
733
|
+
try {
|
|
734
|
+
const submitResult = await flopay.submitElements();
|
|
735
|
+
if (submitResult.error) {
|
|
736
|
+
updateError(submitResult.error.message);
|
|
737
|
+
onError?.(submitResult.error);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
const pmResult = await flopay.createPaymentMethod();
|
|
741
|
+
if (pmResult.error || !pmResult.paymentMethodId) {
|
|
742
|
+
updateError(pmResult.error?.message ?? "Failed to create payment method.");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (!sessionId || !email) {
|
|
746
|
+
throw new FloPayError("Missing sessionId or email", "validation_error");
|
|
747
|
+
}
|
|
748
|
+
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
749
|
+
method: "POST",
|
|
750
|
+
headers: { "Content-Type": "application/json" },
|
|
751
|
+
body: JSON.stringify({
|
|
752
|
+
sessionId,
|
|
753
|
+
email,
|
|
754
|
+
paymentMethodType: pmResult.paymentMethodId,
|
|
755
|
+
isPaypal: false
|
|
756
|
+
})
|
|
757
|
+
});
|
|
758
|
+
if (!intentResponse.ok) throw new FloPayError("Failed to create payment intent", "api_error");
|
|
759
|
+
const intentJson = await intentResponse.json();
|
|
760
|
+
const intentClientSecret = intentJson.data?.id;
|
|
761
|
+
if (!intentClientSecret) throw new FloPayError("No client_secret in payment intent response", "api_error");
|
|
762
|
+
const confirmResult = await flopay.confirmCardPayment({
|
|
763
|
+
clientSecret: intentClientSecret,
|
|
764
|
+
paymentMethodId: pmResult.paymentMethodId
|
|
765
|
+
});
|
|
766
|
+
if (confirmResult.error) {
|
|
767
|
+
setOverlayStatus("error");
|
|
768
|
+
updateError(confirmResult.error.message);
|
|
769
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
handedOff = isSelfContained;
|
|
773
|
+
dispatchTokenizedBody({
|
|
774
|
+
id: pmResult.paymentMethodId,
|
|
775
|
+
type: "card",
|
|
776
|
+
threeDSecureActionResultTokenId: confirmResult.paymentIntentId
|
|
777
|
+
});
|
|
778
|
+
} catch (err) {
|
|
779
|
+
setOverlayStatus("error");
|
|
780
|
+
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
781
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
782
|
+
} finally {
|
|
783
|
+
if (!handedOff) {
|
|
784
|
+
setProcessing(false);
|
|
785
|
+
setOverlayStatus(null);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
},
|
|
789
|
+
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
|
|
790
|
+
);
|
|
791
|
+
const isReady = flopay !== null && elements !== null;
|
|
792
|
+
if (!isReady) {
|
|
793
|
+
return /* @__PURE__ */ jsx3("div", { "data-testid": "flopay-loading", "aria-busy": "true", children: "Loading payment form..." });
|
|
794
|
+
}
|
|
795
|
+
return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
796
|
+
overlayStatus && /* @__PURE__ */ jsx3(ProcessingOverlay, { status: overlayStatus }),
|
|
797
|
+
showWallets && stripeInstance && /* @__PURE__ */ jsx3(StripeElements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ jsx3(
|
|
798
|
+
WalletButtonInner,
|
|
799
|
+
{
|
|
800
|
+
sessionId,
|
|
801
|
+
email,
|
|
802
|
+
billingApiUrl: resolvedBillingApiUrl,
|
|
803
|
+
showApplePay,
|
|
804
|
+
showGooglePay,
|
|
805
|
+
onTokenizedBody: dispatchTokenizedBody,
|
|
806
|
+
onErrorChange: updateError
|
|
807
|
+
}
|
|
808
|
+
) }),
|
|
809
|
+
showPayPal && stripeInstance && /* @__PURE__ */ jsx3(StripeElements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ jsx3(
|
|
810
|
+
PayPalButtonInner,
|
|
811
|
+
{
|
|
812
|
+
sessionId,
|
|
813
|
+
email,
|
|
814
|
+
billingApiUrl: resolvedBillingApiUrl,
|
|
815
|
+
onTokenizedBody: dispatchTokenizedBody,
|
|
816
|
+
onErrorChange: updateError,
|
|
817
|
+
isProcessing: isSubmitting
|
|
818
|
+
}
|
|
819
|
+
) }),
|
|
820
|
+
(showWallets && stripeInstance || showPayPal && stripeInstance) && /* @__PURE__ */ jsxs("div", { style: {
|
|
821
|
+
display: "flex",
|
|
822
|
+
alignItems: "center",
|
|
823
|
+
gap: "0.75rem",
|
|
824
|
+
margin: "0.5rem 0 0.75rem",
|
|
825
|
+
color: "#999",
|
|
826
|
+
fontSize: "0.85rem"
|
|
827
|
+
}, children: [
|
|
828
|
+
/* @__PURE__ */ jsx3("div", { style: { flex: 1, height: 1, backgroundColor: "#ddd" } }),
|
|
829
|
+
/* @__PURE__ */ jsx3("span", { children: "or pay with card" }),
|
|
830
|
+
/* @__PURE__ */ jsx3("div", { style: { flex: 1, height: 1, backgroundColor: "#ddd" } })
|
|
831
|
+
] }),
|
|
832
|
+
/* @__PURE__ */ jsxs("div", { style: { backgroundColor: "#EDEDFF", borderRadius: "8px", padding: "1rem" }, children: [
|
|
833
|
+
/* @__PURE__ */ jsx3("div", { style: { textAlign: "center", fontWeight: 600, fontSize: "1.1rem", padding: "0.5rem 0", color: "#262833" }, children: "Secure card checkout" }),
|
|
834
|
+
/* @__PURE__ */ jsx3("div", { style: {
|
|
835
|
+
backgroundColor: "white",
|
|
836
|
+
border: "1px solid #A4A4FF",
|
|
837
|
+
borderTopLeftRadius: "8px",
|
|
838
|
+
borderTopRightRadius: "8px",
|
|
839
|
+
padding: "10px"
|
|
840
|
+
}, children: /* @__PURE__ */ jsx3(CardNumberElement, { onReady: () => setFormReady(true) }) }),
|
|
841
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex" }, children: [
|
|
842
|
+
/* @__PURE__ */ jsx3("div", { style: {
|
|
843
|
+
flex: 1,
|
|
844
|
+
backgroundColor: "white",
|
|
845
|
+
border: "1px solid #A4A4FF",
|
|
846
|
+
borderTop: "none",
|
|
847
|
+
borderRight: "none",
|
|
848
|
+
borderBottomLeftRadius: "8px",
|
|
849
|
+
padding: "10px"
|
|
850
|
+
}, children: /* @__PURE__ */ jsx3(CardExpiryElement, {}) }),
|
|
851
|
+
/* @__PURE__ */ jsx3("div", { style: {
|
|
852
|
+
flex: 1,
|
|
853
|
+
backgroundColor: "white",
|
|
854
|
+
border: "1px solid #A4A4FF",
|
|
855
|
+
borderTop: "none",
|
|
856
|
+
borderBottomRightRadius: "8px",
|
|
857
|
+
padding: "10px"
|
|
858
|
+
}, children: /* @__PURE__ */ jsx3(CardCvcElement, {}) })
|
|
859
|
+
] }),
|
|
860
|
+
/* @__PURE__ */ jsx3("div", { style: {
|
|
861
|
+
backgroundColor: "white",
|
|
862
|
+
border: "1px solid #A4A4FF",
|
|
863
|
+
borderRadius: "8px",
|
|
864
|
+
marginTop: "0.5rem",
|
|
865
|
+
padding: "10px"
|
|
866
|
+
}, children: /* @__PURE__ */ jsx3(
|
|
867
|
+
"input",
|
|
868
|
+
{
|
|
869
|
+
placeholder: "Full Name on Card",
|
|
870
|
+
autoComplete: "cc-name",
|
|
871
|
+
value: fullName,
|
|
872
|
+
onChange: (e) => handleNameChange(e.target.value),
|
|
873
|
+
disabled: isSubmitting,
|
|
874
|
+
required: true,
|
|
875
|
+
style: {
|
|
876
|
+
width: "100%",
|
|
877
|
+
border: "none",
|
|
878
|
+
outline: "none",
|
|
879
|
+
fontSize: "16px",
|
|
880
|
+
fontFamily: "Poppins, sans-serif",
|
|
881
|
+
color: "#262833"
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
) }),
|
|
885
|
+
displayError && /* @__PURE__ */ jsx3("div", { role: "alert", "data-testid": "flopay-error", style: { color: "red", margin: "0.75rem 0", fontSize: "0.9rem" }, children: displayError }),
|
|
886
|
+
children ?? /* @__PURE__ */ jsx3(
|
|
887
|
+
"button",
|
|
888
|
+
{
|
|
889
|
+
type: "submit",
|
|
890
|
+
disabled: !formReady || isSubmitting,
|
|
891
|
+
"data-testid": "flopay-submit",
|
|
892
|
+
style: {
|
|
893
|
+
width: "100%",
|
|
894
|
+
padding: "0.875rem",
|
|
895
|
+
marginTop: "1rem",
|
|
896
|
+
backgroundColor: "#4A49FF",
|
|
897
|
+
color: "white",
|
|
898
|
+
border: "none",
|
|
899
|
+
borderRadius: "8px",
|
|
900
|
+
fontSize: "1rem",
|
|
901
|
+
fontWeight: 600,
|
|
902
|
+
cursor: !formReady || isSubmitting ? "not-allowed" : "pointer",
|
|
903
|
+
opacity: !formReady || isSubmitting ? 0.5 : 1
|
|
904
|
+
},
|
|
905
|
+
children: isSubmitting ? "PROCESSING..." : submitLabel
|
|
906
|
+
}
|
|
907
|
+
),
|
|
908
|
+
/* @__PURE__ */ jsx3("div", { style: {
|
|
909
|
+
backgroundColor: "#EFF9F0",
|
|
910
|
+
borderRadius: "8px",
|
|
911
|
+
padding: "0.75rem",
|
|
912
|
+
marginTop: "0.75rem",
|
|
913
|
+
textAlign: "center",
|
|
914
|
+
fontSize: "0.85rem",
|
|
915
|
+
fontWeight: 600,
|
|
916
|
+
color: "#7DAD3A"
|
|
917
|
+
}, children: "Secure Card Checkout" })
|
|
918
|
+
] })
|
|
919
|
+
] });
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// src/flopay-checkout.tsx
|
|
923
|
+
import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
924
|
+
function FloPayCheckout({
|
|
925
|
+
sessionId,
|
|
926
|
+
billingApiUrl,
|
|
927
|
+
appearance,
|
|
928
|
+
locale,
|
|
929
|
+
fallbackPublishableKey,
|
|
930
|
+
loading: loadingNode,
|
|
931
|
+
error: errorNode,
|
|
932
|
+
onComplete,
|
|
933
|
+
onError,
|
|
934
|
+
showPayPal = true,
|
|
935
|
+
showApplePay = true,
|
|
936
|
+
showGooglePay = true,
|
|
937
|
+
submitLabel,
|
|
938
|
+
className,
|
|
939
|
+
children,
|
|
940
|
+
checkoutMode: checkoutModeProp,
|
|
941
|
+
confirmLabel,
|
|
942
|
+
renderConfirmButton,
|
|
943
|
+
onSessionCompleted
|
|
944
|
+
}) {
|
|
945
|
+
const resolvedBillingUrl = resolveBillingApiUrl3(billingApiUrl);
|
|
946
|
+
const [unified, setUnified] = useState3(null);
|
|
947
|
+
const [flopay, setFloPay] = useState3(null);
|
|
948
|
+
const flopayRef = useRef3(null);
|
|
949
|
+
const [session, setSession] = useState3(null);
|
|
950
|
+
const [isLoading, setIsLoading] = useState3(true);
|
|
951
|
+
const [loadError, setLoadError] = useState3(null);
|
|
952
|
+
const [currentMode, setCurrentMode] = useState3("full");
|
|
953
|
+
const [confirmProcessing, setConfirmProcessing] = useState3(false);
|
|
954
|
+
const [modeError, setModeError] = useState3(null);
|
|
955
|
+
const autoCheckoutAttempted = useRef3(false);
|
|
956
|
+
const processPaymentForMode = useCallback2(
|
|
957
|
+
async (sess) => {
|
|
958
|
+
const baseUrl = resolvedBillingUrl.replace(/\/+$/, "");
|
|
959
|
+
const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
|
|
960
|
+
method: "POST",
|
|
961
|
+
headers: {
|
|
962
|
+
"Content-Type": "application/json",
|
|
963
|
+
"x-user-id": sess.customer?.id ?? ""
|
|
964
|
+
},
|
|
965
|
+
body: JSON.stringify({
|
|
966
|
+
sessionId,
|
|
967
|
+
tokenizedData: { id: void 0 },
|
|
968
|
+
accountData: {
|
|
969
|
+
userId: sess.customer?.id ?? "",
|
|
970
|
+
email: sess.customer?.email ?? "",
|
|
971
|
+
firstName: sess.customer?.firstName ?? "",
|
|
972
|
+
lastName: sess.customer?.lastName ?? ""
|
|
973
|
+
}
|
|
974
|
+
})
|
|
975
|
+
});
|
|
976
|
+
if (response.ok) {
|
|
977
|
+
onComplete?.({ status: "succeeded" });
|
|
978
|
+
return null;
|
|
979
|
+
}
|
|
980
|
+
const json = await response.json().catch(() => null);
|
|
981
|
+
if ((json?.type === "paypal_redirect_required" || json?.type === "3ds_required") && json?.threeDSecureToken) {
|
|
982
|
+
return {
|
|
983
|
+
type: json.type,
|
|
984
|
+
threeDSecureToken: json.threeDSecureToken,
|
|
985
|
+
paymentMethodId: json.paymentMethodId
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
throw new FloPayError2(
|
|
989
|
+
json?.message ?? "Payment failed. Please try again.",
|
|
990
|
+
"api_error"
|
|
991
|
+
);
|
|
992
|
+
},
|
|
993
|
+
[resolvedBillingUrl, sessionId, onComplete]
|
|
994
|
+
);
|
|
995
|
+
useEffect4(() => {
|
|
996
|
+
let cancelled = false;
|
|
997
|
+
setIsLoading(true);
|
|
998
|
+
setLoadError(null);
|
|
999
|
+
async function init() {
|
|
1000
|
+
try {
|
|
1001
|
+
const api = new PaymentAPI(resolvedBillingUrl);
|
|
1002
|
+
const result = await api.getUnifiedCheckoutSession(sessionId);
|
|
1003
|
+
if (cancelled) return;
|
|
1004
|
+
setUnified(result);
|
|
1005
|
+
const sess = result.data.session ?? null;
|
|
1006
|
+
setSession(sess);
|
|
1007
|
+
if (!sess) {
|
|
1008
|
+
throw new FloPayError2("No session data returned", "api_error");
|
|
1009
|
+
}
|
|
1010
|
+
if (sess.status === "complete") {
|
|
1011
|
+
setIsLoading(false);
|
|
1012
|
+
onSessionCompleted?.(sess.successUrl ?? "");
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
const effectiveMode = checkoutModeProp ?? sess.checkoutMode ?? "full";
|
|
1016
|
+
setCurrentMode(effectiveMode);
|
|
1017
|
+
const hasPayPalRedirectParams = typeof window !== "undefined" && new URLSearchParams(window.location.search).has("payment_intent");
|
|
1018
|
+
if (effectiveMode === "auto" && !autoCheckoutAttempted.current && !hasPayPalRedirectParams) {
|
|
1019
|
+
autoCheckoutAttempted.current = true;
|
|
1020
|
+
const stripeInitPromise = initStripe(result, sess);
|
|
1021
|
+
try {
|
|
1022
|
+
const redirectResult = await processPaymentForMode(sess);
|
|
1023
|
+
if (!redirectResult) {
|
|
1024
|
+
if (!cancelled) setIsLoading(false);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (cancelled) return;
|
|
1028
|
+
await stripeInitPromise;
|
|
1029
|
+
if (redirectResult.type === "paypal_redirect_required") {
|
|
1030
|
+
const stripeRef = flopayRef.current;
|
|
1031
|
+
if (stripeRef) {
|
|
1032
|
+
const rawStripe = stripeRef.getRawProvider();
|
|
1033
|
+
if (rawStripe) {
|
|
1034
|
+
const confirmParams = {
|
|
1035
|
+
return_url: window.location.href
|
|
1036
|
+
};
|
|
1037
|
+
if (redirectResult.paymentMethodId) {
|
|
1038
|
+
confirmParams["payment_method"] = redirectResult.paymentMethodId;
|
|
1039
|
+
}
|
|
1040
|
+
await rawStripe.confirmPayment({
|
|
1041
|
+
clientSecret: redirectResult.threeDSecureToken,
|
|
1042
|
+
confirmParams,
|
|
1043
|
+
redirect: "if_required"
|
|
1044
|
+
});
|
|
1045
|
+
if (!cancelled) setIsLoading(false);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (!cancelled) {
|
|
1051
|
+
setCurrentMode("full");
|
|
1052
|
+
setIsLoading(false);
|
|
1053
|
+
}
|
|
1054
|
+
return;
|
|
1055
|
+
} catch {
|
|
1056
|
+
if (cancelled) return;
|
|
1057
|
+
setCurrentMode("full");
|
|
1058
|
+
await stripeInitPromise;
|
|
1059
|
+
if (!cancelled) setIsLoading(false);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
await initStripe(result, sess);
|
|
1064
|
+
if (!cancelled) setIsLoading(false);
|
|
1065
|
+
} catch (err) {
|
|
1066
|
+
if (cancelled) return;
|
|
1067
|
+
const floPayErr = err instanceof FloPayError2 ? err : new FloPayError2(
|
|
1068
|
+
err instanceof Error ? err.message : "Failed to initialize checkout",
|
|
1069
|
+
"api_error"
|
|
1070
|
+
);
|
|
1071
|
+
setLoadError(floPayErr);
|
|
1072
|
+
setIsLoading(false);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
async function initStripe(result, _sess) {
|
|
1076
|
+
let publishableKey;
|
|
1077
|
+
if (result.provider === "stripe") {
|
|
1078
|
+
publishableKey = result.data.stripe?.publishableKey;
|
|
1079
|
+
}
|
|
1080
|
+
if (!publishableKey) publishableKey = fallbackPublishableKey;
|
|
1081
|
+
if (!publishableKey) {
|
|
1082
|
+
throw new FloPayError2(
|
|
1083
|
+
"No publishable key found in session response. Provide a fallbackPublishableKey prop or ensure the session includes gatewayData.publishableKey.",
|
|
1084
|
+
"validation_error"
|
|
1085
|
+
);
|
|
1086
|
+
}
|
|
1087
|
+
const instance = await loadFloPay(publishableKey, {
|
|
1088
|
+
billingApiUrl: resolvedBillingUrl,
|
|
1089
|
+
locale
|
|
1090
|
+
});
|
|
1091
|
+
flopayRef.current = instance;
|
|
1092
|
+
setFloPay(instance);
|
|
1093
|
+
}
|
|
1094
|
+
init();
|
|
1095
|
+
return () => {
|
|
1096
|
+
cancelled = true;
|
|
1097
|
+
};
|
|
1098
|
+
}, [
|
|
1099
|
+
sessionId,
|
|
1100
|
+
resolvedBillingUrl,
|
|
1101
|
+
fallbackPublishableKey,
|
|
1102
|
+
locale,
|
|
1103
|
+
checkoutModeProp,
|
|
1104
|
+
onSessionCompleted,
|
|
1105
|
+
processPaymentForMode
|
|
1106
|
+
]);
|
|
1107
|
+
const handleConfirmCheckout = useCallback2(async () => {
|
|
1108
|
+
if (confirmProcessing || !session) return;
|
|
1109
|
+
setConfirmProcessing(true);
|
|
1110
|
+
setModeError(null);
|
|
1111
|
+
try {
|
|
1112
|
+
await processPaymentForMode(session);
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
const floPayErr = err instanceof FloPayError2 ? err : new FloPayError2(
|
|
1115
|
+
err instanceof Error ? err.message : "Payment failed",
|
|
1116
|
+
"api_error"
|
|
1117
|
+
);
|
|
1118
|
+
setModeError(floPayErr.message);
|
|
1119
|
+
onError?.(floPayErr);
|
|
1120
|
+
setCurrentMode("full");
|
|
1121
|
+
} finally {
|
|
1122
|
+
setConfirmProcessing(false);
|
|
1123
|
+
}
|
|
1124
|
+
}, [confirmProcessing, session, processPaymentForMode, onError]);
|
|
1125
|
+
const providerOptions = useMemo3(() => {
|
|
1126
|
+
if (!unified || !session) return void 0;
|
|
1127
|
+
const opts = {
|
|
1128
|
+
appearance,
|
|
1129
|
+
paymentMethodCreation: "manual",
|
|
1130
|
+
billingApiUrl: resolvedBillingUrl
|
|
1131
|
+
};
|
|
1132
|
+
if (unified.provider === "stripe" && unified.data.stripe?.clientSecret) {
|
|
1133
|
+
opts.clientSecret = unified.data.stripe.clientSecret;
|
|
1134
|
+
} else {
|
|
1135
|
+
const displayTotal = buildCheckoutDisplayData(session).total;
|
|
1136
|
+
opts.amount = Math.round(displayTotal * 100) || session.amount;
|
|
1137
|
+
opts.currency = session.currency?.toLowerCase();
|
|
1138
|
+
}
|
|
1139
|
+
return opts;
|
|
1140
|
+
}, [unified, session, appearance, resolvedBillingUrl]);
|
|
1141
|
+
const checkoutValue = useMemo3(
|
|
1142
|
+
() => ({
|
|
1143
|
+
session,
|
|
1144
|
+
loading: isLoading,
|
|
1145
|
+
error: loadError,
|
|
1146
|
+
checkoutMode: currentMode
|
|
1147
|
+
}),
|
|
1148
|
+
[session, isLoading, loadError, currentMode]
|
|
1149
|
+
);
|
|
1150
|
+
if (isLoading) {
|
|
1151
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: loadingNode ?? /* @__PURE__ */ jsxs2(
|
|
1152
|
+
"div",
|
|
1153
|
+
{
|
|
1154
|
+
style: {
|
|
1155
|
+
display: "flex",
|
|
1156
|
+
justifyContent: "center",
|
|
1157
|
+
padding: 32
|
|
1158
|
+
},
|
|
1159
|
+
children: [
|
|
1160
|
+
/* @__PURE__ */ jsx4(
|
|
1161
|
+
"div",
|
|
1162
|
+
{
|
|
1163
|
+
style: {
|
|
1164
|
+
width: 24,
|
|
1165
|
+
height: 24,
|
|
1166
|
+
border: "2px solid #e5e7eb",
|
|
1167
|
+
borderTopColor: "#6b7280",
|
|
1168
|
+
borderRadius: "50%",
|
|
1169
|
+
animation: "spin 0.6s linear infinite"
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
),
|
|
1173
|
+
/* @__PURE__ */ jsx4("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
|
|
1174
|
+
]
|
|
1175
|
+
}
|
|
1176
|
+
) });
|
|
1177
|
+
}
|
|
1178
|
+
if (loadError) {
|
|
1179
|
+
if (errorNode) return /* @__PURE__ */ jsx4(Fragment2, { children: errorNode(loadError) });
|
|
1180
|
+
return /* @__PURE__ */ jsx4(
|
|
1181
|
+
"div",
|
|
1182
|
+
{
|
|
1183
|
+
style: {
|
|
1184
|
+
padding: 24,
|
|
1185
|
+
textAlign: "center",
|
|
1186
|
+
color: "#dc2626",
|
|
1187
|
+
fontSize: 14
|
|
1188
|
+
},
|
|
1189
|
+
children: loadError.message
|
|
1190
|
+
}
|
|
1191
|
+
);
|
|
1192
|
+
}
|
|
1193
|
+
if (!flopay || !providerOptions) return /* @__PURE__ */ jsx4(Fragment2, {});
|
|
1194
|
+
if (currentMode === "confirm") {
|
|
1195
|
+
return /* @__PURE__ */ jsx4(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx4(FloPayProvider, { flopay, options: providerOptions, children: /* @__PURE__ */ jsxs2("div", { className, children: [
|
|
1196
|
+
modeError && /* @__PURE__ */ jsx4(
|
|
1197
|
+
"div",
|
|
1198
|
+
{
|
|
1199
|
+
style: {
|
|
1200
|
+
color: "#dc2626",
|
|
1201
|
+
fontSize: "0.875rem",
|
|
1202
|
+
marginBottom: "0.75rem",
|
|
1203
|
+
textAlign: "center"
|
|
1204
|
+
},
|
|
1205
|
+
children: modeError
|
|
1206
|
+
}
|
|
1207
|
+
),
|
|
1208
|
+
renderConfirmButton ? renderConfirmButton({
|
|
1209
|
+
onConfirm: handleConfirmCheckout,
|
|
1210
|
+
isProcessing: confirmProcessing
|
|
1211
|
+
}) : /* @__PURE__ */ jsx4(
|
|
1212
|
+
"button",
|
|
1213
|
+
{
|
|
1214
|
+
type: "button",
|
|
1215
|
+
onClick: handleConfirmCheckout,
|
|
1216
|
+
disabled: confirmProcessing,
|
|
1217
|
+
style: {
|
|
1218
|
+
width: "100%",
|
|
1219
|
+
padding: "0.875rem",
|
|
1220
|
+
backgroundColor: "#4A49FF",
|
|
1221
|
+
color: "white",
|
|
1222
|
+
border: "none",
|
|
1223
|
+
borderRadius: "8px",
|
|
1224
|
+
fontSize: "1rem",
|
|
1225
|
+
fontWeight: 600,
|
|
1226
|
+
cursor: confirmProcessing ? "not-allowed" : "pointer",
|
|
1227
|
+
opacity: confirmProcessing ? 0.6 : 1
|
|
1228
|
+
},
|
|
1229
|
+
children: confirmProcessing ? "Processing..." : confirmLabel ?? "Confirm Purchase"
|
|
1230
|
+
}
|
|
1231
|
+
)
|
|
1232
|
+
] }) }) });
|
|
1233
|
+
}
|
|
1234
|
+
return /* @__PURE__ */ jsx4(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx4(FloPayProvider, { flopay, options: providerOptions, children: children ? /* @__PURE__ */ jsx4(
|
|
1235
|
+
SessionInjector,
|
|
1236
|
+
{
|
|
1237
|
+
sessionId,
|
|
1238
|
+
billingApiUrl: resolvedBillingUrl,
|
|
1239
|
+
session,
|
|
1240
|
+
children
|
|
1241
|
+
}
|
|
1242
|
+
) : /* @__PURE__ */ jsx4(
|
|
1243
|
+
SplitCardForm,
|
|
1244
|
+
{
|
|
1245
|
+
sessionId,
|
|
1246
|
+
email: session?.customer?.email,
|
|
1247
|
+
userId: session?.customer?.id,
|
|
1248
|
+
firstName: session?.customer?.firstName,
|
|
1249
|
+
lastName: session?.customer?.lastName,
|
|
1250
|
+
totalAmount: session ? Math.round(buildCheckoutDisplayData(session).total * 100) : 0,
|
|
1251
|
+
currency: session?.currency?.toLowerCase() ?? "usd",
|
|
1252
|
+
onComplete,
|
|
1253
|
+
onError,
|
|
1254
|
+
showPayPal,
|
|
1255
|
+
showApplePay,
|
|
1256
|
+
showGooglePay,
|
|
1257
|
+
submitLabel,
|
|
1258
|
+
className
|
|
1259
|
+
}
|
|
1260
|
+
) }) });
|
|
1261
|
+
}
|
|
1262
|
+
function SessionInjector({
|
|
1263
|
+
sessionId,
|
|
1264
|
+
billingApiUrl,
|
|
1265
|
+
session,
|
|
1266
|
+
children
|
|
1267
|
+
}) {
|
|
1268
|
+
return /* @__PURE__ */ jsx4(Fragment2, { children: React4.Children.map(children, (child) => {
|
|
1269
|
+
if (!React4.isValidElement(child)) return child;
|
|
1270
|
+
const existing = child.props;
|
|
1271
|
+
const injected = {};
|
|
1272
|
+
if (!existing.sessionId) injected.sessionId = sessionId;
|
|
1273
|
+
if (!existing.billingApiUrl) injected.billingApiUrl = billingApiUrl;
|
|
1274
|
+
if (session?.customer) {
|
|
1275
|
+
if (!existing.email) injected.email = session.customer.email;
|
|
1276
|
+
if (!existing.userId) injected.userId = session.customer.id;
|
|
1277
|
+
if (!existing.firstName)
|
|
1278
|
+
injected.firstName = session.customer.firstName;
|
|
1279
|
+
if (!existing.lastName)
|
|
1280
|
+
injected.lastName = session.customer.lastName;
|
|
1281
|
+
}
|
|
1282
|
+
if (Object.keys(injected).length === 0) return child;
|
|
1283
|
+
return React4.cloneElement(child, injected);
|
|
1284
|
+
}) });
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/checkout-form.tsx
|
|
1288
|
+
import { FloPayError as FloPayError3 } from "@flopay/shared";
|
|
1289
|
+
import { forwardRef as forwardRef2, useCallback as useCallback3, useEffect as useEffect5, useImperativeHandle as useImperativeHandle2, useState as useState4 } from "react";
|
|
1290
|
+
import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1291
|
+
var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
|
|
1292
|
+
var CheckoutForm = forwardRef2(
|
|
1293
|
+
function CheckoutForm2(props, ref) {
|
|
1294
|
+
return /* @__PURE__ */ jsx5(CheckoutFormInner, { ...props, innerRef: ref });
|
|
1295
|
+
}
|
|
1296
|
+
);
|
|
1297
|
+
function CheckoutFormInner({
|
|
1298
|
+
sessionId,
|
|
1299
|
+
billingApiUrl,
|
|
1300
|
+
email,
|
|
1301
|
+
userId,
|
|
1302
|
+
onComplete,
|
|
1303
|
+
onError,
|
|
1304
|
+
onTokenizedBody,
|
|
1305
|
+
layout = "auto",
|
|
1306
|
+
submitLabel = "Pay",
|
|
1307
|
+
showAddress = false,
|
|
1308
|
+
className,
|
|
1309
|
+
children,
|
|
1310
|
+
firstName,
|
|
1311
|
+
lastName,
|
|
1312
|
+
chv,
|
|
1313
|
+
isProcessing: externalProcessing,
|
|
1314
|
+
error: externalError,
|
|
1315
|
+
onErrorChange,
|
|
1316
|
+
innerRef
|
|
1317
|
+
}) {
|
|
1318
|
+
const flopay = useFloPay();
|
|
1319
|
+
const elements = useElements();
|
|
1320
|
+
const contextBillingUrl = useBillingApiUrl();
|
|
1321
|
+
const [processing, setProcessing] = useState4(false);
|
|
1322
|
+
const [error, setError] = useState4(null);
|
|
1323
|
+
const [is3DSActive, setIs3DSActive] = useState4(false);
|
|
1324
|
+
const displayError = externalError ?? error;
|
|
1325
|
+
const isSubmitting = externalProcessing ?? processing;
|
|
1326
|
+
const isSelfContained = !onTokenizedBody;
|
|
1327
|
+
const baseUrl = (billingApiUrl || contextBillingUrl).replace(/\/+$/, "");
|
|
1328
|
+
const updateError = useCallback3(
|
|
1329
|
+
(err) => {
|
|
1330
|
+
setError(err);
|
|
1331
|
+
onErrorChange?.(err);
|
|
1332
|
+
},
|
|
1333
|
+
[onErrorChange]
|
|
1334
|
+
);
|
|
1335
|
+
const processPaymentInternal = useCallback3(
|
|
1336
|
+
async (tokenizedBody) => {
|
|
1337
|
+
setProcessing(true);
|
|
1338
|
+
updateError(null);
|
|
1339
|
+
try {
|
|
1340
|
+
const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
|
|
1341
|
+
method: "POST",
|
|
1342
|
+
headers: {
|
|
1343
|
+
"Content-Type": "application/json",
|
|
1344
|
+
"x-user-id": userId ?? ""
|
|
1345
|
+
},
|
|
1346
|
+
body: JSON.stringify({
|
|
1347
|
+
sessionId,
|
|
1348
|
+
tokenizedData: tokenizedBody,
|
|
1349
|
+
accountData: {
|
|
1350
|
+
userId: userId ?? "",
|
|
1351
|
+
email: email ?? "",
|
|
1352
|
+
firstName: firstName ?? "",
|
|
1353
|
+
lastName: lastName ?? ""
|
|
1354
|
+
},
|
|
1355
|
+
chv
|
|
1356
|
+
})
|
|
1357
|
+
});
|
|
1358
|
+
if (response.ok) {
|
|
1359
|
+
onComplete?.({
|
|
1360
|
+
status: "succeeded",
|
|
1361
|
+
paymentIntentId: tokenizedBody.threeDSecureActionResultTokenId
|
|
1362
|
+
});
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
const json = await response.json().catch(() => null);
|
|
1366
|
+
if (json?.type === "3ds_required") {
|
|
1367
|
+
const secret = json["threeDSecureToken"];
|
|
1368
|
+
if (!flopay || !secret) {
|
|
1369
|
+
updateError("3DS authentication required but no token provided.");
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
setIs3DSActive(true);
|
|
1373
|
+
try {
|
|
1374
|
+
const result = await flopay.confirmPayment({
|
|
1375
|
+
clientSecret: secret,
|
|
1376
|
+
returnUrl: window.location.href
|
|
1377
|
+
});
|
|
1378
|
+
if (result.error) {
|
|
1379
|
+
updateError(result.error.message);
|
|
1380
|
+
onError?.(result.error);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
if (result.status === "succeeded" || result.status === "processing") {
|
|
1384
|
+
await processPaymentInternal({
|
|
1385
|
+
id: result.paymentIntentId,
|
|
1386
|
+
type: "card",
|
|
1387
|
+
threeDSecureActionResultTokenId: result.paymentIntentId
|
|
1388
|
+
});
|
|
1389
|
+
}
|
|
1390
|
+
} finally {
|
|
1391
|
+
setIs3DSActive(false);
|
|
1392
|
+
}
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
if (json?.type === "paypal_redirect_required") {
|
|
1396
|
+
const secret = json["clientSecret"];
|
|
1397
|
+
const pmId = json["paymentMethodId"];
|
|
1398
|
+
if (flopay && secret) {
|
|
1399
|
+
localStorage.setItem(WALLET_RESUME_KEY2, JSON.stringify({
|
|
1400
|
+
sessionId,
|
|
1401
|
+
paymentIntentId: "",
|
|
1402
|
+
tokenType: "card",
|
|
1403
|
+
tokenId: pmId,
|
|
1404
|
+
status: "pending_redirect"
|
|
1405
|
+
}));
|
|
1406
|
+
await flopay.confirmPayment({
|
|
1407
|
+
clientSecret: secret,
|
|
1408
|
+
returnUrl: window.location.href
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
const errorMessage = json?.message ?? "Payment failed. Please try again.";
|
|
1414
|
+
updateError(errorMessage);
|
|
1415
|
+
} catch (err) {
|
|
1416
|
+
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
1417
|
+
} finally {
|
|
1418
|
+
setProcessing(false);
|
|
1419
|
+
}
|
|
1420
|
+
},
|
|
1421
|
+
[baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError]
|
|
1422
|
+
);
|
|
1423
|
+
const dispatchTokenizedBody = useCallback3(
|
|
1424
|
+
(tokenizedBody) => {
|
|
1425
|
+
if (onTokenizedBody) {
|
|
1426
|
+
onTokenizedBody(tokenizedBody);
|
|
1427
|
+
} else {
|
|
1428
|
+
processPaymentInternal(tokenizedBody);
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
[onTokenizedBody, processPaymentInternal]
|
|
1432
|
+
);
|
|
1433
|
+
useImperativeHandle2(innerRef, () => ({
|
|
1434
|
+
async handleNextAction(secret) {
|
|
1435
|
+
if (!flopay) return;
|
|
1436
|
+
setIs3DSActive(true);
|
|
1437
|
+
try {
|
|
1438
|
+
const result = await flopay.confirmPayment({
|
|
1439
|
+
clientSecret: secret,
|
|
1440
|
+
returnUrl: window.location.href
|
|
1441
|
+
});
|
|
1442
|
+
if (result.error) {
|
|
1443
|
+
updateError(result.error.message);
|
|
1444
|
+
onError?.(result.error);
|
|
1445
|
+
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
1446
|
+
dispatchTokenizedBody({
|
|
1447
|
+
id: result.paymentIntentId,
|
|
1448
|
+
type: "card",
|
|
1449
|
+
threeDSecureActionResultTokenId: result.paymentIntentId
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
updateError(err instanceof Error ? err.message : "Payment authentication failed.");
|
|
1454
|
+
} finally {
|
|
1455
|
+
setIs3DSActive(false);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
}), [flopay, dispatchTokenizedBody, onError, updateError]);
|
|
1459
|
+
useEffect5(() => {
|
|
1460
|
+
if (typeof window === "undefined") return;
|
|
1461
|
+
const stored = localStorage.getItem(WALLET_RESUME_KEY2);
|
|
1462
|
+
if (!stored) return;
|
|
1463
|
+
try {
|
|
1464
|
+
const payload = JSON.parse(stored);
|
|
1465
|
+
if (payload.sessionId === sessionId) {
|
|
1466
|
+
localStorage.removeItem(WALLET_RESUME_KEY2);
|
|
1467
|
+
dispatchTokenizedBody({
|
|
1468
|
+
id: payload.tokenId,
|
|
1469
|
+
type: payload.tokenType,
|
|
1470
|
+
threeDSecureActionResultTokenId: payload.paymentIntentId
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
} catch {
|
|
1474
|
+
localStorage.removeItem(WALLET_RESUME_KEY2);
|
|
1475
|
+
}
|
|
1476
|
+
}, [sessionId, dispatchTokenizedBody]);
|
|
1477
|
+
const handleSubmit = useCallback3(
|
|
1478
|
+
async (e) => {
|
|
1479
|
+
e.preventDefault();
|
|
1480
|
+
if (!flopay || !elements || isSubmitting) return;
|
|
1481
|
+
setProcessing(true);
|
|
1482
|
+
updateError(null);
|
|
1483
|
+
try {
|
|
1484
|
+
const submitResult = await flopay.submitElements();
|
|
1485
|
+
if (submitResult.error) {
|
|
1486
|
+
updateError(submitResult.error.message);
|
|
1487
|
+
onError?.(submitResult.error);
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
const pmResult = await flopay.createPaymentMethod();
|
|
1491
|
+
if (pmResult.error || !pmResult.paymentMethodId) {
|
|
1492
|
+
updateError(pmResult.error?.message ?? "Failed to create payment method.");
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
if (!sessionId || !email) {
|
|
1496
|
+
throw new FloPayError3("Missing sessionId or email", "validation_error");
|
|
1497
|
+
}
|
|
1498
|
+
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
1499
|
+
method: "POST",
|
|
1500
|
+
headers: { "Content-Type": "application/json" },
|
|
1501
|
+
body: JSON.stringify({
|
|
1502
|
+
sessionId,
|
|
1503
|
+
email,
|
|
1504
|
+
paymentMethodType: pmResult.paymentMethodId,
|
|
1505
|
+
isPaypal: false
|
|
1506
|
+
})
|
|
1507
|
+
});
|
|
1508
|
+
if (!intentResponse.ok) throw new FloPayError3("Failed to create payment intent", "api_error");
|
|
1509
|
+
const intentJson = await intentResponse.json();
|
|
1510
|
+
const intentClientSecret = intentJson.data?.id;
|
|
1511
|
+
if (!intentClientSecret) throw new FloPayError3("No client_secret in payment intent response", "api_error");
|
|
1512
|
+
const confirmResult = await flopay.confirmCardPayment({
|
|
1513
|
+
clientSecret: intentClientSecret,
|
|
1514
|
+
paymentMethodId: pmResult.paymentMethodId
|
|
1515
|
+
});
|
|
1516
|
+
if (confirmResult.error) {
|
|
1517
|
+
updateError(confirmResult.error.message);
|
|
1518
|
+
return;
|
|
1519
|
+
}
|
|
1520
|
+
dispatchTokenizedBody({
|
|
1521
|
+
id: pmResult.paymentMethodId,
|
|
1522
|
+
type: "card",
|
|
1523
|
+
threeDSecureActionResultTokenId: confirmResult.paymentIntentId
|
|
1524
|
+
});
|
|
1525
|
+
} catch (err) {
|
|
1526
|
+
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
1527
|
+
} finally {
|
|
1528
|
+
if (isSelfContained) {
|
|
1529
|
+
} else {
|
|
1530
|
+
setProcessing(false);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
},
|
|
1534
|
+
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
|
|
1535
|
+
);
|
|
1536
|
+
const isReady = flopay !== null && elements !== null;
|
|
1537
|
+
return /* @__PURE__ */ jsxs3("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
1538
|
+
(is3DSActive || isSubmitting) && /* @__PURE__ */ jsx5("div", { "data-testid": "flopay-overlay", style: {
|
|
1539
|
+
position: "absolute",
|
|
1540
|
+
inset: 0,
|
|
1541
|
+
background: "rgba(255,255,255,0.7)",
|
|
1542
|
+
display: "flex",
|
|
1543
|
+
alignItems: "center",
|
|
1544
|
+
justifyContent: "center",
|
|
1545
|
+
zIndex: 10
|
|
1546
|
+
}, children: is3DSActive ? "Verifying payment..." : "Processing..." }),
|
|
1547
|
+
!isReady && /* @__PURE__ */ jsx5("div", { "data-testid": "flopay-loading", "aria-busy": "true", children: "Loading payment form..." }),
|
|
1548
|
+
isReady && /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1549
|
+
/* @__PURE__ */ jsx5(PaymentElement, { options: { layout } }),
|
|
1550
|
+
showAddress && /* @__PURE__ */ jsx5(AddressElement, { options: { mode: showAddress === true ? "billing" : showAddress } }),
|
|
1551
|
+
displayError && /* @__PURE__ */ jsx5("div", { role: "alert", "data-testid": "flopay-error", style: { color: "red", margin: "0.75rem 0" }, children: displayError }),
|
|
1552
|
+
children ?? /* @__PURE__ */ jsx5(
|
|
1553
|
+
"button",
|
|
1554
|
+
{
|
|
1555
|
+
type: "submit",
|
|
1556
|
+
disabled: isSubmitting || !isReady,
|
|
1557
|
+
"data-testid": "flopay-submit",
|
|
1558
|
+
children: isSubmitting ? "Processing..." : submitLabel
|
|
1559
|
+
}
|
|
1560
|
+
)
|
|
1561
|
+
] })
|
|
1562
|
+
] });
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// src/paypal-button.tsx
|
|
1566
|
+
import { FloPayError as FloPayError4 } from "@flopay/shared";
|
|
1567
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useRef as useRef4, useState as useState5 } from "react";
|
|
1568
|
+
import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1569
|
+
function PayPalButton({
|
|
1570
|
+
sessionId,
|
|
1571
|
+
billingApiUrl,
|
|
1572
|
+
email,
|
|
1573
|
+
userId,
|
|
1574
|
+
firstName,
|
|
1575
|
+
lastName,
|
|
1576
|
+
chv,
|
|
1577
|
+
onTokenizedBody,
|
|
1578
|
+
onComplete,
|
|
1579
|
+
onErrorChange,
|
|
1580
|
+
isProcessing = false
|
|
1581
|
+
}) {
|
|
1582
|
+
const flopay = useFloPay();
|
|
1583
|
+
const elements = useElements();
|
|
1584
|
+
const contextBillingUrl = useBillingApiUrl();
|
|
1585
|
+
const [ready, setReady] = useState5(false);
|
|
1586
|
+
const [submitting, setSubmitting] = useState5(false);
|
|
1587
|
+
const paypalResumeAttempted = useRef4(false);
|
|
1588
|
+
const baseUrl = (billingApiUrl || contextBillingUrl).replace(/\/+$/, "");
|
|
1589
|
+
const processPaymentInternal = useCallback4(
|
|
1590
|
+
async (tokenizedBody) => {
|
|
1591
|
+
try {
|
|
1592
|
+
const response = await fetch(`${baseUrl}/v1/checkouts/sessions/process`, {
|
|
1593
|
+
method: "POST",
|
|
1594
|
+
headers: {
|
|
1595
|
+
"Content-Type": "application/json",
|
|
1596
|
+
"x-user-id": userId ?? ""
|
|
1597
|
+
},
|
|
1598
|
+
body: JSON.stringify({
|
|
1599
|
+
sessionId,
|
|
1600
|
+
tokenizedData: tokenizedBody,
|
|
1601
|
+
accountData: {
|
|
1602
|
+
userId: userId ?? "",
|
|
1603
|
+
email: email ?? "",
|
|
1604
|
+
firstName: firstName ?? "",
|
|
1605
|
+
lastName: lastName ?? ""
|
|
1606
|
+
},
|
|
1607
|
+
chv
|
|
1608
|
+
})
|
|
1609
|
+
});
|
|
1610
|
+
if (response.ok) {
|
|
1611
|
+
onComplete?.();
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
const json = await response.json().catch(() => null);
|
|
1615
|
+
onErrorChange?.(json?.message ?? "Payment failed. Please try again.");
|
|
1616
|
+
} catch (err) {
|
|
1617
|
+
onErrorChange?.(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
1618
|
+
}
|
|
1619
|
+
},
|
|
1620
|
+
[baseUrl, sessionId, userId, email, firstName, lastName, chv, onComplete, onErrorChange]
|
|
1621
|
+
);
|
|
1622
|
+
const dispatchTokenizedBody = useCallback4(
|
|
1623
|
+
(body) => {
|
|
1624
|
+
if (onTokenizedBody) {
|
|
1625
|
+
onTokenizedBody(body);
|
|
1626
|
+
} else {
|
|
1627
|
+
processPaymentInternal(body);
|
|
1628
|
+
}
|
|
1629
|
+
},
|
|
1630
|
+
[onTokenizedBody, processPaymentInternal]
|
|
1631
|
+
);
|
|
1632
|
+
useEffect6(() => {
|
|
1633
|
+
if (!flopay || paypalResumeAttempted.current) return;
|
|
1634
|
+
const params = new URLSearchParams(window.location.search);
|
|
1635
|
+
const paymentIntentId = params.get("payment_intent");
|
|
1636
|
+
const clientSecret = params.get("payment_intent_client_secret");
|
|
1637
|
+
const redirectStatus = params.get("redirect_status");
|
|
1638
|
+
if (!paymentIntentId || !clientSecret) return;
|
|
1639
|
+
paypalResumeAttempted.current = true;
|
|
1640
|
+
(async () => {
|
|
1641
|
+
try {
|
|
1642
|
+
setSubmitting(true);
|
|
1643
|
+
if (redirectStatus === "failed") {
|
|
1644
|
+
onErrorChange?.("PayPal payment was declined. Please try again.");
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
const provider = flopay.getRawProvider();
|
|
1648
|
+
if (!provider?.retrievePaymentIntent) {
|
|
1649
|
+
onErrorChange?.("Cannot retrieve PayPal payment status.");
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
const { paymentIntent, error: retrieveError } = await provider.retrievePaymentIntent(clientSecret);
|
|
1653
|
+
if (retrieveError) {
|
|
1654
|
+
onErrorChange?.(retrieveError.message ?? "Failed to retrieve PayPal payment status.");
|
|
1655
|
+
return;
|
|
1656
|
+
}
|
|
1657
|
+
if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded")) {
|
|
1658
|
+
const pmId = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
|
|
1659
|
+
dispatchTokenizedBody({
|
|
1660
|
+
id: pmId ?? paymentIntent.id,
|
|
1661
|
+
type: "card",
|
|
1662
|
+
threeDSecureActionResultTokenId: paymentIntent.id,
|
|
1663
|
+
isPaypal: true
|
|
1664
|
+
});
|
|
1665
|
+
const url = new URL(window.location.href);
|
|
1666
|
+
url.searchParams.delete("payment_intent");
|
|
1667
|
+
url.searchParams.delete("payment_intent_client_secret");
|
|
1668
|
+
url.searchParams.delete("redirect_status");
|
|
1669
|
+
window.history.replaceState({}, "", url.toString());
|
|
1670
|
+
} else {
|
|
1671
|
+
onErrorChange?.("PayPal payment was not completed. Please try again.");
|
|
1672
|
+
}
|
|
1673
|
+
} catch (err) {
|
|
1674
|
+
onErrorChange?.(err instanceof Error ? err.message : "Failed to complete PayPal payment.");
|
|
1675
|
+
} finally {
|
|
1676
|
+
setSubmitting(false);
|
|
1677
|
+
}
|
|
1678
|
+
})();
|
|
1679
|
+
}, [flopay, dispatchTokenizedBody, onErrorChange]);
|
|
1680
|
+
const handlePayPalConfirm = useCallback4(async () => {
|
|
1681
|
+
if (!flopay || !elements) return;
|
|
1682
|
+
try {
|
|
1683
|
+
setSubmitting(true);
|
|
1684
|
+
onErrorChange?.(null);
|
|
1685
|
+
if (!sessionId || !email) {
|
|
1686
|
+
throw new FloPayError4("Missing sessionId or email for PayPal payment", "validation_error");
|
|
1687
|
+
}
|
|
1688
|
+
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
1689
|
+
method: "POST",
|
|
1690
|
+
headers: { "Content-Type": "application/json" },
|
|
1691
|
+
body: JSON.stringify({
|
|
1692
|
+
sessionId,
|
|
1693
|
+
email,
|
|
1694
|
+
paymentMethodType: "paypal",
|
|
1695
|
+
isPaypal: "true"
|
|
1696
|
+
})
|
|
1697
|
+
});
|
|
1698
|
+
if (!intentResponse.ok) throw new FloPayError4("Failed to create payment intent", "api_error");
|
|
1699
|
+
const intentJson = await intentResponse.json();
|
|
1700
|
+
const intentClientSecret = intentJson.data?.id;
|
|
1701
|
+
if (!intentClientSecret) throw new FloPayError4("No client_secret in response", "api_error");
|
|
1702
|
+
const result = await flopay.confirmPayment({
|
|
1703
|
+
clientSecret: intentClientSecret,
|
|
1704
|
+
returnUrl: window.location.href
|
|
1705
|
+
});
|
|
1706
|
+
if (result.status === "succeeded" || result.status === "processing") {
|
|
1707
|
+
dispatchTokenizedBody({
|
|
1708
|
+
id: result.paymentIntentId,
|
|
1709
|
+
type: "card",
|
|
1710
|
+
threeDSecureActionResultTokenId: result.paymentIntentId,
|
|
1711
|
+
isPaypal: true
|
|
1712
|
+
});
|
|
1713
|
+
} else if (result.error) {
|
|
1714
|
+
onErrorChange?.(result.error.message);
|
|
1715
|
+
}
|
|
1716
|
+
} catch (err) {
|
|
1717
|
+
onErrorChange?.(err instanceof Error ? err.message : "PayPal payment failed. Please try again.");
|
|
1718
|
+
} finally {
|
|
1719
|
+
setSubmitting(false);
|
|
1720
|
+
}
|
|
1721
|
+
}, [flopay, elements, sessionId, email, baseUrl, dispatchTokenizedBody, onErrorChange]);
|
|
1722
|
+
if (!flopay || !elements) {
|
|
1723
|
+
return /* @__PURE__ */ jsx6("div", { style: { height: 45, background: "#f0f0f0", borderRadius: 6, animation: "pulse 1.5s infinite" } });
|
|
1724
|
+
}
|
|
1725
|
+
return /* @__PURE__ */ jsxs4(Fragment4, { children: [
|
|
1726
|
+
!ready && /* @__PURE__ */ jsx6("div", { style: { height: 45, background: "#f0f0f0", borderRadius: 6 } }),
|
|
1727
|
+
/* @__PURE__ */ jsx6("div", { style: ready ? {} : { display: "none" }, children: /* @__PURE__ */ jsx6(
|
|
1728
|
+
"button",
|
|
1729
|
+
{
|
|
1730
|
+
type: "button",
|
|
1731
|
+
onClick: handlePayPalConfirm,
|
|
1732
|
+
disabled: submitting || isProcessing,
|
|
1733
|
+
style: {
|
|
1734
|
+
width: "100%",
|
|
1735
|
+
height: 45,
|
|
1736
|
+
backgroundColor: "#ffc439",
|
|
1737
|
+
color: "#003087",
|
|
1738
|
+
border: "none",
|
|
1739
|
+
borderRadius: 6,
|
|
1740
|
+
fontSize: "1rem",
|
|
1741
|
+
fontWeight: 700,
|
|
1742
|
+
cursor: submitting || isProcessing ? "not-allowed" : "pointer",
|
|
1743
|
+
opacity: submitting || isProcessing ? 0.6 : 1
|
|
1744
|
+
},
|
|
1745
|
+
ref: () => setReady(true),
|
|
1746
|
+
children: submitting ? "Processing..." : "PayPal"
|
|
1747
|
+
}
|
|
1748
|
+
) }),
|
|
1749
|
+
(submitting || isProcessing) && /* @__PURE__ */ jsx6("div", { style: {
|
|
1750
|
+
position: "fixed",
|
|
1751
|
+
inset: 0,
|
|
1752
|
+
background: "rgba(0,0,0,0.4)",
|
|
1753
|
+
display: "flex",
|
|
1754
|
+
alignItems: "center",
|
|
1755
|
+
justifyContent: "center",
|
|
1756
|
+
zIndex: 1e3
|
|
1757
|
+
}, children: /* @__PURE__ */ jsx6("div", { style: {
|
|
1758
|
+
background: "white",
|
|
1759
|
+
borderRadius: 8,
|
|
1760
|
+
padding: "1.5rem",
|
|
1761
|
+
textAlign: "center",
|
|
1762
|
+
boxShadow: "0 4px 24px rgba(0,0,0,0.15)",
|
|
1763
|
+
width: 280
|
|
1764
|
+
}, children: "Processing PayPal payment..." }) })
|
|
1765
|
+
] });
|
|
1766
|
+
}
|
|
1767
|
+
export {
|
|
1768
|
+
AddressElement,
|
|
1769
|
+
CardCvcElement,
|
|
1770
|
+
CardElement,
|
|
1771
|
+
CardExpiryElement,
|
|
1772
|
+
CardNumberElement,
|
|
1773
|
+
CheckoutForm,
|
|
1774
|
+
FloPayCheckout,
|
|
1775
|
+
FloPayProvider,
|
|
1776
|
+
PayPalButton,
|
|
1777
|
+
PaymentElement,
|
|
1778
|
+
SplitCardForm,
|
|
1779
|
+
useCheckout,
|
|
1780
|
+
useElements,
|
|
1781
|
+
useFloPay
|
|
1782
|
+
};
|
|
1783
|
+
//# sourceMappingURL=index.mjs.map
|