@flopay/react 0.3.5 → 0.3.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/index.cjs +574 -207
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +35 -5
- package/dist/index.d.ts +35 -5
- package/dist/index.mjs +565 -198
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -118,7 +118,7 @@ function FloPayProvider({
|
|
|
118
118
|
// src/flopay-checkout.tsx
|
|
119
119
|
var import_react7 = __toESM(require("react"), 1);
|
|
120
120
|
var import_js = require("@flopay/js");
|
|
121
|
-
var
|
|
121
|
+
var import_shared6 = require("@flopay/shared");
|
|
122
122
|
|
|
123
123
|
// src/card-button-content.tsx
|
|
124
124
|
var import_react3 = require("react");
|
|
@@ -162,10 +162,29 @@ function DefaultCardButtonContent() {
|
|
|
162
162
|
)
|
|
163
163
|
] });
|
|
164
164
|
}
|
|
165
|
+
function DefaultBackButtonContent() {
|
|
166
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: "Go back" });
|
|
167
|
+
}
|
|
168
|
+
function DefaultTitleContent() {
|
|
169
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: "Secure card checkout" });
|
|
170
|
+
}
|
|
171
|
+
function isEmptySlotContent(content) {
|
|
172
|
+
return content !== void 0 && (content === "" || content === null || content === false);
|
|
173
|
+
}
|
|
165
174
|
function CardButtonContentSlot({
|
|
166
175
|
content
|
|
167
176
|
}) {
|
|
168
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: content
|
|
177
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: content === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultCardButtonContent, {}) : content });
|
|
178
|
+
}
|
|
179
|
+
function BackButtonContentSlot({
|
|
180
|
+
content
|
|
181
|
+
}) {
|
|
182
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: content === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultBackButtonContent, {}) : content });
|
|
183
|
+
}
|
|
184
|
+
function TitleContentSlot({
|
|
185
|
+
content
|
|
186
|
+
}) {
|
|
187
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: content === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultTitleContent, {}) : content });
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
// src/elements.tsx
|
|
@@ -230,7 +249,7 @@ var AddressElement = createElementComponent("address", "AddressElement");
|
|
|
230
249
|
|
|
231
250
|
// src/split-card-form.tsx
|
|
232
251
|
var import_react_stripe_js = require("@stripe/react-stripe-js");
|
|
233
|
-
var
|
|
252
|
+
var import_shared4 = require("@flopay/shared");
|
|
234
253
|
var import_react6 = require("react");
|
|
235
254
|
|
|
236
255
|
// src/hooks.ts
|
|
@@ -252,8 +271,116 @@ function useBillingApiUrl() {
|
|
|
252
271
|
return ctx.billingApiUrl || (0, import_shared2.resolveBillingApiUrl)();
|
|
253
272
|
}
|
|
254
273
|
|
|
274
|
+
// src/checkout-utils.ts
|
|
275
|
+
var import_shared3 = require("@flopay/shared");
|
|
276
|
+
function mergeInlineSessionPatches(base, patch) {
|
|
277
|
+
if (!patch) return base;
|
|
278
|
+
if (!base) return patch;
|
|
279
|
+
return {
|
|
280
|
+
account: { ...base.account ?? {}, ...patch.account ?? {} },
|
|
281
|
+
couponCodes: patch.couponCodes ?? base.couponCodes,
|
|
282
|
+
tagsData: {
|
|
283
|
+
...base.tagsData ?? {},
|
|
284
|
+
...patch.tagsData ?? {}
|
|
285
|
+
},
|
|
286
|
+
utmMetadata: patch.utmMetadata ?? base.utmMetadata
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function mergeInlineSessionPatch(params, patch) {
|
|
290
|
+
if (!patch) return params;
|
|
291
|
+
return {
|
|
292
|
+
...params,
|
|
293
|
+
account: {
|
|
294
|
+
...params.account,
|
|
295
|
+
...patch.account ?? {}
|
|
296
|
+
},
|
|
297
|
+
couponCodes: patch.couponCodes ?? params.couponCodes,
|
|
298
|
+
tagsData: {
|
|
299
|
+
...params.tagsData ?? {},
|
|
300
|
+
...patch.tagsData ?? {}
|
|
301
|
+
},
|
|
302
|
+
utmMetadata: patch.utmMetadata ?? params.utmMetadata
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function buildSyntheticSession(params, checkoutModeOverride) {
|
|
306
|
+
const totalAmount = [
|
|
307
|
+
...(params.items ?? []).map((item) => item.overrideAmount ?? item.totalAmount ?? 0),
|
|
308
|
+
...(params.subscriptions ?? []).map((subscription) => subscription.overrideAmount ?? subscription.totalAmount ?? 0)
|
|
309
|
+
].reduce((sum, value) => sum + value, 0);
|
|
310
|
+
const currency = params.items?.[0]?.currency ?? params.subscriptions?.[0]?.currency ?? "USD";
|
|
311
|
+
return {
|
|
312
|
+
id: "",
|
|
313
|
+
clientSecret: "",
|
|
314
|
+
mode: "payment",
|
|
315
|
+
amount: Math.round(totalAmount * 100),
|
|
316
|
+
currency,
|
|
317
|
+
status: "open",
|
|
318
|
+
customer: {
|
|
319
|
+
id: params.account.userId,
|
|
320
|
+
email: params.account.email ?? "",
|
|
321
|
+
firstName: params.account.firstName,
|
|
322
|
+
lastName: params.account.lastName,
|
|
323
|
+
gender: params.account.gender ?? void 0,
|
|
324
|
+
city: params.account.city ?? void 0,
|
|
325
|
+
state: params.account.state ?? void 0,
|
|
326
|
+
country: params.account.country ?? void 0,
|
|
327
|
+
zip: params.account.zip ?? void 0
|
|
328
|
+
},
|
|
329
|
+
successUrl: params.successUrl,
|
|
330
|
+
cancelUrl: params.cancelUrl,
|
|
331
|
+
checkoutMode: params.checkoutMode ?? checkoutModeOverride ?? "full",
|
|
332
|
+
items: (params.items ?? []).map((item, idx) => ({
|
|
333
|
+
uuid: `synthetic-item-${idx}`,
|
|
334
|
+
checkoutSessionId: "",
|
|
335
|
+
providerItemId: item.providerItemId,
|
|
336
|
+
providerItemName: item.providerItemName ?? item.providerItemId,
|
|
337
|
+
quantity: item.quantity ?? 1,
|
|
338
|
+
totalAmount: item.totalAmount,
|
|
339
|
+
overrideAmount: item.overrideAmount ?? null,
|
|
340
|
+
currency: item.currency ?? currency
|
|
341
|
+
})),
|
|
342
|
+
subscriptions: (params.subscriptions ?? []).map((subscription, idx) => ({
|
|
343
|
+
uuid: `synthetic-sub-${idx}`,
|
|
344
|
+
checkoutSessionId: "",
|
|
345
|
+
providerPlanId: subscription.providerPlanId,
|
|
346
|
+
providerPlanName: subscription.providerPlanName ?? subscription.providerPlanId,
|
|
347
|
+
quantity: subscription.quantity ?? 1,
|
|
348
|
+
totalAmount: subscription.totalAmount,
|
|
349
|
+
overrideAmount: subscription.overrideAmount ?? null,
|
|
350
|
+
currency: subscription.currency ?? currency
|
|
351
|
+
}))
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function ensureInlineSessionReady(params) {
|
|
355
|
+
if (!params.account.email?.trim()) {
|
|
356
|
+
throw new import_shared3.FloPayError(
|
|
357
|
+
"Email is required before continuing with card checkout.",
|
|
358
|
+
"validation_error",
|
|
359
|
+
{ param: "createSession.account.email" }
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function mergeAccountPatch(base, patch) {
|
|
364
|
+
if (!patch) return base;
|
|
365
|
+
return {
|
|
366
|
+
...base,
|
|
367
|
+
...patch
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function buildDeclineEvent(method, input, overrides) {
|
|
371
|
+
const message = typeof input === "string" ? input : input.message;
|
|
372
|
+
const code = typeof input === "string" ? overrides?.code : overrides?.code ?? input.code;
|
|
373
|
+
const declineCode = typeof input === "string" ? overrides?.declineCode : overrides?.declineCode ?? input.declineCode;
|
|
374
|
+
return {
|
|
375
|
+
method,
|
|
376
|
+
message,
|
|
377
|
+
...code ? { code } : {},
|
|
378
|
+
...declineCode ? { declineCode } : {}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
255
382
|
// src/split-card-form.tsx
|
|
256
|
-
var
|
|
383
|
+
var import_shared5 = require("@flopay/shared");
|
|
257
384
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
258
385
|
var WALLET_RESUME_KEY = "flopay_wallet_resume";
|
|
259
386
|
var FLOPAY_KEYFRAMES = `
|
|
@@ -391,7 +518,8 @@ function PayPalButtonInner({
|
|
|
391
518
|
onTokenizedBody,
|
|
392
519
|
onErrorChange,
|
|
393
520
|
isProcessing = false,
|
|
394
|
-
onButtonClick
|
|
521
|
+
onButtonClick,
|
|
522
|
+
onDecline
|
|
395
523
|
}) {
|
|
396
524
|
const stripe = (0, import_react_stripe_js.useStripe)();
|
|
397
525
|
const elements = (0, import_react_stripe_js.useElements)();
|
|
@@ -411,7 +539,9 @@ function PayPalButtonInner({
|
|
|
411
539
|
try {
|
|
412
540
|
setSubmitting(true);
|
|
413
541
|
if (redirectStatus === "failed") {
|
|
414
|
-
|
|
542
|
+
const message = "PayPal payment was declined. Please try again.";
|
|
543
|
+
onErrorChange?.(message);
|
|
544
|
+
onDecline?.(buildDeclineEvent("paypal", message));
|
|
415
545
|
return;
|
|
416
546
|
}
|
|
417
547
|
const { paymentIntent, error } = await stripe.retrievePaymentIntent(clientSecret);
|
|
@@ -433,7 +563,9 @@ function PayPalButtonInner({
|
|
|
433
563
|
url.searchParams.delete("redirect_status");
|
|
434
564
|
window.history.replaceState({}, "", url.toString());
|
|
435
565
|
} else {
|
|
436
|
-
|
|
566
|
+
const message = "PayPal payment was not completed. Please try again.";
|
|
567
|
+
onErrorChange?.(message);
|
|
568
|
+
onDecline?.(buildDeclineEvent("paypal", message));
|
|
437
569
|
}
|
|
438
570
|
} catch (err) {
|
|
439
571
|
onErrorChange?.(err instanceof Error ? err.message : "Failed to complete PayPal payment.");
|
|
@@ -441,7 +573,7 @@ function PayPalButtonInner({
|
|
|
441
573
|
setSubmitting(false);
|
|
442
574
|
}
|
|
443
575
|
})();
|
|
444
|
-
}, [stripe, onTokenizedBody, onErrorChange]);
|
|
576
|
+
}, [stripe, onTokenizedBody, onErrorChange, onDecline]);
|
|
445
577
|
const handlePayPalConfirm = (0, import_react6.useCallback)(async (_event) => {
|
|
446
578
|
if (!stripe || !elements) return;
|
|
447
579
|
onButtonClick?.("paypal");
|
|
@@ -482,7 +614,11 @@ function PayPalButtonInner({
|
|
|
482
614
|
redirect: "if_required"
|
|
483
615
|
});
|
|
484
616
|
if (confirmError) {
|
|
485
|
-
|
|
617
|
+
const message = confirmError.message ?? "PayPal payment failed.";
|
|
618
|
+
onErrorChange?.(message);
|
|
619
|
+
onDecline?.(buildDeclineEvent("paypal", message, {
|
|
620
|
+
code: confirmError.code
|
|
621
|
+
}));
|
|
486
622
|
return;
|
|
487
623
|
}
|
|
488
624
|
const confirmedPmId = typeof paymentIntent?.payment_method === "string" ? paymentIntent.payment_method : paymentIntent?.payment_method?.id;
|
|
@@ -497,7 +633,7 @@ function PayPalButtonInner({
|
|
|
497
633
|
} finally {
|
|
498
634
|
setSubmitting(false);
|
|
499
635
|
}
|
|
500
|
-
}, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]);
|
|
636
|
+
}, [stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]);
|
|
501
637
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
502
638
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
503
639
|
import_react_stripe_js.ExpressCheckoutElement,
|
|
@@ -506,6 +642,9 @@ function PayPalButtonInner({
|
|
|
506
642
|
onLoadError: () => {
|
|
507
643
|
},
|
|
508
644
|
onConfirm: handlePayPalConfirm,
|
|
645
|
+
onCancel: () => {
|
|
646
|
+
onDecline?.(buildDeclineEvent("paypal", "PayPal checkout was cancelled."));
|
|
647
|
+
},
|
|
509
648
|
options: {
|
|
510
649
|
buttonType: { paypal: "paypal" },
|
|
511
650
|
paymentMethods: {
|
|
@@ -528,13 +667,15 @@ function WalletButtonInner({
|
|
|
528
667
|
showGooglePay = true,
|
|
529
668
|
onTokenizedBody,
|
|
530
669
|
onErrorChange,
|
|
531
|
-
onButtonClick
|
|
670
|
+
onButtonClick,
|
|
671
|
+
onDecline
|
|
532
672
|
}) {
|
|
533
673
|
const stripe = (0, import_react_stripe_js.useStripe)();
|
|
534
674
|
const elements = (0, import_react_stripe_js.useElements)();
|
|
535
675
|
const [ready, setReady] = (0, import_react6.useState)(false);
|
|
536
676
|
const [submitting, setSubmitting] = (0, import_react6.useState)(false);
|
|
537
677
|
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
678
|
+
const lastWalletMethodRef = (0, import_react6.useRef)("card");
|
|
538
679
|
const handleWalletConfirm = (0, import_react6.useCallback)(
|
|
539
680
|
async (_event) => {
|
|
540
681
|
if (!stripe || !elements) return;
|
|
@@ -575,7 +716,12 @@ function WalletButtonInner({
|
|
|
575
716
|
{ payment_method: paymentMethod.id }
|
|
576
717
|
);
|
|
577
718
|
if (confirmError) {
|
|
578
|
-
|
|
719
|
+
const method = walletType === "apple_pay" ? "apple_pay" : "google_pay";
|
|
720
|
+
const message = confirmError.message ?? "Wallet payment failed.";
|
|
721
|
+
onErrorChange?.(message);
|
|
722
|
+
onDecline?.(buildDeclineEvent(method, message, {
|
|
723
|
+
code: confirmError.code
|
|
724
|
+
}));
|
|
579
725
|
return;
|
|
580
726
|
}
|
|
581
727
|
onTokenizedBody({
|
|
@@ -589,7 +735,7 @@ function WalletButtonInner({
|
|
|
589
735
|
setSubmitting(false);
|
|
590
736
|
}
|
|
591
737
|
},
|
|
592
|
-
[stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange]
|
|
738
|
+
[stripe, elements, sessionId, email, baseUrl, onTokenizedBody, onErrorChange, onDecline]
|
|
593
739
|
);
|
|
594
740
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
595
741
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
@@ -598,7 +744,14 @@ function WalletButtonInner({
|
|
|
598
744
|
onReady: () => setReady(true),
|
|
599
745
|
onLoadError: () => {
|
|
600
746
|
},
|
|
747
|
+
onClick: (event) => {
|
|
748
|
+
lastWalletMethodRef.current = event.expressPaymentType === "apple_pay" ? "apple_pay" : "google_pay";
|
|
749
|
+
event.resolve();
|
|
750
|
+
},
|
|
601
751
|
onConfirm: handleWalletConfirm,
|
|
752
|
+
onCancel: () => {
|
|
753
|
+
onDecline?.(buildDeclineEvent(lastWalletMethodRef.current, "Wallet checkout was cancelled."));
|
|
754
|
+
},
|
|
602
755
|
options: {
|
|
603
756
|
buttonType: { applePay: "plain", googlePay: "plain" },
|
|
604
757
|
paymentMethods: {
|
|
@@ -623,6 +776,7 @@ function SplitCardFormInner({
|
|
|
623
776
|
userId,
|
|
624
777
|
onComplete,
|
|
625
778
|
onError,
|
|
779
|
+
onDecline,
|
|
626
780
|
onTokenizedBody,
|
|
627
781
|
firstName,
|
|
628
782
|
lastName,
|
|
@@ -642,7 +796,10 @@ function SplitCardFormInner({
|
|
|
642
796
|
buttonsTheme,
|
|
643
797
|
buttonsStyles: buttonsStylesOverride,
|
|
644
798
|
cardButtonContent,
|
|
799
|
+
cardBackButtonContent,
|
|
800
|
+
cardTitleContent,
|
|
645
801
|
onButtonClick,
|
|
802
|
+
onBeforeButtonClick,
|
|
646
803
|
enableAVS = false,
|
|
647
804
|
avsLayout: avsLayoutProp = "row",
|
|
648
805
|
country: countryProp,
|
|
@@ -651,6 +808,7 @@ function SplitCardFormInner({
|
|
|
651
808
|
onZipChange,
|
|
652
809
|
totalAmount = 0,
|
|
653
810
|
currency = "usd",
|
|
811
|
+
initialCardOpen = false,
|
|
654
812
|
innerRef
|
|
655
813
|
}) {
|
|
656
814
|
const flopay = useFloPay();
|
|
@@ -661,9 +819,10 @@ function SplitCardFormInner({
|
|
|
661
819
|
const [is3DSActive, setIs3DSActive] = (0, import_react6.useState)(false);
|
|
662
820
|
const [selectedCountry, setSelectedCountry] = (0, import_react6.useState)(countryProp ?? "US");
|
|
663
821
|
const [zipCode, setZipCode] = (0, import_react6.useState)(zipProp ?? "");
|
|
822
|
+
const [accountPatch, setAccountPatch] = (0, import_react6.useState)({});
|
|
664
823
|
const zipCodeRef = (0, import_react6.useRef)(zipProp ?? "");
|
|
665
824
|
const selectedCountryRef = (0, import_react6.useRef)(countryProp ?? "US");
|
|
666
|
-
const [viewState, setViewState] = (0, import_react6.useState)("buttons");
|
|
825
|
+
const [viewState, setViewState] = (0, import_react6.useState)(initialCardOpen ? "card" : "buttons");
|
|
667
826
|
const showCardForm = viewState === "expanding" || viewState === "card";
|
|
668
827
|
const TRANSITION_MS = 280;
|
|
669
828
|
const expandToCard = (0, import_react6.useCallback)(() => {
|
|
@@ -674,6 +833,11 @@ function SplitCardFormInner({
|
|
|
674
833
|
setViewState("collapsing");
|
|
675
834
|
setTimeout(() => setViewState("buttons"), TRANSITION_MS);
|
|
676
835
|
}, []);
|
|
836
|
+
(0, import_react6.useEffect)(() => {
|
|
837
|
+
if (layout === "buttons" && initialCardOpen) {
|
|
838
|
+
setViewState("card");
|
|
839
|
+
}
|
|
840
|
+
}, [layout, initialCardOpen]);
|
|
677
841
|
const [fullName, setFullName] = (0, import_react6.useState)("");
|
|
678
842
|
const [formReady, setFormReady] = (0, import_react6.useState)(false);
|
|
679
843
|
const [overlayStatus, setOverlayStatus] = (0, import_react6.useState)(null);
|
|
@@ -681,7 +845,7 @@ function SplitCardFormInner({
|
|
|
681
845
|
const resolvedBillingApiUrl = billingApiUrl || contextBillingUrl;
|
|
682
846
|
const displayError = externalError ?? error;
|
|
683
847
|
const bStyles = (0, import_react6.useMemo)(() => {
|
|
684
|
-
const base = (0,
|
|
848
|
+
const base = (0, import_shared4.resolveButtonsLayoutTheme)(buttonsTheme);
|
|
685
849
|
if (!buttonsStylesOverride) return base;
|
|
686
850
|
return {
|
|
687
851
|
...base,
|
|
@@ -697,6 +861,14 @@ function SplitCardFormInner({
|
|
|
697
861
|
const isSubmitting = externalProcessing ?? processing;
|
|
698
862
|
const isSelfContained = !onTokenizedBody;
|
|
699
863
|
const baseUrl = resolvedBillingApiUrl.replace(/\/+$/, "");
|
|
864
|
+
const resolvedAccount = (0, import_react6.useMemo)(() => mergeAccountPatch({
|
|
865
|
+
userId,
|
|
866
|
+
email,
|
|
867
|
+
firstName,
|
|
868
|
+
lastName,
|
|
869
|
+
country: countryProp,
|
|
870
|
+
zip: zipProp
|
|
871
|
+
}, accountPatch), [userId, email, firstName, lastName, countryProp, zipProp, accountPatch]);
|
|
700
872
|
const stripeInstance = (0, import_react6.useMemo)(() => {
|
|
701
873
|
if (!flopay) return null;
|
|
702
874
|
return flopay.getRawProvider();
|
|
@@ -722,6 +894,12 @@ function SplitCardFormInner({
|
|
|
722
894
|
},
|
|
723
895
|
[onErrorChange]
|
|
724
896
|
);
|
|
897
|
+
const emitDecline = (0, import_react6.useCallback)(
|
|
898
|
+
(method, input, overrides) => {
|
|
899
|
+
onDecline?.(buildDeclineEvent(method, input, overrides));
|
|
900
|
+
},
|
|
901
|
+
[onDecline]
|
|
902
|
+
);
|
|
725
903
|
const showWallets = showApplePay || showGooglePay;
|
|
726
904
|
const handleNameChange = (0, import_react6.useCallback)((value) => {
|
|
727
905
|
setFullName(value);
|
|
@@ -729,6 +907,30 @@ function SplitCardFormInner({
|
|
|
729
907
|
onFirstNameChange?.(parts[0] ?? "");
|
|
730
908
|
onLastNameChange?.(parts.length > 1 ? parts.slice(1).join(" ") : "");
|
|
731
909
|
}, [onFirstNameChange, onLastNameChange]);
|
|
910
|
+
const runBeforeCardButtonClick = (0, import_react6.useCallback)(async () => {
|
|
911
|
+
if (!onBeforeButtonClick) return true;
|
|
912
|
+
try {
|
|
913
|
+
const result = await onBeforeButtonClick({
|
|
914
|
+
method: "card",
|
|
915
|
+
sessionId: sessionId || void 0
|
|
916
|
+
});
|
|
917
|
+
if (result === false) {
|
|
918
|
+
return false;
|
|
919
|
+
}
|
|
920
|
+
if (result?.account) {
|
|
921
|
+
setAccountPatch((prev) => ({ ...prev ?? {}, ...result.account }));
|
|
922
|
+
}
|
|
923
|
+
return true;
|
|
924
|
+
} catch (err) {
|
|
925
|
+
const floPayErr = err instanceof import_shared5.FloPayError ? err : new import_shared5.FloPayError(
|
|
926
|
+
err instanceof Error ? err.message : "Card before-click hook failed.",
|
|
927
|
+
"validation_error"
|
|
928
|
+
);
|
|
929
|
+
updateError(floPayErr.message);
|
|
930
|
+
onError?.(floPayErr);
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
}, [onBeforeButtonClick, sessionId, updateError, onError]);
|
|
732
934
|
const processPaymentInternal = (0, import_react6.useCallback)(
|
|
733
935
|
async (tokenizedBody) => {
|
|
734
936
|
if (processingRef.current) return;
|
|
@@ -741,16 +943,16 @@ function SplitCardFormInner({
|
|
|
741
943
|
method: "POST",
|
|
742
944
|
headers: {
|
|
743
945
|
"Content-Type": "application/json",
|
|
744
|
-
"x-user-id": userId ?? ""
|
|
946
|
+
"x-user-id": resolvedAccount.userId ?? ""
|
|
745
947
|
},
|
|
746
948
|
body: JSON.stringify({
|
|
747
949
|
sessionId,
|
|
748
950
|
tokenizedData: tokenizedBody,
|
|
749
951
|
accountData: {
|
|
750
|
-
userId: userId ?? "",
|
|
751
|
-
email: email ?? "",
|
|
752
|
-
firstName: firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
|
|
753
|
-
lastName: lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
|
|
952
|
+
userId: resolvedAccount.userId ?? "",
|
|
953
|
+
email: resolvedAccount.email ?? "",
|
|
954
|
+
firstName: resolvedAccount.firstName ?? fullName.trim().split(/\s+/)[0] ?? "",
|
|
955
|
+
lastName: resolvedAccount.lastName ?? fullName.trim().split(/\s+/).slice(1).join(" ") ?? "",
|
|
754
956
|
...enableAVS ? { zip: zipCodeRef.current, country: selectedCountryRef.current } : {}
|
|
755
957
|
},
|
|
756
958
|
chv
|
|
@@ -783,6 +985,7 @@ function SplitCardFormInner({
|
|
|
783
985
|
setOverlayStatus("error");
|
|
784
986
|
updateError(result.error.message);
|
|
785
987
|
onError?.(result.error);
|
|
988
|
+
emitDecline("card", result.error);
|
|
786
989
|
return;
|
|
787
990
|
}
|
|
788
991
|
if (result.status === "succeeded" || result.status === "processing") {
|
|
@@ -825,7 +1028,11 @@ function SplitCardFormInner({
|
|
|
825
1028
|
});
|
|
826
1029
|
if (confirmError) {
|
|
827
1030
|
setOverlayStatus("error");
|
|
828
|
-
|
|
1031
|
+
const message2 = confirmError.message ?? "PayPal payment failed.";
|
|
1032
|
+
updateError(message2);
|
|
1033
|
+
emitDecline("paypal", message2, {
|
|
1034
|
+
code: confirmError.code
|
|
1035
|
+
});
|
|
829
1036
|
}
|
|
830
1037
|
} catch (err) {
|
|
831
1038
|
setOverlayStatus("error");
|
|
@@ -834,7 +1041,12 @@ function SplitCardFormInner({
|
|
|
834
1041
|
return;
|
|
835
1042
|
}
|
|
836
1043
|
setOverlayStatus("error");
|
|
837
|
-
|
|
1044
|
+
const message = json?.message ?? "Payment failed. Please try again.";
|
|
1045
|
+
updateError(message);
|
|
1046
|
+
emitDecline(tokenizedBody.isPaypal ? "paypal" : "card", message, {
|
|
1047
|
+
code: json?.code ?? json?.gatewayErrorCode,
|
|
1048
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
1049
|
+
});
|
|
838
1050
|
await new Promise((r) => setTimeout(r, 1500));
|
|
839
1051
|
} catch (err) {
|
|
840
1052
|
setOverlayStatus("error");
|
|
@@ -846,7 +1058,7 @@ function SplitCardFormInner({
|
|
|
846
1058
|
processingRef.current = false;
|
|
847
1059
|
}
|
|
848
1060
|
},
|
|
849
|
-
[baseUrl, sessionId,
|
|
1061
|
+
[baseUrl, sessionId, resolvedAccount, fullName, chv, flopay, onComplete, onError, updateError, emitDecline]
|
|
850
1062
|
);
|
|
851
1063
|
const dispatchTokenizedBody = (0, import_react6.useCallback)(
|
|
852
1064
|
(tokenizedBody) => {
|
|
@@ -870,6 +1082,7 @@ function SplitCardFormInner({
|
|
|
870
1082
|
if (result.error) {
|
|
871
1083
|
updateError(result.error.message);
|
|
872
1084
|
onError?.(result.error);
|
|
1085
|
+
emitDecline("card", result.error);
|
|
873
1086
|
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
874
1087
|
dispatchTokenizedBody({
|
|
875
1088
|
id: result.paymentIntentId,
|
|
@@ -883,7 +1096,7 @@ function SplitCardFormInner({
|
|
|
883
1096
|
setIs3DSActive(false);
|
|
884
1097
|
}
|
|
885
1098
|
}
|
|
886
|
-
}), [flopay, dispatchTokenizedBody, onError, updateError]);
|
|
1099
|
+
}), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
|
|
887
1100
|
(0, import_react6.useEffect)(() => {
|
|
888
1101
|
if (typeof window === "undefined") return;
|
|
889
1102
|
const stored = localStorage.getItem(WALLET_RESUME_KEY);
|
|
@@ -906,14 +1119,16 @@ function SplitCardFormInner({
|
|
|
906
1119
|
async (e) => {
|
|
907
1120
|
e.preventDefault();
|
|
908
1121
|
if (!flopay || !elements || isSubmitting || processingRef.current) return;
|
|
909
|
-
|
|
1122
|
+
if (layout !== "buttons") {
|
|
1123
|
+
onButtonClick?.("card");
|
|
1124
|
+
}
|
|
910
1125
|
setProcessing(true);
|
|
911
1126
|
setOverlayStatus("processing");
|
|
912
1127
|
updateError(null);
|
|
913
1128
|
let handedOff = false;
|
|
914
1129
|
try {
|
|
915
1130
|
if (enableAVS && !zipCodeRef.current.trim()) {
|
|
916
|
-
updateError((0,
|
|
1131
|
+
updateError((0, import_shared4.getPostalCodeLabel)(selectedCountryRef.current) + " is required");
|
|
917
1132
|
return;
|
|
918
1133
|
}
|
|
919
1134
|
const submitResult = await flopay.submitElements();
|
|
@@ -934,23 +1149,23 @@ function SplitCardFormInner({
|
|
|
934
1149
|
updateError(pmResult.error?.message ?? "Failed to create payment method.");
|
|
935
1150
|
return;
|
|
936
1151
|
}
|
|
937
|
-
if (!sessionId || !email) {
|
|
938
|
-
throw new
|
|
1152
|
+
if (!sessionId || !resolvedAccount.email) {
|
|
1153
|
+
throw new import_shared5.FloPayError("Missing sessionId or email", "validation_error");
|
|
939
1154
|
}
|
|
940
1155
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
941
1156
|
method: "POST",
|
|
942
1157
|
headers: { "Content-Type": "application/json" },
|
|
943
1158
|
body: JSON.stringify({
|
|
944
1159
|
sessionId,
|
|
945
|
-
email,
|
|
1160
|
+
email: resolvedAccount.email,
|
|
946
1161
|
paymentMethodType: pmResult.paymentMethodId,
|
|
947
1162
|
isPaypal: false
|
|
948
1163
|
})
|
|
949
1164
|
});
|
|
950
|
-
if (!intentResponse.ok) throw new
|
|
1165
|
+
if (!intentResponse.ok) throw new import_shared5.FloPayError("Failed to create payment intent", "api_error");
|
|
951
1166
|
const intentJson = await intentResponse.json();
|
|
952
1167
|
const intentClientSecret = intentJson.data?.id;
|
|
953
|
-
if (!intentClientSecret) throw new
|
|
1168
|
+
if (!intentClientSecret) throw new import_shared5.FloPayError("No client_secret in payment intent response", "api_error");
|
|
954
1169
|
const confirmResult = await flopay.confirmCardPayment({
|
|
955
1170
|
clientSecret: intentClientSecret,
|
|
956
1171
|
paymentMethodId: pmResult.paymentMethodId
|
|
@@ -958,6 +1173,8 @@ function SplitCardFormInner({
|
|
|
958
1173
|
if (confirmResult.error) {
|
|
959
1174
|
setOverlayStatus("error");
|
|
960
1175
|
updateError(confirmResult.error.message);
|
|
1176
|
+
onError?.(confirmResult.error);
|
|
1177
|
+
emitDecline("card", confirmResult.error);
|
|
961
1178
|
await new Promise((r) => setTimeout(r, 1500));
|
|
962
1179
|
return;
|
|
963
1180
|
}
|
|
@@ -978,7 +1195,7 @@ function SplitCardFormInner({
|
|
|
978
1195
|
}
|
|
979
1196
|
}
|
|
980
1197
|
},
|
|
981
|
-
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
|
|
1198
|
+
[flopay, elements, isSubmitting, sessionId, resolvedAccount.email, baseUrl, isSelfContained, dispatchTokenizedBody, onButtonClick, onError, updateError, emitDecline, layout]
|
|
982
1199
|
);
|
|
983
1200
|
const isReady = flopay !== null && elements !== null;
|
|
984
1201
|
if (!isReady) {
|
|
@@ -988,6 +1205,8 @@ function SplitCardFormInner({
|
|
|
988
1205
|
const resolvedBorder = isButtons ? bStyles.cardInputBorder ?? "#e5e7eb" : "#A4A4FF";
|
|
989
1206
|
const cardBg = isButtons ? bStyles.cardFormContainer?.backgroundColor ?? "white" : "#EDEDFF";
|
|
990
1207
|
const cardInputBg = isButtons ? bStyles.cardInputBackground ?? "white" : "white";
|
|
1208
|
+
const hideBackButtonLabel = isEmptySlotContent(cardBackButtonContent);
|
|
1209
|
+
const hideTitle = isEmptySlotContent(cardTitleContent);
|
|
991
1210
|
const stripeElementStyle = isButtons && (bStyles.cardInputColor || bStyles.cardInputPlaceholderColor || bStyles.cardInputFontSize) ? {
|
|
992
1211
|
style: {
|
|
993
1212
|
base: {
|
|
@@ -1018,7 +1237,7 @@ function SplitCardFormInner({
|
|
|
1018
1237
|
style: {
|
|
1019
1238
|
display: "inline-flex",
|
|
1020
1239
|
alignItems: "center",
|
|
1021
|
-
gap: "0.5rem",
|
|
1240
|
+
gap: hideBackButtonLabel ? 0 : "0.5rem",
|
|
1022
1241
|
background: "none",
|
|
1023
1242
|
border: "none",
|
|
1024
1243
|
cursor: "pointer",
|
|
@@ -1043,11 +1262,11 @@ function SplitCardFormInner({
|
|
|
1043
1262
|
transition: "background-color 0.15s",
|
|
1044
1263
|
...bStyles.backButtonIcon
|
|
1045
1264
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("path", { d: "M15 18l-6-6 6-6" }) }) }),
|
|
1046
|
-
|
|
1265
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BackButtonContentSlot, { content: cardBackButtonContent })
|
|
1047
1266
|
]
|
|
1048
1267
|
}
|
|
1049
1268
|
),
|
|
1050
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
|
|
1269
|
+
hideTitle ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { flex: 1 } }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
|
|
1051
1270
|
flex: 1,
|
|
1052
1271
|
textAlign: "center",
|
|
1053
1272
|
fontWeight: 600,
|
|
@@ -1055,9 +1274,9 @@ function SplitCardFormInner({
|
|
|
1055
1274
|
color: "#262833",
|
|
1056
1275
|
paddingRight: 80,
|
|
1057
1276
|
...bStyles.title
|
|
1058
|
-
}, children:
|
|
1277
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TitleContentSlot, { content: cardTitleContent }) })
|
|
1059
1278
|
] }),
|
|
1060
|
-
!isButtons && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { textAlign: "center", fontWeight: 600, fontSize: "1.1rem", padding: "0.5rem 0", color: "#262833" }, children:
|
|
1279
|
+
!isButtons && !hideTitle && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { textAlign: "center", fontWeight: 600, fontSize: "1.1rem", padding: "0.5rem 0", color: "#262833" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TitleContentSlot, { content: cardTitleContent }) }),
|
|
1061
1280
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: {
|
|
1062
1281
|
backgroundColor: cardInputBg,
|
|
1063
1282
|
border: `1px solid ${resolvedBorder}`,
|
|
@@ -1147,7 +1366,7 @@ function SplitCardFormInner({
|
|
|
1147
1366
|
cursor: "pointer",
|
|
1148
1367
|
...isButtons && bStyles.nameInput ? bStyles.nameInput : {}
|
|
1149
1368
|
},
|
|
1150
|
-
children:
|
|
1369
|
+
children: import_shared4.COUNTRY_OPTIONS.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("option", { value: c.code, children: [
|
|
1151
1370
|
c.flag,
|
|
1152
1371
|
" ",
|
|
1153
1372
|
c.name
|
|
@@ -1164,7 +1383,7 @@ function SplitCardFormInner({
|
|
|
1164
1383
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1165
1384
|
"input",
|
|
1166
1385
|
{
|
|
1167
|
-
placeholder: (0,
|
|
1386
|
+
placeholder: (0, import_shared4.getPostalCodeLabel)(selectedCountry),
|
|
1168
1387
|
autoComplete: "postal-code",
|
|
1169
1388
|
value: zipCode,
|
|
1170
1389
|
onChange: (e) => {
|
|
@@ -1261,32 +1480,36 @@ function SplitCardFormInner({
|
|
|
1261
1480
|
PayPalButtonInner,
|
|
1262
1481
|
{
|
|
1263
1482
|
sessionId,
|
|
1264
|
-
email,
|
|
1483
|
+
email: resolvedAccount.email,
|
|
1265
1484
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1266
1485
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1267
1486
|
onErrorChange: updateError,
|
|
1268
1487
|
isProcessing: isSubmitting,
|
|
1269
|
-
onButtonClick
|
|
1488
|
+
onButtonClick,
|
|
1489
|
+
onDecline
|
|
1270
1490
|
}
|
|
1271
1491
|
) }) : showPayPal ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
|
|
1272
1492
|
showWallets && stripeInstance ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: walletOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1273
1493
|
WalletButtonInner,
|
|
1274
1494
|
{
|
|
1275
1495
|
sessionId,
|
|
1276
|
-
email,
|
|
1496
|
+
email: resolvedAccount.email,
|
|
1277
1497
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1278
1498
|
showApplePay,
|
|
1279
1499
|
showGooglePay,
|
|
1280
1500
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1281
1501
|
onErrorChange: updateError,
|
|
1282
|
-
onButtonClick
|
|
1502
|
+
onButtonClick,
|
|
1503
|
+
onDecline
|
|
1283
1504
|
}
|
|
1284
1505
|
) }) : showWallets ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { height: 44, borderRadius: 8, background: "#e5e7eb", animation: "flopay-pulse 1.5s ease-in-out infinite" } }) : null,
|
|
1285
1506
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1286
1507
|
"button",
|
|
1287
1508
|
{
|
|
1288
1509
|
type: "button",
|
|
1289
|
-
onClick: () => {
|
|
1510
|
+
onClick: async () => {
|
|
1511
|
+
const shouldContinue = await runBeforeCardButtonClick();
|
|
1512
|
+
if (!shouldContinue) return;
|
|
1290
1513
|
onButtonClick?.("card");
|
|
1291
1514
|
expandToCard();
|
|
1292
1515
|
},
|
|
@@ -1351,23 +1574,25 @@ function SplitCardFormInner({
|
|
|
1351
1574
|
WalletButtonInner,
|
|
1352
1575
|
{
|
|
1353
1576
|
sessionId,
|
|
1354
|
-
email,
|
|
1577
|
+
email: resolvedAccount.email,
|
|
1355
1578
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1356
1579
|
showApplePay,
|
|
1357
1580
|
showGooglePay,
|
|
1358
1581
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1359
|
-
onErrorChange: updateError
|
|
1582
|
+
onErrorChange: updateError,
|
|
1583
|
+
onDecline
|
|
1360
1584
|
}
|
|
1361
1585
|
) }),
|
|
1362
1586
|
showPayPal && stripeInstance && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_stripe_js.Elements, { stripe: stripeInstance, options: paypalOptions, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1363
1587
|
PayPalButtonInner,
|
|
1364
1588
|
{
|
|
1365
1589
|
sessionId,
|
|
1366
|
-
email,
|
|
1590
|
+
email: resolvedAccount.email,
|
|
1367
1591
|
billingApiUrl: resolvedBillingApiUrl,
|
|
1368
1592
|
onTokenizedBody: dispatchTokenizedBody,
|
|
1369
1593
|
onErrorChange: updateError,
|
|
1370
|
-
isProcessing: isSubmitting
|
|
1594
|
+
isProcessing: isSubmitting,
|
|
1595
|
+
onDecline
|
|
1371
1596
|
}
|
|
1372
1597
|
) }),
|
|
1373
1598
|
(showWallets && stripeInstance || showPayPal && stripeInstance) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: {
|
|
@@ -1399,6 +1624,7 @@ function FloPayCheckout({
|
|
|
1399
1624
|
error: errorNode,
|
|
1400
1625
|
onComplete,
|
|
1401
1626
|
onError,
|
|
1627
|
+
onDecline,
|
|
1402
1628
|
showPayPal = true,
|
|
1403
1629
|
showApplePay = true,
|
|
1404
1630
|
showGooglePay = true,
|
|
@@ -1406,7 +1632,10 @@ function FloPayCheckout({
|
|
|
1406
1632
|
buttonsTheme,
|
|
1407
1633
|
buttonsStyles,
|
|
1408
1634
|
cardButtonContent,
|
|
1635
|
+
cardBackButtonContent,
|
|
1636
|
+
cardTitleContent,
|
|
1409
1637
|
onButtonClick,
|
|
1638
|
+
onBeforeButtonClick,
|
|
1410
1639
|
enableAVS,
|
|
1411
1640
|
avsLayout,
|
|
1412
1641
|
submitLabel,
|
|
@@ -1417,7 +1646,7 @@ function FloPayCheckout({
|
|
|
1417
1646
|
renderConfirmButton,
|
|
1418
1647
|
onSessionCompleted
|
|
1419
1648
|
}) {
|
|
1420
|
-
const resolvedBillingUrl = (0,
|
|
1649
|
+
const resolvedBillingUrl = (0, import_shared6.resolveBillingApiUrl)(billingApiUrl);
|
|
1421
1650
|
const [unified, setUnified] = (0, import_react7.useState)(null);
|
|
1422
1651
|
const [flopay, setFloPay] = (0, import_react7.useState)(null);
|
|
1423
1652
|
const flopayRef = (0, import_react7.useRef)(null);
|
|
@@ -1428,13 +1657,28 @@ function FloPayCheckout({
|
|
|
1428
1657
|
const [currentMode, setCurrentMode] = (0, import_react7.useState)("full");
|
|
1429
1658
|
const [confirmProcessing, setConfirmProcessing] = (0, import_react7.useState)(false);
|
|
1430
1659
|
const [modeError, setModeError] = (0, import_react7.useState)(null);
|
|
1660
|
+
const [createSessionPatch, setCreateSessionPatch] = (0, import_react7.useState)(void 0);
|
|
1661
|
+
const [cardBootstrapPending, setCardBootstrapPending] = (0, import_react7.useState)(false);
|
|
1662
|
+
const [deferredCardOpen, setDeferredCardOpen] = (0, import_react7.useState)(false);
|
|
1431
1663
|
const autoCheckoutAttempted = (0, import_react7.useRef)(false);
|
|
1432
1664
|
const onCompleteRef = (0, import_react7.useRef)(onComplete);
|
|
1433
1665
|
onCompleteRef.current = onComplete;
|
|
1434
1666
|
const onErrorRef = (0, import_react7.useRef)(onError);
|
|
1435
1667
|
onErrorRef.current = onError;
|
|
1668
|
+
const onDeclineRef = (0, import_react7.useRef)(onDecline);
|
|
1669
|
+
onDeclineRef.current = onDecline;
|
|
1436
1670
|
const onSessionCompletedRef = (0, import_react7.useRef)(onSessionCompleted);
|
|
1437
1671
|
onSessionCompletedRef.current = onSessionCompleted;
|
|
1672
|
+
const effectiveCreateSession = (0, import_react7.useMemo)(
|
|
1673
|
+
() => createSessionParams ? mergeInlineSessionPatch(createSessionParams, createSessionPatch) : void 0,
|
|
1674
|
+
[createSessionParams, createSessionPatch]
|
|
1675
|
+
);
|
|
1676
|
+
const emitDecline = (0, import_react7.useCallback)(
|
|
1677
|
+
(method, input, overrides) => {
|
|
1678
|
+
onDeclineRef.current?.(buildDeclineEvent(method, input, overrides));
|
|
1679
|
+
},
|
|
1680
|
+
[]
|
|
1681
|
+
);
|
|
1438
1682
|
const processPaymentForMode = (0, import_react7.useCallback)(
|
|
1439
1683
|
async (sess) => {
|
|
1440
1684
|
const baseUrl = resolvedBillingUrl.replace(/\/+$/, "");
|
|
@@ -1474,9 +1718,13 @@ function FloPayCheckout({
|
|
|
1474
1718
|
// No client secret available
|
|
1475
1719
|
};
|
|
1476
1720
|
}
|
|
1477
|
-
throw new
|
|
1721
|
+
throw new import_shared6.FloPayError(
|
|
1478
1722
|
json?.message ?? "Payment failed. Please try again.",
|
|
1479
|
-
"api_error"
|
|
1723
|
+
"api_error",
|
|
1724
|
+
{
|
|
1725
|
+
code: json?.code ?? json?.gatewayErrorCode,
|
|
1726
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
1727
|
+
}
|
|
1480
1728
|
);
|
|
1481
1729
|
},
|
|
1482
1730
|
[resolvedBillingUrl, resolvedSessionId]
|
|
@@ -1495,6 +1743,9 @@ function FloPayCheckout({
|
|
|
1495
1743
|
});
|
|
1496
1744
|
if (nextActionError) {
|
|
1497
1745
|
setModeError(nextActionError.message ?? "3DS authentication failed.");
|
|
1746
|
+
emitDecline("card", nextActionError.message ?? "3DS authentication failed.", {
|
|
1747
|
+
code: nextActionError.code
|
|
1748
|
+
});
|
|
1498
1749
|
return false;
|
|
1499
1750
|
}
|
|
1500
1751
|
if (paymentIntent && (paymentIntent.status === "requires_capture" || paymentIntent.status === "succeeded")) {
|
|
@@ -1517,6 +1768,9 @@ function FloPayCheckout({
|
|
|
1517
1768
|
});
|
|
1518
1769
|
if (error) {
|
|
1519
1770
|
setModeError(error.message ?? "PayPal authorization failed.");
|
|
1771
|
+
emitDecline("paypal", error.message ?? "PayPal authorization failed.", {
|
|
1772
|
+
code: error.code
|
|
1773
|
+
});
|
|
1520
1774
|
return false;
|
|
1521
1775
|
}
|
|
1522
1776
|
onCompleteRef.current?.({ status: "succeeded" });
|
|
@@ -1524,16 +1778,24 @@ function FloPayCheckout({
|
|
|
1524
1778
|
}
|
|
1525
1779
|
return false;
|
|
1526
1780
|
},
|
|
1527
|
-
[resolvedBillingUrl, resolvedSessionId]
|
|
1781
|
+
[resolvedBillingUrl, resolvedSessionId, emitDecline]
|
|
1528
1782
|
);
|
|
1529
1783
|
const inflightRef = (0, import_react7.useRef)(/* @__PURE__ */ new Map());
|
|
1530
1784
|
const initializedHashRef = (0, import_react7.useRef)(null);
|
|
1785
|
+
const deferInlineSessionUntilCardClick = Boolean(
|
|
1786
|
+
effectiveCreateSession && !children && layout === "buttons" && onBeforeButtonClick && (effectiveCreateSession.checkoutMode ?? checkoutModeProp ?? "full") === "full"
|
|
1787
|
+
);
|
|
1531
1788
|
function hashCreateParams(params) {
|
|
1532
1789
|
const key = JSON.stringify({
|
|
1533
1790
|
c: params?.clientId,
|
|
1791
|
+
successUrl: params?.successUrl,
|
|
1792
|
+
cancelUrl: params?.cancelUrl,
|
|
1534
1793
|
i: params?.items?.map((x) => `${x.providerItemId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
|
|
1535
1794
|
s: params?.subscriptions?.map((x) => `${x.providerPlanId}:${x.totalAmount}:${x.overrideAmount ?? ""}:${x.quantity ?? 1}`).sort(),
|
|
1536
|
-
|
|
1795
|
+
account: params?.account,
|
|
1796
|
+
couponCodes: params?.couponCodes,
|
|
1797
|
+
tagsData: params?.tagsData,
|
|
1798
|
+
utmMetadata: params?.utmMetadata,
|
|
1537
1799
|
m: params?.checkoutMode ?? "full"
|
|
1538
1800
|
});
|
|
1539
1801
|
let h = 0;
|
|
@@ -1543,138 +1805,167 @@ function FloPayCheckout({
|
|
|
1543
1805
|
return `flopay_session_${Math.abs(h).toString(36)}`;
|
|
1544
1806
|
}
|
|
1545
1807
|
const createSessionHash = (0, import_react7.useMemo)(
|
|
1546
|
-
() =>
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1808
|
+
() => effectiveCreateSession ? hashCreateParams(effectiveCreateSession) : "",
|
|
1809
|
+
[effectiveCreateSession]
|
|
1810
|
+
);
|
|
1811
|
+
const createSessionParamsRef = (0, import_react7.useRef)(effectiveCreateSession);
|
|
1812
|
+
createSessionParamsRef.current = effectiveCreateSession;
|
|
1813
|
+
(0, import_react7.useEffect)(() => {
|
|
1814
|
+
setResolvedSessionId(sessionIdProp ?? "");
|
|
1815
|
+
}, [sessionIdProp]);
|
|
1816
|
+
async function resolveInlineSession(params, cacheKey) {
|
|
1817
|
+
ensureInlineSessionReady(params);
|
|
1818
|
+
const api = new import_js.PaymentAPI(resolvedBillingUrl);
|
|
1819
|
+
let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
|
|
1820
|
+
let realResult = null;
|
|
1821
|
+
if (sid) {
|
|
1822
|
+
try {
|
|
1823
|
+
realResult = await api.getUnifiedCheckoutSession(sid);
|
|
1824
|
+
if (realResult.data.session?.status === "complete") {
|
|
1825
|
+
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1826
|
+
sid = null;
|
|
1827
|
+
realResult = null;
|
|
1828
|
+
}
|
|
1829
|
+
} catch {
|
|
1830
|
+
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1831
|
+
sid = null;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
if (!sid) {
|
|
1835
|
+
realResult = await api.createAndFetchSession(params);
|
|
1836
|
+
sid = realResult.data.session?.id ?? "";
|
|
1837
|
+
if (sid && typeof window !== "undefined") {
|
|
1838
|
+
window.sessionStorage.setItem(cacheKey, sid);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return { sid: sid ?? "", result: realResult };
|
|
1842
|
+
}
|
|
1843
|
+
const bootstrapInlineSession = (0, import_react7.useCallback)(
|
|
1844
|
+
async (patch) => {
|
|
1845
|
+
const baseParams = createSessionParamsRef.current;
|
|
1846
|
+
if (!baseParams) {
|
|
1847
|
+
throw new import_shared6.FloPayError("createSession is required to bootstrap checkout.", "validation_error");
|
|
1848
|
+
}
|
|
1849
|
+
const mergedParams = mergeInlineSessionPatch(baseParams, patch);
|
|
1850
|
+
const cacheKey = hashCreateParams(mergedParams);
|
|
1851
|
+
let promise = inflightRef.current.get(cacheKey);
|
|
1852
|
+
if (!promise) {
|
|
1853
|
+
promise = resolveInlineSession(mergedParams, cacheKey);
|
|
1854
|
+
inflightRef.current.set(cacheKey, promise);
|
|
1855
|
+
}
|
|
1856
|
+
let resolved;
|
|
1857
|
+
try {
|
|
1858
|
+
resolved = await promise;
|
|
1859
|
+
} finally {
|
|
1860
|
+
inflightRef.current.delete(cacheKey);
|
|
1861
|
+
}
|
|
1862
|
+
if (patch) {
|
|
1863
|
+
setCreateSessionPatch((prev) => mergeInlineSessionPatches(prev, patch));
|
|
1864
|
+
}
|
|
1865
|
+
const { sid, result: realResult } = resolved;
|
|
1866
|
+
setUnified(realResult);
|
|
1867
|
+
if (realResult.data.session) {
|
|
1868
|
+
setSession(realResult.data.session);
|
|
1869
|
+
}
|
|
1870
|
+
if (sid) {
|
|
1871
|
+
setResolvedSessionId(sid);
|
|
1872
|
+
}
|
|
1873
|
+
let publishableKey;
|
|
1874
|
+
if (realResult.provider === "stripe") {
|
|
1875
|
+
publishableKey = realResult.data.stripe?.publishableKey;
|
|
1876
|
+
}
|
|
1877
|
+
if (!publishableKey) publishableKey = fallbackPublishableKey;
|
|
1878
|
+
if (!publishableKey) {
|
|
1879
|
+
throw new import_shared6.FloPayError(
|
|
1880
|
+
"No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
|
|
1881
|
+
"validation_error"
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
const instance = await (0, import_js.loadFloPay)(publishableKey, {
|
|
1885
|
+
billingApiUrl: resolvedBillingUrl,
|
|
1886
|
+
locale
|
|
1887
|
+
});
|
|
1888
|
+
flopayRef.current = instance;
|
|
1889
|
+
setFloPay(instance);
|
|
1890
|
+
initializedHashRef.current = cacheKey;
|
|
1891
|
+
setIsLoading(false);
|
|
1892
|
+
return resolved;
|
|
1893
|
+
},
|
|
1894
|
+
[fallbackPublishableKey, locale, resolvedBillingUrl]
|
|
1555
1895
|
);
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1896
|
+
const handleDeferredCardButtonClick = (0, import_react7.useCallback)(async () => {
|
|
1897
|
+
if (cardBootstrapPending) return;
|
|
1898
|
+
setLoadError(null);
|
|
1899
|
+
setCardBootstrapPending(true);
|
|
1900
|
+
try {
|
|
1901
|
+
const beforeClickResult = await onBeforeButtonClick?.({
|
|
1902
|
+
method: "card",
|
|
1903
|
+
createSession: effectiveCreateSession
|
|
1904
|
+
});
|
|
1905
|
+
if (beforeClickResult === false) {
|
|
1906
|
+
setDeferredCardOpen(false);
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
const patch = beforeClickResult && typeof beforeClickResult === "object" ? beforeClickResult : void 0;
|
|
1910
|
+
const baseParams = createSessionParamsRef.current;
|
|
1911
|
+
if (!baseParams) {
|
|
1912
|
+
throw new import_shared6.FloPayError(
|
|
1913
|
+
"createSession is required to bootstrap checkout.",
|
|
1914
|
+
"validation_error"
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
ensureInlineSessionReady(mergeInlineSessionPatch(baseParams, patch));
|
|
1918
|
+
setDeferredCardOpen(true);
|
|
1919
|
+
onButtonClick?.("card");
|
|
1920
|
+
await bootstrapInlineSession(patch);
|
|
1921
|
+
} catch (err) {
|
|
1922
|
+
setDeferredCardOpen(false);
|
|
1923
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(
|
|
1924
|
+
err instanceof Error ? err.message : "Failed to start card checkout.",
|
|
1925
|
+
"api_error"
|
|
1926
|
+
);
|
|
1927
|
+
setLoadError(floPayErr);
|
|
1928
|
+
onErrorRef.current?.(floPayErr);
|
|
1929
|
+
} finally {
|
|
1930
|
+
setCardBootstrapPending(false);
|
|
1931
|
+
}
|
|
1932
|
+
}, [
|
|
1933
|
+
bootstrapInlineSession,
|
|
1934
|
+
cardBootstrapPending,
|
|
1935
|
+
effectiveCreateSession,
|
|
1936
|
+
onButtonClick,
|
|
1937
|
+
onBeforeButtonClick
|
|
1938
|
+
]);
|
|
1558
1939
|
(0, import_react7.useEffect)(() => {
|
|
1559
1940
|
let cancelled = false;
|
|
1560
1941
|
setLoadError(null);
|
|
1561
1942
|
if (createSessionHash) {
|
|
1943
|
+
if (deferInlineSessionUntilCardClick) {
|
|
1944
|
+
if (initializedHashRef.current !== createSessionHash) {
|
|
1945
|
+
setSession(null);
|
|
1946
|
+
setUnified(null);
|
|
1947
|
+
setFloPay(null);
|
|
1948
|
+
setResolvedSessionId("");
|
|
1949
|
+
setDeferredCardOpen(false);
|
|
1950
|
+
flopayRef.current = null;
|
|
1951
|
+
}
|
|
1952
|
+
setCurrentMode(createSessionParamsRef.current?.checkoutMode ?? checkoutModeProp ?? "full");
|
|
1953
|
+
setIsLoading(false);
|
|
1954
|
+
return () => {
|
|
1955
|
+
cancelled = true;
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1562
1958
|
if (initializedHashRef.current === createSessionHash) return;
|
|
1563
1959
|
const params = createSessionParamsRef.current;
|
|
1564
|
-
|
|
1565
|
-
...(params.items ?? []).map((i) => i.overrideAmount ?? i.totalAmount ?? 0),
|
|
1566
|
-
...(params.subscriptions ?? []).map((s) => s.overrideAmount ?? s.totalAmount ?? 0)
|
|
1567
|
-
].reduce((sum, v) => sum + v, 0);
|
|
1568
|
-
const currency = params.items?.[0]?.currency ?? params.subscriptions?.[0]?.currency ?? "USD";
|
|
1569
|
-
const syntheticSession = {
|
|
1570
|
-
id: "",
|
|
1571
|
-
clientSecret: "",
|
|
1572
|
-
mode: "payment",
|
|
1573
|
-
amount: Math.round(totalAmount * 100),
|
|
1574
|
-
currency,
|
|
1575
|
-
status: "open",
|
|
1576
|
-
customer: {
|
|
1577
|
-
id: params.account.userId,
|
|
1578
|
-
email: params.account.email,
|
|
1579
|
-
firstName: params.account.firstName,
|
|
1580
|
-
lastName: params.account.lastName
|
|
1581
|
-
},
|
|
1582
|
-
successUrl: params.successUrl,
|
|
1583
|
-
cancelUrl: params.cancelUrl,
|
|
1584
|
-
checkoutMode: params.checkoutMode ?? "full",
|
|
1585
|
-
items: (params.items ?? []).map((i, idx) => ({
|
|
1586
|
-
uuid: `synthetic-item-${idx}`,
|
|
1587
|
-
checkoutSessionId: "",
|
|
1588
|
-
providerItemId: i.providerItemId,
|
|
1589
|
-
providerItemName: i.providerItemName ?? i.providerItemId,
|
|
1590
|
-
quantity: i.quantity ?? 1,
|
|
1591
|
-
totalAmount: i.totalAmount,
|
|
1592
|
-
overrideAmount: i.overrideAmount ?? null,
|
|
1593
|
-
currency: i.currency ?? currency
|
|
1594
|
-
})),
|
|
1595
|
-
subscriptions: (params.subscriptions ?? []).map((s, idx) => ({
|
|
1596
|
-
uuid: `synthetic-sub-${idx}`,
|
|
1597
|
-
checkoutSessionId: "",
|
|
1598
|
-
providerPlanId: s.providerPlanId,
|
|
1599
|
-
providerPlanName: s.providerPlanName ?? s.providerPlanId,
|
|
1600
|
-
quantity: s.quantity ?? 1,
|
|
1601
|
-
totalAmount: s.totalAmount,
|
|
1602
|
-
overrideAmount: s.overrideAmount ?? null,
|
|
1603
|
-
currency: s.currency ?? currency
|
|
1604
|
-
}))
|
|
1605
|
-
};
|
|
1606
|
-
setSession(syntheticSession);
|
|
1960
|
+
setSession(buildSyntheticSession(params, checkoutModeProp));
|
|
1607
1961
|
setCurrentMode(params.checkoutMode ?? checkoutModeProp ?? "full");
|
|
1608
1962
|
setIsLoading(false);
|
|
1609
|
-
const cacheKey = createSessionHash;
|
|
1610
|
-
async function resolveSession() {
|
|
1611
|
-
const api = new import_js.PaymentAPI(resolvedBillingUrl);
|
|
1612
|
-
let sid = typeof window !== "undefined" ? window.sessionStorage.getItem(cacheKey) : null;
|
|
1613
|
-
let realResult = null;
|
|
1614
|
-
if (sid) {
|
|
1615
|
-
try {
|
|
1616
|
-
realResult = await api.getUnifiedCheckoutSession(sid);
|
|
1617
|
-
if (realResult.data.session?.status === "complete") {
|
|
1618
|
-
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1619
|
-
sid = null;
|
|
1620
|
-
realResult = null;
|
|
1621
|
-
}
|
|
1622
|
-
} catch {
|
|
1623
|
-
if (typeof window !== "undefined") window.sessionStorage.removeItem(cacheKey);
|
|
1624
|
-
sid = null;
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
if (!sid) {
|
|
1628
|
-
realResult = await api.createAndFetchSession(createSessionParamsRef.current);
|
|
1629
|
-
sid = realResult.data.session?.id ?? "";
|
|
1630
|
-
if (sid && typeof window !== "undefined") {
|
|
1631
|
-
window.sessionStorage.setItem(cacheKey, sid);
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
return { sid: sid ?? "", result: realResult };
|
|
1635
|
-
}
|
|
1636
1963
|
(async () => {
|
|
1637
1964
|
try {
|
|
1638
|
-
|
|
1639
|
-
if (!promise) {
|
|
1640
|
-
promise = resolveSession();
|
|
1641
|
-
inflightRef.current.set(cacheKey, promise);
|
|
1642
|
-
}
|
|
1643
|
-
let resolved;
|
|
1644
|
-
try {
|
|
1645
|
-
resolved = await promise;
|
|
1646
|
-
} finally {
|
|
1647
|
-
inflightRef.current.delete(cacheKey);
|
|
1648
|
-
}
|
|
1649
|
-
if (cancelled) return;
|
|
1650
|
-
const { sid, result: realResult } = resolved;
|
|
1651
|
-
if (realResult) {
|
|
1652
|
-
setUnified(realResult);
|
|
1653
|
-
if (realResult.data.session) setSession(realResult.data.session);
|
|
1654
|
-
}
|
|
1655
|
-
if (sid) {
|
|
1656
|
-
setResolvedSessionId(sid);
|
|
1657
|
-
}
|
|
1658
|
-
let pk;
|
|
1659
|
-
if (realResult?.provider === "stripe") {
|
|
1660
|
-
pk = realResult.data.stripe?.publishableKey;
|
|
1661
|
-
}
|
|
1662
|
-
if (!pk) pk = fallbackPublishableKey;
|
|
1663
|
-
if (!pk) {
|
|
1664
|
-
throw new import_shared5.FloPayError(
|
|
1665
|
-
"No publishable key found. Provide fallbackPublishableKey or ensure the session includes gatewayData.publishableKey.",
|
|
1666
|
-
"validation_error"
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
const instance = await (0, import_js.loadFloPay)(pk, { billingApiUrl: resolvedBillingUrl, locale });
|
|
1670
|
-
if (!cancelled) {
|
|
1671
|
-
flopayRef.current = instance;
|
|
1672
|
-
setFloPay(instance);
|
|
1673
|
-
initializedHashRef.current = cacheKey;
|
|
1674
|
-
}
|
|
1965
|
+
await bootstrapInlineSession();
|
|
1675
1966
|
} catch (err) {
|
|
1676
1967
|
if (cancelled) return;
|
|
1677
|
-
const floPayErr = err instanceof
|
|
1968
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(err instanceof Error ? err.message : "Failed to create session", "api_error");
|
|
1678
1969
|
setLoadError(floPayErr);
|
|
1679
1970
|
}
|
|
1680
1971
|
})();
|
|
@@ -1692,7 +1983,7 @@ function FloPayCheckout({
|
|
|
1692
1983
|
const sess = result.data.session ?? null;
|
|
1693
1984
|
setSession(sess);
|
|
1694
1985
|
if (!sess) {
|
|
1695
|
-
throw new
|
|
1986
|
+
throw new import_shared6.FloPayError("No session data returned", "api_error");
|
|
1696
1987
|
}
|
|
1697
1988
|
if (sess.status === "complete") {
|
|
1698
1989
|
setIsLoading(false);
|
|
@@ -1731,7 +2022,7 @@ function FloPayCheckout({
|
|
|
1731
2022
|
if (!cancelled) setIsLoading(false);
|
|
1732
2023
|
} catch (err) {
|
|
1733
2024
|
if (cancelled) return;
|
|
1734
|
-
const floPayErr = err instanceof
|
|
2025
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(err instanceof Error ? err.message : "Failed to initialize checkout", "api_error");
|
|
1735
2026
|
setLoadError(floPayErr);
|
|
1736
2027
|
setIsLoading(false);
|
|
1737
2028
|
}
|
|
@@ -1743,7 +2034,7 @@ function FloPayCheckout({
|
|
|
1743
2034
|
}
|
|
1744
2035
|
if (!publishableKey) publishableKey = fallbackPublishableKey;
|
|
1745
2036
|
if (!publishableKey) {
|
|
1746
|
-
throw new
|
|
2037
|
+
throw new import_shared6.FloPayError(
|
|
1747
2038
|
"No publishable key found in session response. Provide a fallbackPublishableKey prop or ensure the session includes gatewayData.publishableKey.",
|
|
1748
2039
|
"validation_error"
|
|
1749
2040
|
);
|
|
@@ -1759,7 +2050,7 @@ function FloPayCheckout({
|
|
|
1759
2050
|
return () => {
|
|
1760
2051
|
cancelled = true;
|
|
1761
2052
|
};
|
|
1762
|
-
}, [resolvedSessionId, createSessionHash,
|
|
2053
|
+
}, [resolvedSessionId, createSessionHash, checkoutModeProp, deferInlineSessionUntilCardClick, bootstrapInlineSession]);
|
|
1763
2054
|
const handleConfirmCheckout = (0, import_react7.useCallback)(async () => {
|
|
1764
2055
|
if (confirmProcessing || !session) return;
|
|
1765
2056
|
setConfirmProcessing(true);
|
|
@@ -1774,17 +2065,18 @@ function FloPayCheckout({
|
|
|
1774
2065
|
setCurrentMode("full");
|
|
1775
2066
|
}
|
|
1776
2067
|
} catch (err) {
|
|
1777
|
-
const floPayErr = err instanceof
|
|
2068
|
+
const floPayErr = err instanceof import_shared6.FloPayError ? err : new import_shared6.FloPayError(
|
|
1778
2069
|
err instanceof Error ? err.message : "Payment failed",
|
|
1779
2070
|
"api_error"
|
|
1780
2071
|
);
|
|
1781
2072
|
setModeError(floPayErr.message);
|
|
1782
2073
|
onError?.(floPayErr);
|
|
2074
|
+
emitDecline("card", floPayErr);
|
|
1783
2075
|
setCurrentMode("full");
|
|
1784
2076
|
} finally {
|
|
1785
2077
|
setConfirmProcessing(false);
|
|
1786
2078
|
}
|
|
1787
|
-
}, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError]);
|
|
2079
|
+
}, [confirmProcessing, session, processPaymentForMode, handleRedirectResult, onError, emitDecline]);
|
|
1788
2080
|
const providerOptions = (0, import_react7.useMemo)(() => {
|
|
1789
2081
|
if (!unified || !session) return void 0;
|
|
1790
2082
|
const opts = {
|
|
@@ -1795,7 +2087,7 @@ function FloPayCheckout({
|
|
|
1795
2087
|
if (unified.provider === "stripe" && unified.data.stripe?.clientSecret) {
|
|
1796
2088
|
opts.clientSecret = unified.data.stripe.clientSecret;
|
|
1797
2089
|
} else {
|
|
1798
|
-
const displayTotal = (0,
|
|
2090
|
+
const displayTotal = (0, import_shared6.buildCheckoutDisplayData)(session).total;
|
|
1799
2091
|
opts.amount = Math.round(displayTotal * 100) || session.amount;
|
|
1800
2092
|
opts.currency = session.currency?.toLowerCase();
|
|
1801
2093
|
}
|
|
@@ -1810,6 +2102,8 @@ function FloPayCheckout({
|
|
|
1810
2102
|
}),
|
|
1811
2103
|
[session, isLoading, loadError, currentMode]
|
|
1812
2104
|
);
|
|
2105
|
+
const shouldShowInterimButtons = Boolean(createSessionParams) && layout === "buttons" && (!flopay || !providerOptions);
|
|
2106
|
+
const shouldKeepDeferredInterimVisible = shouldShowInterimButtons && deferInlineSessionUntilCardClick;
|
|
1813
2107
|
if (isLoading) {
|
|
1814
2108
|
if (loadingNode) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: loadingNode });
|
|
1815
2109
|
if (layout === "buttons") {
|
|
@@ -1838,7 +2132,7 @@ function FloPayCheckout({
|
|
|
1838
2132
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes spin { to { transform: rotate(360deg); } }` })
|
|
1839
2133
|
] });
|
|
1840
2134
|
}
|
|
1841
|
-
if (loadError) {
|
|
2135
|
+
if (loadError && !shouldKeepDeferredInterimVisible) {
|
|
1842
2136
|
if (errorNode) return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: errorNode(loadError) });
|
|
1843
2137
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1844
2138
|
"div",
|
|
@@ -1853,17 +2147,23 @@ function FloPayCheckout({
|
|
|
1853
2147
|
}
|
|
1854
2148
|
);
|
|
1855
2149
|
}
|
|
1856
|
-
if (
|
|
2150
|
+
if (shouldShowInterimButtons) {
|
|
1857
2151
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
1858
2152
|
InterimButtonsView,
|
|
1859
2153
|
{
|
|
1860
2154
|
onButtonClick,
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2155
|
+
onCardButtonClick: deferInlineSessionUntilCardClick ? handleDeferredCardButtonClick : void 0,
|
|
2156
|
+
cardLoading: cardBootstrapPending,
|
|
2157
|
+
cardOpen: deferInlineSessionUntilCardClick ? deferredCardOpen : void 0,
|
|
2158
|
+
errorMessage: shouldKeepDeferredInterimVisible ? loadError?.message ?? null : null,
|
|
2159
|
+
showPayPal: deferInlineSessionUntilCardClick ? false : showPayPal,
|
|
2160
|
+
showApplePay: deferInlineSessionUntilCardClick ? false : showApplePay,
|
|
2161
|
+
showGooglePay: deferInlineSessionUntilCardClick ? false : showGooglePay,
|
|
1864
2162
|
buttonsTheme,
|
|
1865
2163
|
buttonsStyles,
|
|
1866
|
-
cardButtonContent
|
|
2164
|
+
cardButtonContent,
|
|
2165
|
+
cardBackButtonContent,
|
|
2166
|
+
cardTitleContent
|
|
1867
2167
|
}
|
|
1868
2168
|
) });
|
|
1869
2169
|
}
|
|
@@ -1940,10 +2240,11 @@ function FloPayCheckout({
|
|
|
1940
2240
|
userId: session?.customer?.id,
|
|
1941
2241
|
firstName: session?.customer?.firstName,
|
|
1942
2242
|
lastName: session?.customer?.lastName,
|
|
1943
|
-
totalAmount: session ? Math.round((0,
|
|
2243
|
+
totalAmount: session ? Math.round((0, import_shared6.buildCheckoutDisplayData)(session).total * 100) : 0,
|
|
1944
2244
|
currency: session?.currency?.toLowerCase() ?? "usd",
|
|
1945
2245
|
onComplete,
|
|
1946
2246
|
onError,
|
|
2247
|
+
onDecline,
|
|
1947
2248
|
showPayPal,
|
|
1948
2249
|
showApplePay,
|
|
1949
2250
|
showGooglePay,
|
|
@@ -1951,11 +2252,15 @@ function FloPayCheckout({
|
|
|
1951
2252
|
buttonsTheme,
|
|
1952
2253
|
buttonsStyles,
|
|
1953
2254
|
cardButtonContent,
|
|
2255
|
+
cardBackButtonContent,
|
|
2256
|
+
cardTitleContent,
|
|
1954
2257
|
onButtonClick,
|
|
2258
|
+
onBeforeButtonClick,
|
|
1955
2259
|
enableAVS,
|
|
1956
2260
|
avsLayout,
|
|
1957
2261
|
country: session?.customer?.country,
|
|
1958
2262
|
zip: session?.customer?.zip,
|
|
2263
|
+
initialCardOpen: deferredCardOpen,
|
|
1959
2264
|
submitLabel,
|
|
1960
2265
|
className
|
|
1961
2266
|
}
|
|
@@ -1988,22 +2293,36 @@ function SessionInjector({
|
|
|
1988
2293
|
}
|
|
1989
2294
|
function InterimButtonsView({
|
|
1990
2295
|
onButtonClick,
|
|
2296
|
+
onCardButtonClick,
|
|
2297
|
+
cardLoading = false,
|
|
2298
|
+
cardOpen,
|
|
2299
|
+
errorMessage,
|
|
1991
2300
|
showPayPal,
|
|
1992
2301
|
showApplePay,
|
|
1993
2302
|
showGooglePay,
|
|
1994
2303
|
buttonsTheme,
|
|
1995
2304
|
buttonsStyles: stylesOverride,
|
|
1996
|
-
cardButtonContent
|
|
2305
|
+
cardButtonContent,
|
|
2306
|
+
cardBackButtonContent,
|
|
2307
|
+
cardTitleContent
|
|
1997
2308
|
}) {
|
|
1998
2309
|
const [showCardForm, setShowCardForm] = (0, import_react7.useState)(false);
|
|
2310
|
+
const isCardOpenControlled = typeof cardOpen === "boolean";
|
|
2311
|
+
(0, import_react7.useEffect)(() => {
|
|
2312
|
+
if (isCardOpenControlled) {
|
|
2313
|
+
setShowCardForm(cardOpen);
|
|
2314
|
+
}
|
|
2315
|
+
}, [cardOpen, isCardOpenControlled]);
|
|
1999
2316
|
const bStyles = (0, import_react7.useMemo)(() => {
|
|
2000
|
-
const base = (0,
|
|
2317
|
+
const base = (0, import_shared6.resolveButtonsLayoutTheme)(buttonsTheme);
|
|
2001
2318
|
if (!stylesOverride) return base;
|
|
2002
2319
|
return {
|
|
2003
2320
|
...base,
|
|
2004
2321
|
...stylesOverride,
|
|
2005
2322
|
cardButton: { ...base.cardButton, ...stylesOverride.cardButton },
|
|
2006
2323
|
cardFormContainer: { ...base.cardFormContainer, ...stylesOverride.cardFormContainer },
|
|
2324
|
+
backButton: { ...base.backButton, ...stylesOverride.backButton },
|
|
2325
|
+
backButtonIcon: { ...base.backButtonIcon, ...stylesOverride.backButtonIcon },
|
|
2007
2326
|
submitButton: { ...base.submitButton, ...stylesOverride.submitButton },
|
|
2008
2327
|
title: { ...base.title, ...stylesOverride.title }
|
|
2009
2328
|
};
|
|
@@ -2017,6 +2336,8 @@ function InterimButtonsView({
|
|
|
2017
2336
|
if (showCardForm) {
|
|
2018
2337
|
const inputBorder = bStyles.cardInputBorder ?? "#e5e7eb";
|
|
2019
2338
|
const inputBg = bStyles.cardInputBackground ?? "white";
|
|
2339
|
+
const hideBackButtonLabel = isEmptySlotContent(cardBackButtonContent);
|
|
2340
|
+
const hideTitle = isEmptySlotContent(cardTitleContent);
|
|
2020
2341
|
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
|
|
2021
2342
|
backgroundColor: bStyles.cardFormContainer?.backgroundColor ?? "white",
|
|
2022
2343
|
borderRadius: "8px",
|
|
@@ -2029,19 +2350,26 @@ function InterimButtonsView({
|
|
|
2029
2350
|
"button",
|
|
2030
2351
|
{
|
|
2031
2352
|
type: "button",
|
|
2032
|
-
onClick: () =>
|
|
2353
|
+
onClick: () => {
|
|
2354
|
+
if (!isCardOpenControlled) {
|
|
2355
|
+
setShowCardForm(false);
|
|
2356
|
+
}
|
|
2357
|
+
},
|
|
2358
|
+
"aria-label": "Back to payment methods",
|
|
2359
|
+
disabled: isCardOpenControlled || cardLoading,
|
|
2033
2360
|
style: {
|
|
2034
2361
|
display: "inline-flex",
|
|
2035
2362
|
alignItems: "center",
|
|
2036
|
-
gap: "0.5rem",
|
|
2363
|
+
gap: hideBackButtonLabel ? 0 : "0.5rem",
|
|
2037
2364
|
background: "none",
|
|
2038
2365
|
border: "none",
|
|
2039
|
-
cursor: "pointer",
|
|
2040
2366
|
color: "#4b5563",
|
|
2041
2367
|
fontSize: "0.85rem",
|
|
2042
2368
|
fontWeight: 500,
|
|
2043
2369
|
padding: 0,
|
|
2044
2370
|
flexShrink: 0,
|
|
2371
|
+
opacity: isCardOpenControlled || cardLoading ? 0.6 : 1,
|
|
2372
|
+
cursor: isCardOpenControlled || cardLoading ? "not-allowed" : "pointer",
|
|
2045
2373
|
...bStyles.backButton
|
|
2046
2374
|
},
|
|
2047
2375
|
children: [
|
|
@@ -2055,11 +2383,11 @@ function InterimButtonsView({
|
|
|
2055
2383
|
backgroundColor: "#f3f4f6",
|
|
2056
2384
|
...bStyles.backButtonIcon
|
|
2057
2385
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M15 18l-6-6 6-6" }) }) }),
|
|
2058
|
-
|
|
2386
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(BackButtonContentSlot, { content: cardBackButtonContent })
|
|
2059
2387
|
]
|
|
2060
2388
|
}
|
|
2061
2389
|
),
|
|
2062
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
|
|
2390
|
+
hideTitle ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { flex: 1 } }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: {
|
|
2063
2391
|
flex: 1,
|
|
2064
2392
|
textAlign: "center",
|
|
2065
2393
|
fontWeight: 600,
|
|
@@ -2067,7 +2395,7 @@ function InterimButtonsView({
|
|
|
2067
2395
|
color: "#262833",
|
|
2068
2396
|
paddingRight: 80,
|
|
2069
2397
|
...bStyles.title
|
|
2070
|
-
}, children:
|
|
2398
|
+
}, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(TitleContentSlot, { content: cardTitleContent }) })
|
|
2071
2399
|
] }),
|
|
2072
2400
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTopLeftRadius: 8, borderTopRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: { width: "60%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
|
|
2073
2401
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { display: "flex" }, children: [
|
|
@@ -2101,10 +2429,16 @@ function InterimButtonsView({
|
|
|
2101
2429
|
"button",
|
|
2102
2430
|
{
|
|
2103
2431
|
type: "button",
|
|
2104
|
-
onClick: () => {
|
|
2432
|
+
onClick: async () => {
|
|
2433
|
+
if (cardLoading) return;
|
|
2434
|
+
if (onCardButtonClick) {
|
|
2435
|
+
await onCardButtonClick();
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2105
2438
|
onButtonClick?.("card");
|
|
2106
2439
|
setShowCardForm(true);
|
|
2107
2440
|
},
|
|
2441
|
+
disabled: cardLoading,
|
|
2108
2442
|
style: {
|
|
2109
2443
|
width: "100%",
|
|
2110
2444
|
padding: "0.9rem 1rem",
|
|
@@ -2114,7 +2448,7 @@ function InterimButtonsView({
|
|
|
2114
2448
|
borderRadius: "8px",
|
|
2115
2449
|
fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
|
|
2116
2450
|
fontWeight: 600,
|
|
2117
|
-
cursor: "pointer",
|
|
2451
|
+
cursor: cardLoading ? "not-allowed" : "pointer",
|
|
2118
2452
|
display: "flex",
|
|
2119
2453
|
alignItems: "center",
|
|
2120
2454
|
justifyContent: "center",
|
|
@@ -2122,6 +2456,7 @@ function InterimButtonsView({
|
|
|
2122
2456
|
boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
|
|
2123
2457
|
transition: "transform 0.1s",
|
|
2124
2458
|
position: "relative",
|
|
2459
|
+
opacity: cardLoading ? 0.6 : 1,
|
|
2125
2460
|
...bStyles.cardButton
|
|
2126
2461
|
},
|
|
2127
2462
|
onMouseDown: (e) => {
|
|
@@ -2133,12 +2468,29 @@ function InterimButtonsView({
|
|
|
2133
2468
|
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(CardButtonContentSlot, { content: cardButtonContent })
|
|
2134
2469
|
}
|
|
2135
2470
|
),
|
|
2471
|
+
errorMessage && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: {
|
|
2472
|
+
margin: "0.25rem 0",
|
|
2473
|
+
padding: "0.625rem 0.875rem",
|
|
2474
|
+
background: "#FEF2F2",
|
|
2475
|
+
border: "1px solid #FECACA",
|
|
2476
|
+
borderRadius: "8px",
|
|
2477
|
+
color: "#991B1B",
|
|
2478
|
+
fontSize: "0.85rem",
|
|
2479
|
+
fontWeight: 600,
|
|
2480
|
+
display: "flex",
|
|
2481
|
+
alignItems: "center",
|
|
2482
|
+
gap: "0.5rem",
|
|
2483
|
+
...bStyles.errorBanner
|
|
2484
|
+
}, children: [
|
|
2485
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { d: "M12 9v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z", stroke: "#DC2626", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }),
|
|
2486
|
+
errorMessage
|
|
2487
|
+
] }),
|
|
2136
2488
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("style", { children: `@keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
|
|
2137
2489
|
] });
|
|
2138
2490
|
}
|
|
2139
2491
|
|
|
2140
2492
|
// src/checkout-form.tsx
|
|
2141
|
-
var
|
|
2493
|
+
var import_shared7 = require("@flopay/shared");
|
|
2142
2494
|
var import_react8 = require("react");
|
|
2143
2495
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
2144
2496
|
var WALLET_RESUME_KEY2 = "flopay_wallet_resume";
|
|
@@ -2154,6 +2506,7 @@ function CheckoutFormInner({
|
|
|
2154
2506
|
userId,
|
|
2155
2507
|
onComplete,
|
|
2156
2508
|
onError,
|
|
2509
|
+
onDecline,
|
|
2157
2510
|
onTokenizedBody,
|
|
2158
2511
|
layout = "auto",
|
|
2159
2512
|
submitLabel = "Pay",
|
|
@@ -2185,6 +2538,12 @@ function CheckoutFormInner({
|
|
|
2185
2538
|
},
|
|
2186
2539
|
[onErrorChange]
|
|
2187
2540
|
);
|
|
2541
|
+
const emitDecline = (0, import_react8.useCallback)(
|
|
2542
|
+
(input, overrides) => {
|
|
2543
|
+
onDecline?.(buildDeclineEvent("card", input, overrides));
|
|
2544
|
+
},
|
|
2545
|
+
[onDecline]
|
|
2546
|
+
);
|
|
2188
2547
|
const processPaymentInternal = (0, import_react8.useCallback)(
|
|
2189
2548
|
async (tokenizedBody) => {
|
|
2190
2549
|
setProcessing(true);
|
|
@@ -2231,6 +2590,7 @@ function CheckoutFormInner({
|
|
|
2231
2590
|
if (result.error) {
|
|
2232
2591
|
updateError(result.error.message);
|
|
2233
2592
|
onError?.(result.error);
|
|
2593
|
+
emitDecline(result.error);
|
|
2234
2594
|
return;
|
|
2235
2595
|
}
|
|
2236
2596
|
if (result.status === "succeeded" || result.status === "processing") {
|
|
@@ -2265,13 +2625,17 @@ function CheckoutFormInner({
|
|
|
2265
2625
|
}
|
|
2266
2626
|
const errorMessage = json?.message ?? "Payment failed. Please try again.";
|
|
2267
2627
|
updateError(errorMessage);
|
|
2628
|
+
emitDecline(errorMessage, {
|
|
2629
|
+
code: json?.code,
|
|
2630
|
+
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
2631
|
+
});
|
|
2268
2632
|
} catch (err) {
|
|
2269
2633
|
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
2270
2634
|
} finally {
|
|
2271
2635
|
setProcessing(false);
|
|
2272
2636
|
}
|
|
2273
2637
|
},
|
|
2274
|
-
[baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError]
|
|
2638
|
+
[baseUrl, sessionId, userId, email, firstName, lastName, chv, flopay, onComplete, onError, updateError, emitDecline]
|
|
2275
2639
|
);
|
|
2276
2640
|
const dispatchTokenizedBody = (0, import_react8.useCallback)(
|
|
2277
2641
|
(tokenizedBody) => {
|
|
@@ -2295,6 +2659,7 @@ function CheckoutFormInner({
|
|
|
2295
2659
|
if (result.error) {
|
|
2296
2660
|
updateError(result.error.message);
|
|
2297
2661
|
onError?.(result.error);
|
|
2662
|
+
emitDecline(result.error);
|
|
2298
2663
|
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
2299
2664
|
dispatchTokenizedBody({
|
|
2300
2665
|
id: result.paymentIntentId,
|
|
@@ -2308,7 +2673,7 @@ function CheckoutFormInner({
|
|
|
2308
2673
|
setIs3DSActive(false);
|
|
2309
2674
|
}
|
|
2310
2675
|
}
|
|
2311
|
-
}), [flopay, dispatchTokenizedBody, onError, updateError]);
|
|
2676
|
+
}), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
|
|
2312
2677
|
(0, import_react8.useEffect)(() => {
|
|
2313
2678
|
if (typeof window === "undefined") return;
|
|
2314
2679
|
const stored = localStorage.getItem(WALLET_RESUME_KEY2);
|
|
@@ -2346,7 +2711,7 @@ function CheckoutFormInner({
|
|
|
2346
2711
|
return;
|
|
2347
2712
|
}
|
|
2348
2713
|
if (!sessionId || !email) {
|
|
2349
|
-
throw new
|
|
2714
|
+
throw new import_shared7.FloPayError("Missing sessionId or email", "validation_error");
|
|
2350
2715
|
}
|
|
2351
2716
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
2352
2717
|
method: "POST",
|
|
@@ -2358,16 +2723,18 @@ function CheckoutFormInner({
|
|
|
2358
2723
|
isPaypal: false
|
|
2359
2724
|
})
|
|
2360
2725
|
});
|
|
2361
|
-
if (!intentResponse.ok) throw new
|
|
2726
|
+
if (!intentResponse.ok) throw new import_shared7.FloPayError("Failed to create payment intent", "api_error");
|
|
2362
2727
|
const intentJson = await intentResponse.json();
|
|
2363
2728
|
const intentClientSecret = intentJson.data?.id;
|
|
2364
|
-
if (!intentClientSecret) throw new
|
|
2729
|
+
if (!intentClientSecret) throw new import_shared7.FloPayError("No client_secret in payment intent response", "api_error");
|
|
2365
2730
|
const confirmResult = await flopay.confirmCardPayment({
|
|
2366
2731
|
clientSecret: intentClientSecret,
|
|
2367
2732
|
paymentMethodId: pmResult.paymentMethodId
|
|
2368
2733
|
});
|
|
2369
2734
|
if (confirmResult.error) {
|
|
2370
2735
|
updateError(confirmResult.error.message);
|
|
2736
|
+
onError?.(confirmResult.error);
|
|
2737
|
+
emitDecline(confirmResult.error);
|
|
2371
2738
|
return;
|
|
2372
2739
|
}
|
|
2373
2740
|
dispatchTokenizedBody({
|
|
@@ -2384,7 +2751,7 @@ function CheckoutFormInner({
|
|
|
2384
2751
|
}
|
|
2385
2752
|
}
|
|
2386
2753
|
},
|
|
2387
|
-
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError]
|
|
2754
|
+
[flopay, elements, isSubmitting, sessionId, email, baseUrl, isSelfContained, dispatchTokenizedBody, onError, updateError, emitDecline]
|
|
2388
2755
|
);
|
|
2389
2756
|
const isReady = flopay !== null && elements !== null;
|
|
2390
2757
|
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
@@ -2416,7 +2783,7 @@ function CheckoutFormInner({
|
|
|
2416
2783
|
}
|
|
2417
2784
|
|
|
2418
2785
|
// src/paypal-button.tsx
|
|
2419
|
-
var
|
|
2786
|
+
var import_shared8 = require("@flopay/shared");
|
|
2420
2787
|
var import_react9 = require("react");
|
|
2421
2788
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
2422
2789
|
function PayPalButton({
|
|
@@ -2536,7 +2903,7 @@ function PayPalButton({
|
|
|
2536
2903
|
setSubmitting(true);
|
|
2537
2904
|
onErrorChange?.(null);
|
|
2538
2905
|
if (!sessionId || !email) {
|
|
2539
|
-
throw new
|
|
2906
|
+
throw new import_shared8.FloPayError("Missing sessionId or email for PayPal payment", "validation_error");
|
|
2540
2907
|
}
|
|
2541
2908
|
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
2542
2909
|
method: "POST",
|
|
@@ -2548,10 +2915,10 @@ function PayPalButton({
|
|
|
2548
2915
|
isPaypal: "true"
|
|
2549
2916
|
})
|
|
2550
2917
|
});
|
|
2551
|
-
if (!intentResponse.ok) throw new
|
|
2918
|
+
if (!intentResponse.ok) throw new import_shared8.FloPayError("Failed to create payment intent", "api_error");
|
|
2552
2919
|
const intentJson = await intentResponse.json();
|
|
2553
2920
|
const intentClientSecret = intentJson.data?.id;
|
|
2554
|
-
if (!intentClientSecret) throw new
|
|
2921
|
+
if (!intentClientSecret) throw new import_shared8.FloPayError("No client_secret in response", "api_error");
|
|
2555
2922
|
const result = await flopay.confirmPayment({
|
|
2556
2923
|
clientSecret: intentClientSecret,
|
|
2557
2924
|
returnUrl: window.location.href
|