@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/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