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