@flopay/react 1.4.0 → 1.4.2
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 +100 -165
- package/dist/index.cjs +729 -1270
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -180
- package/dist/index.d.ts +71 -180
- package/dist/index.mjs +949 -1484
- package/dist/index.mjs.map +1 -1
- package/dist/payment-logos/alipay.svg +1 -0
- package/dist/payment-logos/bancontact.svg +1 -0
- package/dist/payment-logos/blik.svg +1 -0
- package/dist/payment-logos/eps.svg +1 -0
- package/dist/payment-logos/giropay.svg +1 -0
- package/dist/payment-logos/ideal.svg +1 -0
- package/dist/payment-logos/klarna.svg +1 -0
- package/dist/payment-logos/p24.svg +1 -0
- package/dist/payment-logos/sepa_debit.svg +1 -0
- package/dist/payment-logos/wechat_pay.svg +1 -0
- package/package.json +4 -3
package/dist/index.mjs
CHANGED
|
@@ -193,7 +193,7 @@ function FloPayProvider({
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
// src/flopay-checkout.tsx
|
|
196
|
-
import React8, { useCallback as useCallback4, useEffect as
|
|
196
|
+
import React8, { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo4, useRef as useRef5, useState as useState4 } from "react";
|
|
197
197
|
import { PaymentAPI as PaymentAPI4 } from "@flopay/js";
|
|
198
198
|
import { SDK_VERSION as SDK_VERSION2, FloPayError as FloPayError5, resolveBillingApiUrl as resolveBillingApiUrl3, buildCheckoutDisplayData, resolveButtonsLayoutTheme as resolveButtonsLayoutTheme2, resolveTheme as resolveTheme2 } from "@flopay/shared";
|
|
199
199
|
|
|
@@ -264,73 +264,10 @@ function TitleContentSlot({
|
|
|
264
264
|
return /* @__PURE__ */ jsx2(Fragment, { children: content === void 0 ? /* @__PURE__ */ jsx2(DefaultTitleContent, {}) : content });
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
// src/elements.tsx
|
|
268
|
-
import { useEffect as useEffect2, useRef as useRef2, useContext } from "react";
|
|
269
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
270
|
-
function createElementComponent(elementType, displayName) {
|
|
271
|
-
function ElementComponent({
|
|
272
|
-
className,
|
|
273
|
-
id,
|
|
274
|
-
style,
|
|
275
|
-
options,
|
|
276
|
-
onChange,
|
|
277
|
-
onReady,
|
|
278
|
-
onFocus,
|
|
279
|
-
onBlur,
|
|
280
|
-
onEscape
|
|
281
|
-
}) {
|
|
282
|
-
const containerRef = useRef2(null);
|
|
283
|
-
const elementRef = useRef2(null);
|
|
284
|
-
const { elements, reportInteractive } = useContext(FloPayContext);
|
|
285
|
-
useEffect2(() => {
|
|
286
|
-
if (!elements || !containerRef.current) return;
|
|
287
|
-
let mounted = true;
|
|
288
|
-
(async () => {
|
|
289
|
-
let element = elements.getElement(elementType);
|
|
290
|
-
if (!element) {
|
|
291
|
-
element = await elements.create(elementType, options);
|
|
292
|
-
}
|
|
293
|
-
if (!mounted || !containerRef.current) {
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
element.mount(containerRef.current);
|
|
297
|
-
elementRef.current = element;
|
|
298
|
-
if (onChange) element.on("change", onChange);
|
|
299
|
-
element.on("ready", () => {
|
|
300
|
-
reportInteractive?.();
|
|
301
|
-
onReady?.();
|
|
302
|
-
});
|
|
303
|
-
if (onFocus) element.on("focus", onFocus);
|
|
304
|
-
if (onBlur) element.on("blur", onBlur);
|
|
305
|
-
if (onEscape) element.on("escape", onEscape);
|
|
306
|
-
})();
|
|
307
|
-
return () => {
|
|
308
|
-
mounted = false;
|
|
309
|
-
if (elementRef.current) {
|
|
310
|
-
try {
|
|
311
|
-
elementRef.current.unmount();
|
|
312
|
-
} catch {
|
|
313
|
-
}
|
|
314
|
-
elementRef.current = null;
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
}, [elements, reportInteractive]);
|
|
318
|
-
return /* @__PURE__ */ jsx3("div", { ref: containerRef, className, id, style });
|
|
319
|
-
}
|
|
320
|
-
ElementComponent.displayName = displayName;
|
|
321
|
-
return ElementComponent;
|
|
322
|
-
}
|
|
323
|
-
var PaymentElement = createElementComponent("payment", "PaymentElement");
|
|
324
|
-
var CardElement = createElementComponent("card", "CardElement");
|
|
325
|
-
var CardNumberElement = createElementComponent("cardNumber", "CardNumberElement");
|
|
326
|
-
var CardExpiryElement = createElementComponent("cardExpiry", "CardExpiryElement");
|
|
327
|
-
var CardCvcElement = createElementComponent("cardCvc", "CardCvcElement");
|
|
328
|
-
var AddressElement = createElementComponent("address", "AddressElement");
|
|
329
|
-
|
|
330
267
|
// src/split-card-form.tsx
|
|
331
268
|
import {
|
|
332
269
|
ExpressCheckoutElement,
|
|
333
|
-
PaymentElement
|
|
270
|
+
PaymentElement,
|
|
334
271
|
Elements as StripeElements,
|
|
335
272
|
useElements as useStripeElements,
|
|
336
273
|
useStripe as useStripeRaw
|
|
@@ -358,8 +295,8 @@ import {
|
|
|
358
295
|
import { PaymentAPI as PaymentAPI2 } from "@flopay/js";
|
|
359
296
|
|
|
360
297
|
// src/vault-card-fields.tsx
|
|
361
|
-
import { useEffect as
|
|
362
|
-
import { jsx as
|
|
298
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
299
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
363
300
|
function VaultCardFields({
|
|
364
301
|
capture,
|
|
365
302
|
html,
|
|
@@ -371,16 +308,16 @@ function VaultCardFields({
|
|
|
371
308
|
onError,
|
|
372
309
|
onValidation
|
|
373
310
|
}) {
|
|
374
|
-
const containerRef =
|
|
375
|
-
const onReadyRef =
|
|
376
|
-
const onErrorRef =
|
|
377
|
-
const onValidationRef =
|
|
311
|
+
const containerRef = useRef2(null);
|
|
312
|
+
const onReadyRef = useRef2(onReady);
|
|
313
|
+
const onErrorRef = useRef2(onError);
|
|
314
|
+
const onValidationRef = useRef2(onValidation);
|
|
378
315
|
onReadyRef.current = onReady;
|
|
379
316
|
onErrorRef.current = onError;
|
|
380
317
|
onValidationRef.current = onValidation;
|
|
381
|
-
const themeRef =
|
|
318
|
+
const themeRef = useRef2(theme);
|
|
382
319
|
themeRef.current = theme;
|
|
383
|
-
|
|
320
|
+
useEffect2(() => {
|
|
384
321
|
const el = containerRef.current;
|
|
385
322
|
if (!el) return;
|
|
386
323
|
let active = true;
|
|
@@ -422,41 +359,98 @@ function VaultCardFields({
|
|
|
422
359
|
capture.unmount();
|
|
423
360
|
};
|
|
424
361
|
}, [capture, html, messageToken, expectedOrigin]);
|
|
425
|
-
|
|
362
|
+
useEffect2(() => {
|
|
426
363
|
if (theme) capture.applyTheme?.(theme);
|
|
427
364
|
}, [capture, theme]);
|
|
428
|
-
return /* @__PURE__ */
|
|
365
|
+
return /* @__PURE__ */ jsx3("div", { ref: containerRef, "data-testid": "flopay-vault-card-fields", style: containerStyle });
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// src/error-banner.tsx
|
|
369
|
+
import "react";
|
|
370
|
+
import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
371
|
+
function ErrorBanner({
|
|
372
|
+
children,
|
|
373
|
+
margin,
|
|
374
|
+
icon = true,
|
|
375
|
+
styleOverride
|
|
376
|
+
}) {
|
|
377
|
+
return /* @__PURE__ */ jsxs2(
|
|
378
|
+
"div",
|
|
379
|
+
{
|
|
380
|
+
role: "alert",
|
|
381
|
+
"data-testid": "flopay-error",
|
|
382
|
+
style: {
|
|
383
|
+
margin,
|
|
384
|
+
padding: "0.625rem 0.875rem",
|
|
385
|
+
background: "#FEF2F2",
|
|
386
|
+
border: "1px solid #FECACA",
|
|
387
|
+
borderRadius: "8px",
|
|
388
|
+
color: "#991B1B",
|
|
389
|
+
fontSize: "0.85rem",
|
|
390
|
+
fontWeight: 600,
|
|
391
|
+
...icon ? { display: "flex", alignItems: "center", gap: "0.5rem" } : {},
|
|
392
|
+
...styleOverride ?? {}
|
|
393
|
+
},
|
|
394
|
+
children: [
|
|
395
|
+
icon && /* @__PURE__ */ jsx4("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx4(
|
|
396
|
+
"path",
|
|
397
|
+
{
|
|
398
|
+
d: "M12 9v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z",
|
|
399
|
+
stroke: "#DC2626",
|
|
400
|
+
strokeWidth: "2",
|
|
401
|
+
strokeLinecap: "round",
|
|
402
|
+
strokeLinejoin: "round"
|
|
403
|
+
}
|
|
404
|
+
) }),
|
|
405
|
+
children
|
|
406
|
+
]
|
|
407
|
+
}
|
|
408
|
+
);
|
|
429
409
|
}
|
|
430
410
|
|
|
411
|
+
// src/payment-logos.generated.ts
|
|
412
|
+
var PAYMENT_METHOD_LOGO_URLS = {
|
|
413
|
+
"alipay": new URL("./payment-logos/alipay.svg", import.meta.url).href,
|
|
414
|
+
"bancontact": new URL("./payment-logos/bancontact.svg", import.meta.url).href,
|
|
415
|
+
"blik": new URL("./payment-logos/blik.svg", import.meta.url).href,
|
|
416
|
+
"eps": new URL("./payment-logos/eps.svg", import.meta.url).href,
|
|
417
|
+
"giropay": new URL("./payment-logos/giropay.svg", import.meta.url).href,
|
|
418
|
+
"ideal": new URL("./payment-logos/ideal.svg", import.meta.url).href,
|
|
419
|
+
"klarna": new URL("./payment-logos/klarna.svg", import.meta.url).href,
|
|
420
|
+
"p24": new URL("./payment-logos/p24.svg", import.meta.url).href,
|
|
421
|
+
"sepa_debit": new URL("./payment-logos/sepa_debit.svg", import.meta.url).href,
|
|
422
|
+
"wechat_pay": new URL("./payment-logos/wechat_pay.svg", import.meta.url).href
|
|
423
|
+
};
|
|
424
|
+
|
|
431
425
|
// src/split-card-form.tsx
|
|
432
|
-
import React7, {
|
|
426
|
+
import React7, { useCallback as useCallback3, useContext as useContext2, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState3 } from "react";
|
|
433
427
|
|
|
434
428
|
// src/hooks.ts
|
|
435
|
-
import { useContext
|
|
429
|
+
import { useContext } from "react";
|
|
436
430
|
import { resolveBillingApiUrl as resolveBillingApiUrl2 } from "@flopay/shared";
|
|
437
431
|
function useFloPay() {
|
|
438
|
-
const ctx =
|
|
432
|
+
const ctx = useContext(FloPayContext);
|
|
439
433
|
return ctx.flopay;
|
|
440
434
|
}
|
|
441
435
|
function usePayPalFloPay() {
|
|
442
|
-
const ctx =
|
|
436
|
+
const ctx = useContext(FloPayContext);
|
|
443
437
|
return ctx.paypalFlopay ?? null;
|
|
444
438
|
}
|
|
445
439
|
function useElements() {
|
|
446
|
-
const ctx =
|
|
440
|
+
const ctx = useContext(FloPayContext);
|
|
447
441
|
return ctx.elements;
|
|
448
442
|
}
|
|
449
443
|
function useCheckout() {
|
|
450
|
-
return
|
|
444
|
+
return useContext(CheckoutContext);
|
|
451
445
|
}
|
|
452
446
|
function useBillingApiUrl() {
|
|
453
|
-
const ctx =
|
|
447
|
+
const ctx = useContext(FloPayContext);
|
|
454
448
|
return ctx.billingApiUrl || resolveBillingApiUrl2();
|
|
455
449
|
}
|
|
456
450
|
|
|
457
451
|
// src/processing-overlay.tsx
|
|
458
452
|
import "react";
|
|
459
|
-
import { jsx as jsx5, jsxs as
|
|
453
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
460
454
|
var PROCESSING_OVERLAY_SUCCESS_DELAY_MS = 1200;
|
|
461
455
|
var PROCESSING_OVERLAY_ERROR_DELAY_MS = 1500;
|
|
462
456
|
function ProcessingOverlay({
|
|
@@ -480,7 +474,7 @@ function ProcessingOverlay({
|
|
|
480
474
|
zIndex: 1e3,
|
|
481
475
|
backdropFilter: "blur(2px)"
|
|
482
476
|
},
|
|
483
|
-
children: /* @__PURE__ */
|
|
477
|
+
children: /* @__PURE__ */ jsxs3(
|
|
484
478
|
"div",
|
|
485
479
|
{
|
|
486
480
|
style: {
|
|
@@ -496,8 +490,8 @@ function ProcessingOverlay({
|
|
|
496
490
|
gap: 16
|
|
497
491
|
},
|
|
498
492
|
children: [
|
|
499
|
-
/* @__PURE__ */
|
|
500
|
-
status === "processing" && /* @__PURE__ */
|
|
493
|
+
/* @__PURE__ */ jsxs3("div", { style: { width: 48, height: 48, position: "relative" }, children: [
|
|
494
|
+
status === "processing" && /* @__PURE__ */ jsxs3(
|
|
501
495
|
"svg",
|
|
502
496
|
{
|
|
503
497
|
width: "48",
|
|
@@ -512,7 +506,7 @@ function ProcessingOverlay({
|
|
|
512
506
|
]
|
|
513
507
|
}
|
|
514
508
|
),
|
|
515
|
-
status === "success" && /* @__PURE__ */ jsx5("div", { style: { animation: "flopay-pop 0.4s cubic-bezier(0.34,1.56,0.64,1)" }, children: /* @__PURE__ */
|
|
509
|
+
status === "success" && /* @__PURE__ */ jsx5("div", { style: { animation: "flopay-pop 0.4s cubic-bezier(0.34,1.56,0.64,1)" }, children: /* @__PURE__ */ jsxs3("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
516
510
|
/* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "11", fill: "#22c55e" }),
|
|
517
511
|
/* @__PURE__ */ jsx5(
|
|
518
512
|
"path",
|
|
@@ -530,7 +524,7 @@ function ProcessingOverlay({
|
|
|
530
524
|
}
|
|
531
525
|
)
|
|
532
526
|
] }) }),
|
|
533
|
-
status === "error" && /* @__PURE__ */ jsx5("div", { style: { animation: "flopay-shake 0.4s ease" }, children: /* @__PURE__ */
|
|
527
|
+
status === "error" && /* @__PURE__ */ jsx5("div", { style: { animation: "flopay-shake 0.4s ease" }, children: /* @__PURE__ */ jsxs3("svg", { width: "48", height: "48", viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
|
|
534
528
|
/* @__PURE__ */ jsx5("circle", { cx: "12", cy: "12", r: "11", fill: "#ef4444" }),
|
|
535
529
|
/* @__PURE__ */ jsx5(
|
|
536
530
|
"path",
|
|
@@ -548,7 +542,7 @@ function ProcessingOverlay({
|
|
|
548
542
|
)
|
|
549
543
|
] }) })
|
|
550
544
|
] }),
|
|
551
|
-
/* @__PURE__ */
|
|
545
|
+
/* @__PURE__ */ jsxs3(
|
|
552
546
|
"span",
|
|
553
547
|
{
|
|
554
548
|
style: {
|
|
@@ -717,13 +711,6 @@ function buildDeclineEvent(method, input, overrides) {
|
|
|
717
711
|
...declineCode ? { declineCode } : {}
|
|
718
712
|
};
|
|
719
713
|
}
|
|
720
|
-
function isRecord(value) {
|
|
721
|
-
return typeof value === "object" && value !== null;
|
|
722
|
-
}
|
|
723
|
-
function readString(payload, key) {
|
|
724
|
-
const value = payload?.[key];
|
|
725
|
-
return typeof value === "string" && value.trim() ? value : void 0;
|
|
726
|
-
}
|
|
727
714
|
var FRIENDLY_MESSAGE_OVERRIDES = [
|
|
728
715
|
{
|
|
729
716
|
match: /^paypal authorization required\.?$/i,
|
|
@@ -739,20 +726,8 @@ function applyFriendlyMessageOverride(message) {
|
|
|
739
726
|
}
|
|
740
727
|
return message;
|
|
741
728
|
}
|
|
742
|
-
function
|
|
743
|
-
|
|
744
|
-
const rawMessage = readString(payload, "message") ?? readString(nestedError, "message") ?? fallbackMessage;
|
|
745
|
-
const message = applyFriendlyMessageOverride(rawMessage) ?? rawMessage;
|
|
746
|
-
const code = readString(payload, "code") ?? readString(payload, "gatewayErrorCode") ?? readString(nestedError, "code");
|
|
747
|
-
const declineCode = readString(payload, "declineCode") ?? readString(payload, "gatewayDeclineReason") ?? readString(payload, "decline_code") ?? readString(nestedError, "decline_code");
|
|
748
|
-
return new FloPayError(message, "api_error", {
|
|
749
|
-
...code ? { code } : {},
|
|
750
|
-
...declineCode ? { declineCode } : {}
|
|
751
|
-
});
|
|
752
|
-
}
|
|
753
|
-
async function buildFloPayApiErrorFromResponse(response, fallbackMessage) {
|
|
754
|
-
const payload = await response.json().catch(() => null);
|
|
755
|
-
return buildFloPayApiError(payload, fallbackMessage);
|
|
729
|
+
function isSuccessfulPaymentIntentStatus(status) {
|
|
730
|
+
return status === "succeeded" || status === "requires_capture" || status === "processing";
|
|
756
731
|
}
|
|
757
732
|
function mapPayPalIntentStatusToPaymentResult(status) {
|
|
758
733
|
if (status === "succeeded") {
|
|
@@ -763,41 +738,6 @@ function mapPayPalIntentStatusToPaymentResult(status) {
|
|
|
763
738
|
}
|
|
764
739
|
return "failed";
|
|
765
740
|
}
|
|
766
|
-
function resolvePaymentIntentPaymentMethodId(paymentIntent) {
|
|
767
|
-
const paymentMethod = paymentIntent?.payment_method;
|
|
768
|
-
if (typeof paymentMethod === "string" && paymentMethod.startsWith("pm_")) {
|
|
769
|
-
return paymentMethod;
|
|
770
|
-
}
|
|
771
|
-
if (paymentMethod && typeof paymentMethod === "object" && typeof paymentMethod.id === "string") {
|
|
772
|
-
return paymentMethod.id;
|
|
773
|
-
}
|
|
774
|
-
return void 0;
|
|
775
|
-
}
|
|
776
|
-
async function retrievePaymentIntentFromProvider(provider, clientSecret) {
|
|
777
|
-
const retriever = provider;
|
|
778
|
-
if (isSetupIntentClientSecret(clientSecret)) {
|
|
779
|
-
if (!retriever?.retrieveSetupIntent) {
|
|
780
|
-
return null;
|
|
781
|
-
}
|
|
782
|
-
const { setupIntent, error: error2 } = await retriever.retrieveSetupIntent(clientSecret);
|
|
783
|
-
if (error2) {
|
|
784
|
-
throw new FloPayError(error2.message ?? "Failed to retrieve setup intent.", "api_error", {
|
|
785
|
-
...error2.code ? { code: error2.code } : {}
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
return setupIntent ?? null;
|
|
789
|
-
}
|
|
790
|
-
if (!retriever?.retrievePaymentIntent) {
|
|
791
|
-
return null;
|
|
792
|
-
}
|
|
793
|
-
const { paymentIntent, error } = await retriever.retrievePaymentIntent(clientSecret);
|
|
794
|
-
if (error) {
|
|
795
|
-
throw new FloPayError(error.message ?? "Failed to retrieve payment intent.", "api_error", {
|
|
796
|
-
...error.code ? { code: error.code } : {}
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
return paymentIntent ?? null;
|
|
800
|
-
}
|
|
801
741
|
function resolveWalletElementsMode(amountInMinorUnits) {
|
|
802
742
|
if (typeof amountInMinorUnits !== "number" || !Number.isFinite(amountInMinorUnits) || amountInMinorUnits <= 0) {
|
|
803
743
|
return { mode: "setup" };
|
|
@@ -866,10 +806,15 @@ function isInAppBrowser(userAgent) {
|
|
|
866
806
|
}
|
|
867
807
|
|
|
868
808
|
// src/direct-paypal-button.tsx
|
|
869
|
-
import { useCallback as useCallback2, useEffect as
|
|
809
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
|
|
870
810
|
import { loadScript } from "@paypal/paypal-js";
|
|
871
811
|
import { PaymentAPI } from "@flopay/js";
|
|
872
|
-
import {
|
|
812
|
+
import {
|
|
813
|
+
FloPayError as FloPayError2,
|
|
814
|
+
SDK_VERSION,
|
|
815
|
+
generateIdempotencyKey,
|
|
816
|
+
normalizeGatewayEnvironment
|
|
817
|
+
} from "@flopay/shared";
|
|
873
818
|
|
|
874
819
|
// src/merchant-callback.ts
|
|
875
820
|
function invokeMerchantCallback(callback) {
|
|
@@ -921,8 +866,14 @@ function isPopupBlockedError(err) {
|
|
|
921
866
|
}
|
|
922
867
|
|
|
923
868
|
// src/direct-paypal-button.tsx
|
|
924
|
-
import { jsx as jsx6, jsxs as
|
|
869
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
925
870
|
var DEFAULT_BUTTON_HEIGHT = 45;
|
|
871
|
+
var PAYPAL_INIT_RETRY_DELAY_MS = 1e3;
|
|
872
|
+
var PAYPAL_RENDER_WATCHDOG_MS = 11e3;
|
|
873
|
+
var PAYPAL_INIT_ACKNOWLEDGEMENT_HOSTS = [
|
|
874
|
+
"https://www.paypal.com",
|
|
875
|
+
"https://www.sandbox.paypal.com"
|
|
876
|
+
];
|
|
926
877
|
var DIRECT_PAYPAL_RECOVERY_MESSAGE = "We couldn't open PayPal. Try again or choose another payment method.";
|
|
927
878
|
var DIRECT_PAYPAL_RECOVERY_ACTION_STYLE = {
|
|
928
879
|
width: "100%",
|
|
@@ -937,6 +888,9 @@ var DIRECT_PAYPAL_RECOVERY_ACTION_STYLE = {
|
|
|
937
888
|
fontWeight: 700,
|
|
938
889
|
cursor: "pointer"
|
|
939
890
|
};
|
|
891
|
+
function isPayPalInitAcknowledgementTimeout(message) {
|
|
892
|
+
return message.includes("No ack for postMessage init()") && PAYPAL_INIT_ACKNOWLEDGEMENT_HOSTS.some((host) => message.includes(host)) && message.includes("10000ms");
|
|
893
|
+
}
|
|
940
894
|
function buildDirectPayPalRecoveryMessage(popupBlocked) {
|
|
941
895
|
return popupBlocked ? `${DIRECT_PAYPAL_RECOVERY_MESSAGE} Allow pop-ups for this site, then try again.` : DIRECT_PAYPAL_RECOVERY_MESSAGE;
|
|
942
896
|
}
|
|
@@ -956,6 +910,8 @@ function DirectPayPalButtonImplementation({
|
|
|
956
910
|
onTechnicalFailure,
|
|
957
911
|
isProcessing = false,
|
|
958
912
|
onLoadStateChange,
|
|
913
|
+
onInitializationStateChange,
|
|
914
|
+
allowAutomaticRetry = true,
|
|
959
915
|
onButtonClick,
|
|
960
916
|
runBeforeButtonClick,
|
|
961
917
|
session,
|
|
@@ -984,7 +940,7 @@ function DirectPayPalButtonImplementation({
|
|
|
984
940
|
telemetryContext?.layout
|
|
985
941
|
]);
|
|
986
942
|
const floPayTelemetry = useMemo2(() => getFloPayTelemetryBridge(flopay), [flopay]);
|
|
987
|
-
|
|
943
|
+
useEffect3(() => () => {
|
|
988
944
|
if (!standaloneTelemetry) return;
|
|
989
945
|
void standaloneTelemetry.flush().catch(() => {
|
|
990
946
|
}).finally(() => standaloneTelemetry.destroy());
|
|
@@ -1009,76 +965,104 @@ function DirectPayPalButtonImplementation({
|
|
|
1009
965
|
startTiming: () => floPayTelemetry?.now() ?? standaloneTelemetry?.now() ?? 0,
|
|
1010
966
|
elapsed: (startedAt) => floPayTelemetry?.elapsed(startedAt) ?? Math.max(0, (standaloneTelemetry?.now() ?? startedAt) - startedAt)
|
|
1011
967
|
}), [floPayTelemetry, standaloneTelemetry]);
|
|
1012
|
-
const containerRef =
|
|
1013
|
-
const providerStartedAt =
|
|
1014
|
-
const focusTargetRef =
|
|
1015
|
-
const pendingProviderFocusRef =
|
|
968
|
+
const containerRef = useRef3(null);
|
|
969
|
+
const providerStartedAt = useRef3(0);
|
|
970
|
+
const focusTargetRef = useRef3(null);
|
|
971
|
+
const pendingProviderFocusRef = useRef3(false);
|
|
1016
972
|
const [ready, setReady] = useState2(false);
|
|
973
|
+
const [recovering, setRecovering] = useState2(false);
|
|
1017
974
|
const [renderGeneration, setRenderGeneration] = useState2(0);
|
|
1018
|
-
const activeRenderGenerationRef =
|
|
975
|
+
const activeRenderGenerationRef = useRef3(0);
|
|
1019
976
|
const [showRetryFocusTarget, setShowRetryFocusTarget] = useState2(false);
|
|
977
|
+
const automaticRetryUsedRef = useRef3(false);
|
|
978
|
+
const initializationFailureLoggedRef = useRef3(false);
|
|
1020
979
|
const [failed, setFailed] = useState2(false);
|
|
1021
980
|
const [submitting, setSubmitting] = useState2(false);
|
|
1022
981
|
const baseUrl = useMemo2(() => billingApiUrl.replace(/\/+$/, ""), [billingApiUrl]);
|
|
982
|
+
const initializationConfigKey = useMemo2(
|
|
983
|
+
() => JSON.stringify([
|
|
984
|
+
baseUrl,
|
|
985
|
+
clientId,
|
|
986
|
+
currency,
|
|
987
|
+
environment ?? "",
|
|
988
|
+
isSubscription,
|
|
989
|
+
sessionId,
|
|
990
|
+
existingOrderId ?? ""
|
|
991
|
+
]),
|
|
992
|
+
[baseUrl, clientId, currency, environment, existingOrderId, isSubscription, sessionId]
|
|
993
|
+
);
|
|
994
|
+
const initializationConfigKeyRef = useRef3(initializationConfigKey);
|
|
995
|
+
const initializationConfigChanged = initializationConfigKeyRef.current !== initializationConfigKey;
|
|
1023
996
|
const [debugLines, setDebugLines] = useState2([]);
|
|
1024
997
|
const appendDebug = (line) => {
|
|
1025
998
|
if (!debug) return;
|
|
1026
999
|
setDebugLines((prev) => [...prev, `${(/* @__PURE__ */ new Date()).toISOString().slice(11, 23)} ${line}`]);
|
|
1027
1000
|
};
|
|
1028
|
-
const onTokenizedBodyRef =
|
|
1029
|
-
const onCompleteRef =
|
|
1030
|
-
const onErrorChangeRef =
|
|
1031
|
-
const onDeclineRef =
|
|
1032
|
-
const onTechnicalFailureRef =
|
|
1033
|
-
const onButtonClickRef =
|
|
1034
|
-
const onLoadStateChangeRef =
|
|
1035
|
-
const
|
|
1036
|
-
const
|
|
1037
|
-
const
|
|
1038
|
-
const
|
|
1039
|
-
const
|
|
1040
|
-
const
|
|
1041
|
-
const
|
|
1042
|
-
const
|
|
1043
|
-
const
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1001
|
+
const onTokenizedBodyRef = useRef3(onTokenizedBody);
|
|
1002
|
+
const onCompleteRef = useRef3(onComplete);
|
|
1003
|
+
const onErrorChangeRef = useRef3(onErrorChange);
|
|
1004
|
+
const onDeclineRef = useRef3(onDecline);
|
|
1005
|
+
const onTechnicalFailureRef = useRef3(onTechnicalFailure);
|
|
1006
|
+
const onButtonClickRef = useRef3(onButtonClick);
|
|
1007
|
+
const onLoadStateChangeRef = useRef3(onLoadStateChange);
|
|
1008
|
+
const onInitializationStateChangeRef = useRef3(onInitializationStateChange);
|
|
1009
|
+
const runBeforeButtonClickRef = useRef3(runBeforeButtonClick);
|
|
1010
|
+
const sessionRef = useRef3(session);
|
|
1011
|
+
const emailRef = useRef3(email);
|
|
1012
|
+
const nonceRef = useRef3(nonce);
|
|
1013
|
+
const beforeClickRef = useRef3(null);
|
|
1014
|
+
const attemptGenerationRef = useRef3(0);
|
|
1015
|
+
const attemptRef = useRef3(null);
|
|
1016
|
+
const invalidatedAttemptGenerationRef = useRef3(null);
|
|
1017
|
+
const attemptContextBySurfaceRef = useRef3(/* @__PURE__ */ new Map());
|
|
1018
|
+
const approvalContextByTokenRef = useRef3(/* @__PURE__ */ new Map());
|
|
1019
|
+
const componentMountedRef = useRef3(false);
|
|
1020
|
+
useEffect3(() => {
|
|
1021
|
+
componentMountedRef.current = true;
|
|
1022
|
+
return () => {
|
|
1023
|
+
componentMountedRef.current = false;
|
|
1024
|
+
};
|
|
1025
|
+
}, []);
|
|
1026
|
+
useEffect3(() => {
|
|
1046
1027
|
onTokenizedBodyRef.current = onTokenizedBody;
|
|
1047
1028
|
}, [onTokenizedBody]);
|
|
1048
|
-
|
|
1029
|
+
useEffect3(() => {
|
|
1049
1030
|
onCompleteRef.current = onComplete;
|
|
1050
1031
|
}, [onComplete]);
|
|
1051
|
-
|
|
1032
|
+
useEffect3(() => {
|
|
1052
1033
|
onErrorChangeRef.current = onErrorChange;
|
|
1053
1034
|
}, [onErrorChange]);
|
|
1054
|
-
|
|
1035
|
+
useEffect3(() => {
|
|
1055
1036
|
onDeclineRef.current = onDecline;
|
|
1056
1037
|
}, [onDecline]);
|
|
1057
|
-
|
|
1038
|
+
useEffect3(() => {
|
|
1058
1039
|
onTechnicalFailureRef.current = onTechnicalFailure;
|
|
1059
1040
|
}, [onTechnicalFailure]);
|
|
1060
|
-
|
|
1041
|
+
useEffect3(() => {
|
|
1061
1042
|
onButtonClickRef.current = onButtonClick;
|
|
1062
1043
|
}, [onButtonClick]);
|
|
1063
|
-
|
|
1044
|
+
useEffect3(() => {
|
|
1064
1045
|
onLoadStateChangeRef.current = onLoadStateChange;
|
|
1065
1046
|
}, [onLoadStateChange]);
|
|
1066
|
-
|
|
1047
|
+
useEffect3(() => {
|
|
1048
|
+
onInitializationStateChangeRef.current = onInitializationStateChange;
|
|
1049
|
+
}, [onInitializationStateChange]);
|
|
1050
|
+
useEffect3(() => {
|
|
1067
1051
|
runBeforeButtonClickRef.current = runBeforeButtonClick;
|
|
1068
1052
|
}, [runBeforeButtonClick]);
|
|
1069
|
-
|
|
1053
|
+
useEffect3(() => {
|
|
1070
1054
|
sessionRef.current = session;
|
|
1071
1055
|
}, [session]);
|
|
1072
|
-
|
|
1056
|
+
useEffect3(() => {
|
|
1073
1057
|
emailRef.current = email;
|
|
1074
1058
|
}, [email]);
|
|
1075
|
-
|
|
1059
|
+
useEffect3(() => {
|
|
1076
1060
|
nonceRef.current = nonce;
|
|
1077
1061
|
}, [nonce]);
|
|
1078
|
-
|
|
1062
|
+
useEffect3(() => {
|
|
1079
1063
|
activeRenderGenerationRef.current = renderGeneration;
|
|
1080
1064
|
}, [renderGeneration]);
|
|
1081
|
-
|
|
1065
|
+
useEffect3(() => {
|
|
1082
1066
|
if (showRetryFocusTarget) focusTargetRef.current?.focus();
|
|
1083
1067
|
}, [showRetryFocusTarget, renderGeneration]);
|
|
1084
1068
|
const focusRetryTarget = useCallback2(() => {
|
|
@@ -1097,7 +1081,7 @@ function DirectPayPalButtonImplementation({
|
|
|
1097
1081
|
);
|
|
1098
1082
|
(target ?? containerRef.current)?.focus?.();
|
|
1099
1083
|
}, []);
|
|
1100
|
-
|
|
1084
|
+
useEffect3(() => {
|
|
1101
1085
|
if (!ready || !pendingProviderFocusRef.current) return;
|
|
1102
1086
|
pendingProviderFocusRef.current = false;
|
|
1103
1087
|
focusPayPalSurface();
|
|
@@ -1180,7 +1164,7 @@ function DirectPayPalButtonImplementation({
|
|
|
1180
1164
|
}
|
|
1181
1165
|
return token;
|
|
1182
1166
|
};
|
|
1183
|
-
|
|
1167
|
+
useEffect3(() => {
|
|
1184
1168
|
const handleVisibilityChange = () => {
|
|
1185
1169
|
if (document.visibilityState === "visible") {
|
|
1186
1170
|
scheduleMissingCallbackRecovery();
|
|
@@ -1198,11 +1182,21 @@ function DirectPayPalButtonImplementation({
|
|
|
1198
1182
|
clearAttemptTimer();
|
|
1199
1183
|
};
|
|
1200
1184
|
}, []);
|
|
1201
|
-
|
|
1185
|
+
useEffect3(() => {
|
|
1186
|
+
if (initializationConfigKeyRef.current === initializationConfigKey) return;
|
|
1187
|
+
initializationConfigKeyRef.current = initializationConfigKey;
|
|
1188
|
+
automaticRetryUsedRef.current = false;
|
|
1189
|
+
initializationFailureLoggedRef.current = false;
|
|
1190
|
+
setReady(false);
|
|
1191
|
+
setRecovering(false);
|
|
1192
|
+
setFailed(false);
|
|
1193
|
+
onInitializationStateChangeRef.current?.("loading");
|
|
1194
|
+
}, [initializationConfigKey]);
|
|
1195
|
+
useEffect3(() => {
|
|
1202
1196
|
invokeMerchantCallback(() => onLoadStateChangeRef.current?.(ready && !failed));
|
|
1203
1197
|
}, [ready, failed]);
|
|
1204
1198
|
const normalizedEnv = normalizeGatewayEnvironment(environment);
|
|
1205
|
-
|
|
1199
|
+
useEffect3(() => {
|
|
1206
1200
|
const maskedClient = clientId ? `${clientId.slice(0, 6)}\u2026(len ${clientId.length})` : "(empty)";
|
|
1207
1201
|
const ua = typeof navigator !== "undefined" ? navigator.userAgent : "(no navigator)";
|
|
1208
1202
|
appendDebug(`mount clientId=${maskedClient} env=${environment ?? "(unset)"}\u2192${normalizedEnv ?? "live"} ccy=${currency} sub=${isSubscription}`);
|
|
@@ -1232,7 +1226,29 @@ function DirectPayPalButtonImplementation({
|
|
|
1232
1226
|
paymentMethodCategory: "paypal"
|
|
1233
1227
|
});
|
|
1234
1228
|
let cancelled = false;
|
|
1235
|
-
let
|
|
1229
|
+
let superseded = false;
|
|
1230
|
+
const isInactive = () => cancelled || superseded;
|
|
1231
|
+
let paypalIntentIdempotencyKey;
|
|
1232
|
+
let paypalIntentAttemptActive = false;
|
|
1233
|
+
const beginPaypalIntentAttempt = () => {
|
|
1234
|
+
paypalIntentIdempotencyKey = generateIdempotencyKey();
|
|
1235
|
+
if (!paypalIntentIdempotencyKey) {
|
|
1236
|
+
console.warn(
|
|
1237
|
+
"[FloPay] DirectPayPal: secure idempotency key unavailable; retries may not be deduplicated."
|
|
1238
|
+
);
|
|
1239
|
+
}
|
|
1240
|
+
paypalIntentAttemptActive = true;
|
|
1241
|
+
};
|
|
1242
|
+
let retryTimer = null;
|
|
1243
|
+
let preRenderFailureHandled = false;
|
|
1244
|
+
let ownedButtons = null;
|
|
1245
|
+
let ownedButtonsClosed = false;
|
|
1246
|
+
const closeOwnedButtons = () => {
|
|
1247
|
+
if (!ownedButtons || ownedButtonsClosed) return;
|
|
1248
|
+
ownedButtonsClosed = true;
|
|
1249
|
+
ownedButtons.close().catch(() => {
|
|
1250
|
+
});
|
|
1251
|
+
};
|
|
1236
1252
|
let overlayStartedAt = null;
|
|
1237
1253
|
const finishOverlay = () => {
|
|
1238
1254
|
if (overlayStartedAt === null) return;
|
|
@@ -1302,6 +1318,7 @@ function DirectPayPalButtonImplementation({
|
|
|
1302
1318
|
};
|
|
1303
1319
|
const errorMessages = [];
|
|
1304
1320
|
const onWindowError = (ev) => {
|
|
1321
|
+
if (isInactive()) return;
|
|
1305
1322
|
const msg = ev.message ?? String(ev.error ?? "(no message)");
|
|
1306
1323
|
if (msg && (msg.toLowerCase().includes("paypal") || msg.toLowerCase().includes("zoid") || msg.toLowerCase().includes("postrobot") || msg.toLowerCase().includes("storage"))) {
|
|
1307
1324
|
appendDebug(`window:error ${msg.slice(0, 140)}`);
|
|
@@ -1309,6 +1326,7 @@ function DirectPayPalButtonImplementation({
|
|
|
1309
1326
|
}
|
|
1310
1327
|
};
|
|
1311
1328
|
const onUnhandledRejection = (ev) => {
|
|
1329
|
+
if (isInactive()) return;
|
|
1312
1330
|
const reason = ev.reason instanceof Error ? ev.reason.message : String(ev.reason ?? "(no reason)");
|
|
1313
1331
|
if (reason && (reason.toLowerCase().includes("paypal") || reason.toLowerCase().includes("zoid") || reason.toLowerCase().includes("postrobot") || reason.toLowerCase().includes("storage"))) {
|
|
1314
1332
|
appendDebug(`window:rejection ${reason.slice(0, 140)}`);
|
|
@@ -1323,6 +1341,7 @@ function DirectPayPalButtonImplementation({
|
|
|
1323
1341
|
if (debug && typeof PerformanceObserver !== "undefined") {
|
|
1324
1342
|
try {
|
|
1325
1343
|
perfObserver = new PerformanceObserver((list) => {
|
|
1344
|
+
if (isInactive()) return;
|
|
1326
1345
|
for (const entry of list.getEntries()) {
|
|
1327
1346
|
if (!entry.name.toLowerCase().includes("paypal")) continue;
|
|
1328
1347
|
paypalRequestCount.value += 1;
|
|
@@ -1351,17 +1370,13 @@ function DirectPayPalButtonImplementation({
|
|
|
1351
1370
|
return lower.includes("zoid destroyed") || lower.includes("destroyed all components") || lower.includes("window closed") || lower.includes("detected container element removed");
|
|
1352
1371
|
};
|
|
1353
1372
|
const forwardError = (message) => {
|
|
1354
|
-
if (
|
|
1373
|
+
if (isInactive()) return;
|
|
1355
1374
|
if (isZoidLifecycleMessage(message)) return;
|
|
1356
1375
|
const friendly = applyFriendlyMessageOverride(message) ?? message;
|
|
1357
1376
|
invokeMerchantCallback(() => onErrorChangeRef.current?.(friendly));
|
|
1358
1377
|
};
|
|
1359
1378
|
const markRenderFailed = (message) => {
|
|
1360
1379
|
if (cancelled) return;
|
|
1361
|
-
if (isZoidLifecycleMessage(message)) {
|
|
1362
|
-
appendDebug(`markRenderFailed:skip-zoid msg=${message.slice(0, 80)}`);
|
|
1363
|
-
return;
|
|
1364
|
-
}
|
|
1365
1380
|
setFailed(true);
|
|
1366
1381
|
if (!message.includes("paypal_ineligible")) {
|
|
1367
1382
|
telemetrySource.error({
|
|
@@ -1374,8 +1389,48 @@ function DirectPayPalButtonImplementation({
|
|
|
1374
1389
|
}
|
|
1375
1390
|
console.error("[FloPay] DirectPayPal load/render failure:", message);
|
|
1376
1391
|
};
|
|
1392
|
+
const handlePreRenderFailure = (message, retryable = false) => {
|
|
1393
|
+
if (cancelled) return;
|
|
1394
|
+
const isRecoveryAttempt = automaticRetryUsedRef.current || !allowAutomaticRetry;
|
|
1395
|
+
if (preRenderFailureHandled) return;
|
|
1396
|
+
preRenderFailureHandled = true;
|
|
1397
|
+
superseded = true;
|
|
1398
|
+
stopDiagnostics();
|
|
1399
|
+
closeOwnedButtons();
|
|
1400
|
+
const isRetryableInitializationFailure = retryable || isPayPalInitAcknowledgementTimeout(message);
|
|
1401
|
+
if (isRetryableInitializationFailure && allowAutomaticRetry && !automaticRetryUsedRef.current) {
|
|
1402
|
+
automaticRetryUsedRef.current = true;
|
|
1403
|
+
setReady(false);
|
|
1404
|
+
setSubmitting(false);
|
|
1405
|
+
setRecovering(true);
|
|
1406
|
+
onInitializationStateChangeRef.current?.("retrying");
|
|
1407
|
+
retryTimer = setTimeout(() => {
|
|
1408
|
+
if (cancelled) return;
|
|
1409
|
+
setRenderGeneration((generation) => generation + 1);
|
|
1410
|
+
}, PAYPAL_INIT_RETRY_DELAY_MS);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (isRetryableInitializationFailure || isRecoveryAttempt) {
|
|
1414
|
+
setReady(false);
|
|
1415
|
+
setSubmitting(false);
|
|
1416
|
+
setRecovering(false);
|
|
1417
|
+
setFailed(true);
|
|
1418
|
+
onInitializationStateChangeRef.current?.("exhausted");
|
|
1419
|
+
if (!initializationFailureLoggedRef.current) {
|
|
1420
|
+
initializationFailureLoggedRef.current = true;
|
|
1421
|
+
const failureCode = isPayPalInitAcknowledgementTimeout(message) ? "paypal_init_timeout" : retryable ? "paypal_render_timeout" : "paypal_init_failure";
|
|
1422
|
+
console.error(
|
|
1423
|
+
"[FloPay] Direct PayPal initialization failed:",
|
|
1424
|
+
failureCode
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
markRenderFailed(message);
|
|
1430
|
+
};
|
|
1377
1431
|
setFailed(false);
|
|
1378
1432
|
const dispatchTokenizedBody = async (body, prepared = beforeClickRef.current) => {
|
|
1433
|
+
if (isInactive()) return;
|
|
1379
1434
|
const effectiveSessionId = prepared?.sessionId ?? sessionId;
|
|
1380
1435
|
if (onTokenizedBodyRef.current) {
|
|
1381
1436
|
await onTokenizedBodyRef.current(body, {
|
|
@@ -1429,41 +1484,45 @@ function DirectPayPalButtonImplementation({
|
|
|
1429
1484
|
}
|
|
1430
1485
|
);
|
|
1431
1486
|
if (response.ok) {
|
|
1487
|
+
if (superseded || cancelled && componentMountedRef.current) return;
|
|
1432
1488
|
finishProcessing();
|
|
1433
1489
|
telemetrySource.terminal({
|
|
1434
1490
|
outcome: "payment_succeeded",
|
|
1435
1491
|
provider: "paypal",
|
|
1436
1492
|
paymentMethodCategory: "paypal"
|
|
1437
1493
|
});
|
|
1438
|
-
|
|
1439
|
-
const json = await response.json().catch(() => null);
|
|
1440
|
-
const rawMessage = json?.["message"] ?? "PayPal payment failed.";
|
|
1441
|
-
const message = applyFriendlyMessageOverride(rawMessage) ?? rawMessage;
|
|
1442
|
-
finishProcessing();
|
|
1443
|
-
if (typeof json?.["declineCode"] === "string") {
|
|
1444
|
-
telemetrySource.terminal({
|
|
1445
|
-
outcome: "payment_declined",
|
|
1446
|
-
provider: "paypal",
|
|
1447
|
-
paymentMethodCategory: "paypal"
|
|
1448
|
-
});
|
|
1449
|
-
} else {
|
|
1450
|
-
telemetrySource.error({
|
|
1451
|
-
errorCode: "PAYMENT_PROCESSING_FAILED",
|
|
1452
|
-
stage: "processing",
|
|
1453
|
-
provider: "paypal",
|
|
1454
|
-
paymentMethodCategory: "paypal",
|
|
1455
|
-
requestCategory: "process_payment",
|
|
1456
|
-
statusClass: response.status >= 500 ? "5xx" : response.status >= 400 ? "4xx" : response.status >= 300 ? "3xx" : "unknown"
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
forwardError(message);
|
|
1460
|
-
invokeMerchantCallback(() => onDeclineRef.current?.(buildDeclineEvent("paypal", message, {
|
|
1461
|
-
code: json?.["code"],
|
|
1462
|
-
declineCode: json?.["declineCode"]
|
|
1463
|
-
})));
|
|
1494
|
+
invokeMerchantCallback(() => onCompleteRef.current?.({ status: "succeeded", checkoutMethod: "paypal" }));
|
|
1464
1495
|
return;
|
|
1465
1496
|
}
|
|
1497
|
+
if (isInactive()) return;
|
|
1498
|
+
const json = await response.json().catch(() => null);
|
|
1499
|
+
const rawMessage = json?.["message"] ?? "PayPal payment failed.";
|
|
1500
|
+
const message = applyFriendlyMessageOverride(rawMessage) ?? rawMessage;
|
|
1501
|
+
finishProcessing();
|
|
1502
|
+
if (typeof json?.["declineCode"] === "string") {
|
|
1503
|
+
telemetrySource.terminal({
|
|
1504
|
+
outcome: "payment_declined",
|
|
1505
|
+
provider: "paypal",
|
|
1506
|
+
paymentMethodCategory: "paypal"
|
|
1507
|
+
});
|
|
1508
|
+
} else {
|
|
1509
|
+
telemetrySource.error({
|
|
1510
|
+
errorCode: "PAYMENT_PROCESSING_FAILED",
|
|
1511
|
+
stage: "processing",
|
|
1512
|
+
provider: "paypal",
|
|
1513
|
+
paymentMethodCategory: "paypal",
|
|
1514
|
+
requestCategory: "process_payment",
|
|
1515
|
+
statusClass: response.status >= 500 ? "5xx" : response.status >= 400 ? "4xx" : response.status >= 300 ? "3xx" : "unknown"
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
forwardError(message);
|
|
1519
|
+
invokeMerchantCallback(() => onDeclineRef.current?.(buildDeclineEvent("paypal", message, {
|
|
1520
|
+
code: json?.["code"],
|
|
1521
|
+
declineCode: json?.["declineCode"]
|
|
1522
|
+
})));
|
|
1523
|
+
return;
|
|
1466
1524
|
} catch (err) {
|
|
1525
|
+
if (isInactive()) return;
|
|
1467
1526
|
finishProcessing();
|
|
1468
1527
|
telemetrySource.error({
|
|
1469
1528
|
errorCode: "NETWORK_REQUEST_FAILED",
|
|
@@ -1479,15 +1538,21 @@ function DirectPayPalButtonImplementation({
|
|
|
1479
1538
|
invokeMerchantCallback(() => onDeclineRef.current?.(buildDeclineEvent("paypal", message)));
|
|
1480
1539
|
return;
|
|
1481
1540
|
}
|
|
1482
|
-
invokeMerchantCallback(() => onCompleteRef.current?.({ status: "succeeded", checkoutMethod: "paypal" }));
|
|
1483
1541
|
};
|
|
1542
|
+
const supersededRenderError = () => new FloPayError2(
|
|
1543
|
+
"PayPal render was superseded.",
|
|
1544
|
+
"api_error",
|
|
1545
|
+
{ code: "paypal_render_superseded" }
|
|
1546
|
+
);
|
|
1547
|
+
const resolveExistingOrder = (orderId) => isInactive() ? Promise.reject(supersededRenderError()) : Promise.resolve(orderId);
|
|
1484
1548
|
const createPaypalIntent = async (fallbackMessage) => {
|
|
1549
|
+
if (isInactive()) {
|
|
1550
|
+
throw supersededRenderError();
|
|
1551
|
+
}
|
|
1552
|
+
if (!paypalIntentAttemptActive) beginPaypalIntentAttempt();
|
|
1485
1553
|
const prepared = beforeClickRef.current;
|
|
1486
1554
|
const effectiveSessionId = prepared?.sessionId ?? sessionId;
|
|
1487
|
-
const effectiveEmail = prepared?.accountPatch?.email ?? emailRef.current;
|
|
1488
|
-
const intentHeaders = { "Content-Type": "application/json" };
|
|
1489
1555
|
const currentNonce = prepared?.nonce ?? nonceRef.current;
|
|
1490
|
-
if (currentNonce) intentHeaders["x-checkout-session-token"] = currentNonce;
|
|
1491
1556
|
telemetrySource.log({
|
|
1492
1557
|
name: "payment.intent.started",
|
|
1493
1558
|
stage: "processing",
|
|
@@ -1495,22 +1560,22 @@ function DirectPayPalButtonImplementation({
|
|
|
1495
1560
|
paymentMethodCategory: "paypal",
|
|
1496
1561
|
requestCategory: "intent_create"
|
|
1497
1562
|
});
|
|
1498
|
-
const
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1563
|
+
const intent = await new PaymentAPI(baseUrl, { telemetry: false }).createSessionIntent(
|
|
1564
|
+
effectiveSessionId,
|
|
1565
|
+
currentNonce ?? "",
|
|
1566
|
+
{
|
|
1567
|
+
provider: "paypal",
|
|
1568
|
+
paymentMethodCategory: "wallet",
|
|
1504
1569
|
paymentMethodType: "paypal",
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1570
|
+
paymentMethodId: null,
|
|
1571
|
+
intentKind: isSubscription ? "subscription" : "order"
|
|
1572
|
+
},
|
|
1573
|
+
{ idempotencyKey: paypalIntentIdempotencyKey }
|
|
1574
|
+
);
|
|
1575
|
+
if (isInactive()) {
|
|
1576
|
+
throw supersededRenderError();
|
|
1511
1577
|
}
|
|
1512
|
-
|
|
1513
|
-
if (!id) {
|
|
1578
|
+
if (intent.provider !== "paypal") {
|
|
1514
1579
|
throw new Error(fallbackMessage);
|
|
1515
1580
|
}
|
|
1516
1581
|
telemetrySource.log({
|
|
@@ -1521,7 +1586,24 @@ function DirectPayPalButtonImplementation({
|
|
|
1521
1586
|
requestCategory: "intent_create",
|
|
1522
1587
|
statusClass: "2xx"
|
|
1523
1588
|
});
|
|
1524
|
-
return
|
|
1589
|
+
return intent.providerObjectId;
|
|
1590
|
+
};
|
|
1591
|
+
const reportPayPalDecline = (error, context) => {
|
|
1592
|
+
const currentNonce = context?.nonce ?? nonceRef.current;
|
|
1593
|
+
if (!currentNonce) return;
|
|
1594
|
+
const effectiveSessionId = context?.sessionId ?? sessionId;
|
|
1595
|
+
const providerDeclineReason = getProviderDeclineCode(error) ?? getProviderErrorCode(error) ?? "provider_declined";
|
|
1596
|
+
void new PaymentAPI(baseUrl, { telemetry: false }).reportSessionIntentDecline(
|
|
1597
|
+
effectiveSessionId,
|
|
1598
|
+
currentNonce,
|
|
1599
|
+
{
|
|
1600
|
+
provider: "paypal",
|
|
1601
|
+
paymentMethodCategory: "wallet",
|
|
1602
|
+
paymentMethodType: "paypal",
|
|
1603
|
+
providerDeclineReason
|
|
1604
|
+
}
|
|
1605
|
+
).catch(() => {
|
|
1606
|
+
});
|
|
1525
1607
|
};
|
|
1526
1608
|
const effectRenderGeneration = renderGeneration;
|
|
1527
1609
|
appendDebug("loadScript:scheduled (deferred 1 tick)");
|
|
@@ -1550,9 +1632,11 @@ function DirectPayPalButtonImplementation({
|
|
|
1550
1632
|
...paypalSdkEnv === "sandbox" ? { dataNamespace: "paypal_sandbox" } : {}
|
|
1551
1633
|
});
|
|
1552
1634
|
loadPromise.then((paypal) => {
|
|
1553
|
-
appendDebug(`loadScript:resolved
|
|
1554
|
-
if (
|
|
1555
|
-
|
|
1635
|
+
appendDebug(`loadScript:resolved inactive=${isInactive()} ns=${!!paypal} buttons=${!!paypal?.Buttons}`);
|
|
1636
|
+
if (isInactive()) return;
|
|
1637
|
+
if (!paypal?.Buttons) {
|
|
1638
|
+
appendDebug("FAIL: namespace missing Buttons factory");
|
|
1639
|
+
handlePreRenderFailure("PayPal SDK did not expose the Buttons factory.");
|
|
1556
1640
|
return;
|
|
1557
1641
|
}
|
|
1558
1642
|
telemetrySource.log({
|
|
@@ -1562,7 +1646,7 @@ function DirectPayPalButtonImplementation({
|
|
|
1562
1646
|
paymentMethodCategory: "paypal"
|
|
1563
1647
|
});
|
|
1564
1648
|
const handleApprove = async (data) => {
|
|
1565
|
-
if (
|
|
1649
|
+
if (isInactive() || activeRenderGenerationRef.current !== effectRenderGeneration) return;
|
|
1566
1650
|
const token = data.subscriptionID ?? data.orderID ?? "";
|
|
1567
1651
|
const context = token ? approvalContextByTokenRef.current.get(token) : attemptContextBySurfaceRef.current.get(effectRenderGeneration);
|
|
1568
1652
|
if (!context || !isAttemptActive(context.generation)) return;
|
|
@@ -1587,7 +1671,7 @@ function DirectPayPalButtonImplementation({
|
|
|
1587
1671
|
} catch (err) {
|
|
1588
1672
|
forwardError(err instanceof Error ? err.message : "PayPal capture failed.");
|
|
1589
1673
|
} finally {
|
|
1590
|
-
setSubmitting(false);
|
|
1674
|
+
if (!isInactive()) setSubmitting(false);
|
|
1591
1675
|
}
|
|
1592
1676
|
};
|
|
1593
1677
|
const buttons = paypal.Buttons({
|
|
@@ -1598,11 +1682,20 @@ function DirectPayPalButtonImplementation({
|
|
|
1598
1682
|
// inline-session/account patches land before the order is created,
|
|
1599
1683
|
// matching the Stripe-rendered PayPal flow.
|
|
1600
1684
|
onClick: async (_data, actions) => {
|
|
1685
|
+
if (isInactive()) {
|
|
1686
|
+
await actions.reject();
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
beginPaypalIntentAttempt();
|
|
1601
1690
|
const runner = runBeforeButtonClickRef.current;
|
|
1602
1691
|
let prepared = {};
|
|
1603
1692
|
if (runner) {
|
|
1604
1693
|
try {
|
|
1605
1694
|
const beforeClick = await runner("paypal");
|
|
1695
|
+
if (isInactive()) {
|
|
1696
|
+
await actions.reject();
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1606
1699
|
if (!beforeClick.proceed) {
|
|
1607
1700
|
beforeClickRef.current = null;
|
|
1608
1701
|
attemptContextBySurfaceRef.current.delete(effectRenderGeneration);
|
|
@@ -1656,10 +1749,11 @@ function DirectPayPalButtonImplementation({
|
|
|
1656
1749
|
// prior backend round-trip (the `paypal_direct_required` retry
|
|
1657
1750
|
// path), feed it straight to PayPal instead of creating a new one.
|
|
1658
1751
|
// Otherwise fall back to the normal create-intent call.
|
|
1659
|
-
createOrder: isSubscription ? void 0 : existingOrderId ? () =>
|
|
1660
|
-
createSubscription: isSubscription ? existingOrderId ? () =>
|
|
1752
|
+
createOrder: isSubscription ? void 0 : existingOrderId ? () => resolveExistingOrder(existingOrderId).then(registerApprovalContext) : () => createPaypalIntent("Failed to create PayPal order.").then(registerApprovalContext),
|
|
1753
|
+
createSubscription: isSubscription ? existingOrderId ? () => resolveExistingOrder(existingOrderId).then(registerApprovalContext) : () => createPaypalIntent("Failed to create PayPal subscription.").then(registerApprovalContext) : void 0,
|
|
1661
1754
|
onApprove: handleApprove,
|
|
1662
1755
|
onCancel: () => {
|
|
1756
|
+
if (isInactive()) return;
|
|
1663
1757
|
const context = attemptContextBySurfaceRef.current.get(effectRenderGeneration) ?? beforeClickRef.current;
|
|
1664
1758
|
if (context) attemptContextBySurfaceRef.current.delete(context.surfaceKey);
|
|
1665
1759
|
beforeClickRef.current = null;
|
|
@@ -1673,7 +1767,7 @@ function DirectPayPalButtonImplementation({
|
|
|
1673
1767
|
invalidateAttempt({ generation: context?.generation, remount: true, focus: false });
|
|
1674
1768
|
},
|
|
1675
1769
|
onError: (err) => {
|
|
1676
|
-
if (
|
|
1770
|
+
if (isInactive() || activeRenderGenerationRef.current !== effectRenderGeneration) return;
|
|
1677
1771
|
const message = err instanceof Error ? err.message : "PayPal failed to render.";
|
|
1678
1772
|
appendDebug(`onError rendered=${rendered} msg=${message.slice(0, 120)}`);
|
|
1679
1773
|
if (rendered) {
|
|
@@ -1693,13 +1787,16 @@ function DirectPayPalButtonImplementation({
|
|
|
1693
1787
|
paymentMethodCategory: "paypal",
|
|
1694
1788
|
requestCategory: "provider_sdk"
|
|
1695
1789
|
});
|
|
1790
|
+
if (isProviderDecline(err)) {
|
|
1791
|
+
reportPayPalDecline(err, context);
|
|
1792
|
+
}
|
|
1696
1793
|
if (context) attemptContextBySurfaceRef.current.delete(context.surfaceKey);
|
|
1697
1794
|
beforeClickRef.current = null;
|
|
1698
1795
|
invalidateAttempt({ generation: context?.generation, remount: true, showRetry: true, focus: true });
|
|
1699
1796
|
notifyTechnicalFailure(err, { code: "paypal_runtime_failed" });
|
|
1700
1797
|
return;
|
|
1701
1798
|
}
|
|
1702
|
-
|
|
1799
|
+
handlePreRenderFailure(message);
|
|
1703
1800
|
}
|
|
1704
1801
|
});
|
|
1705
1802
|
const eligible = buttons.isEligible();
|
|
@@ -1712,17 +1809,19 @@ function DirectPayPalButtonImplementation({
|
|
|
1712
1809
|
});
|
|
1713
1810
|
if (!eligible) {
|
|
1714
1811
|
setReady(false);
|
|
1715
|
-
|
|
1812
|
+
handlePreRenderFailure(
|
|
1716
1813
|
"PayPal buttons are not eligible to render in this context (paypal_ineligible)."
|
|
1717
1814
|
);
|
|
1718
1815
|
return;
|
|
1719
1816
|
}
|
|
1720
1817
|
const typedButtons = buttons;
|
|
1818
|
+
ownedButtons = typedButtons;
|
|
1721
1819
|
if (debug) {
|
|
1722
1820
|
appendDebug(`container:dims ${formatDims(container)} visibility=${typeof document !== "undefined" ? document.visibilityState : "(no document)"}`);
|
|
1723
1821
|
}
|
|
1724
1822
|
if (debug && typeof MutationObserver !== "undefined") {
|
|
1725
1823
|
containerObserver = new MutationObserver((mutations) => {
|
|
1824
|
+
if (isInactive()) return;
|
|
1726
1825
|
for (const mutation of mutations) {
|
|
1727
1826
|
if (mutation.type === "childList") {
|
|
1728
1827
|
mutation.addedNodes.forEach((node) => {
|
|
@@ -1750,7 +1849,7 @@ function DirectPayPalButtonImplementation({
|
|
|
1750
1849
|
});
|
|
1751
1850
|
}
|
|
1752
1851
|
const snapshotContainer = (when) => {
|
|
1753
|
-
if (
|
|
1852
|
+
if (isInactive() || rendered) return;
|
|
1754
1853
|
const iframes = container.querySelectorAll("iframe");
|
|
1755
1854
|
const hasStorageAccess = typeof document !== "undefined" && "hasStorageAccess" in document;
|
|
1756
1855
|
appendDebug(`watchdog:${when} container=${formatDims(container)} children=${container.childElementCount} iframes=${iframes.length} visibility=${typeof document !== "undefined" ? document.visibilityState : "(no document)"} cookies=${typeof navigator !== "undefined" ? navigator.cookieEnabled : "?"} hasStorageAccessApi=${hasStorageAccess} paypalNetRequests=${paypalRequestCount.value}`);
|
|
@@ -1770,17 +1869,25 @@ function DirectPayPalButtonImplementation({
|
|
|
1770
1869
|
watchdogTimers.push(setTimeout(() => snapshotContainer("15s"), 15e3));
|
|
1771
1870
|
}
|
|
1772
1871
|
appendDebug("render:start");
|
|
1872
|
+
watchdogTimers.push(setTimeout(() => {
|
|
1873
|
+
if (isInactive() || rendered) return;
|
|
1874
|
+
appendDebug("watchdog:11s retryable render timeout");
|
|
1875
|
+
handlePreRenderFailure(
|
|
1876
|
+
"PayPal Buttons render did not settle within 11000ms.",
|
|
1877
|
+
true
|
|
1878
|
+
);
|
|
1879
|
+
}, PAYPAL_RENDER_WATCHDOG_MS));
|
|
1773
1880
|
buttons.render(container).then(() => {
|
|
1774
|
-
appendDebug(`render:resolved
|
|
1881
|
+
appendDebug(`render:resolved inactive=${isInactive()}`);
|
|
1775
1882
|
stopDiagnostics();
|
|
1776
|
-
if (
|
|
1777
|
-
|
|
1778
|
-
});
|
|
1883
|
+
if (isInactive()) {
|
|
1884
|
+
closeOwnedButtons();
|
|
1779
1885
|
return;
|
|
1780
1886
|
}
|
|
1781
|
-
activeButtons = typedButtons;
|
|
1782
1887
|
rendered = true;
|
|
1783
1888
|
setReady(true);
|
|
1889
|
+
setRecovering(false);
|
|
1890
|
+
onInitializationStateChangeRef.current?.("ready");
|
|
1784
1891
|
telemetrySource.log({
|
|
1785
1892
|
name: "provider.ready",
|
|
1786
1893
|
stage: "provider_ready",
|
|
@@ -1798,25 +1905,23 @@ function DirectPayPalButtonImplementation({
|
|
|
1798
1905
|
const message = err instanceof Error ? err.message : "PayPal failed to render.";
|
|
1799
1906
|
appendDebug(`render:rejected msg=${message.slice(0, 120)}`);
|
|
1800
1907
|
stopDiagnostics();
|
|
1801
|
-
|
|
1908
|
+
handlePreRenderFailure(message);
|
|
1802
1909
|
});
|
|
1803
1910
|
}).catch((err) => {
|
|
1804
1911
|
const message = err instanceof Error ? err.message : "PayPal SDK failed to load.";
|
|
1805
1912
|
appendDebug(`loadScript:rejected msg=${message.slice(0, 120)}`);
|
|
1806
|
-
|
|
1913
|
+
handlePreRenderFailure(message);
|
|
1807
1914
|
});
|
|
1808
1915
|
}, 0);
|
|
1809
1916
|
return () => {
|
|
1810
1917
|
cancelled = true;
|
|
1811
1918
|
clearTimeout(startTimer);
|
|
1919
|
+
if (retryTimer) clearTimeout(retryTimer);
|
|
1812
1920
|
stopDiagnostics();
|
|
1813
|
-
|
|
1814
|
-
activeButtons.close().catch(() => {
|
|
1815
|
-
});
|
|
1816
|
-
}
|
|
1921
|
+
closeOwnedButtons();
|
|
1817
1922
|
};
|
|
1818
|
-
}, [baseUrl, clientId, currency, environment, isSubscription, sessionId, existingOrderId, renderGeneration, telemetrySource]);
|
|
1819
|
-
const debugPanel = debug ? /* @__PURE__ */
|
|
1923
|
+
}, [allowAutomaticRetry, baseUrl, clientId, currency, environment, isSubscription, sessionId, existingOrderId, renderGeneration, telemetrySource]);
|
|
1924
|
+
const debugPanel = debug ? /* @__PURE__ */ jsxs4(
|
|
1820
1925
|
"pre",
|
|
1821
1926
|
{
|
|
1822
1927
|
"data-testid": "flopay-direct-paypal-debug",
|
|
@@ -1840,7 +1945,7 @@ ${debugLines.join("\n")}`
|
|
|
1840
1945
|
]
|
|
1841
1946
|
}
|
|
1842
1947
|
) : null;
|
|
1843
|
-
if (failed) {
|
|
1948
|
+
if (failed && !initializationConfigChanged) {
|
|
1844
1949
|
return debug ? /* @__PURE__ */ jsx6("div", { children: debugPanel }) : null;
|
|
1845
1950
|
}
|
|
1846
1951
|
return (
|
|
@@ -1848,51 +1953,64 @@ ${debugLines.join("\n")}`
|
|
|
1848
1953
|
// (otherwise the fragment's placeholder + container become two siblings
|
|
1849
1954
|
// and any spacing-sensitive layout has to reason about both). The wrapper
|
|
1850
1955
|
// intentionally has no margin/padding so the parent owns all spacing.
|
|
1851
|
-
/* @__PURE__ */
|
|
1956
|
+
/* @__PURE__ */ jsxs4("div", { children: [
|
|
1852
1957
|
debugPanel,
|
|
1853
|
-
/* @__PURE__ */
|
|
1854
|
-
|
|
1855
|
-
"div",
|
|
1856
|
-
{
|
|
1857
|
-
"data-testid": "flopay-direct-paypal-placeholder",
|
|
1858
|
-
style: {
|
|
1859
|
-
position: "absolute",
|
|
1860
|
-
inset: 0,
|
|
1861
|
-
borderRadius: 8,
|
|
1862
|
-
background: "#e5e7eb",
|
|
1863
|
-
animation: "flopay-pulse 1.5s ease-in-out infinite",
|
|
1864
|
-
pointerEvents: "none"
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
),
|
|
1868
|
-
/* @__PURE__ */ jsx6(
|
|
1869
|
-
"div",
|
|
1870
|
-
{
|
|
1871
|
-
ref: containerRef,
|
|
1872
|
-
"data-testid": "flopay-direct-paypal-container",
|
|
1873
|
-
tabIndex: -1,
|
|
1874
|
-
"aria-label": "PayPal payment method",
|
|
1875
|
-
style: {
|
|
1876
|
-
minHeight: DEFAULT_BUTTON_HEIGHT,
|
|
1877
|
-
display: "flex",
|
|
1878
|
-
opacity: ready ? 1 : 0
|
|
1879
|
-
},
|
|
1880
|
-
"aria-busy": submitting || isProcessing
|
|
1881
|
-
},
|
|
1882
|
-
renderGeneration
|
|
1883
|
-
)
|
|
1884
|
-
] }),
|
|
1885
|
-
showRetryFocusTarget && /* @__PURE__ */ jsx6(
|
|
1886
|
-
"button",
|
|
1958
|
+
/* @__PURE__ */ jsxs4(
|
|
1959
|
+
"div",
|
|
1887
1960
|
{
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1961
|
+
"aria-hidden": recovering ? true : void 0,
|
|
1962
|
+
style: recovering ? {
|
|
1963
|
+
position: "relative",
|
|
1964
|
+
height: 0,
|
|
1965
|
+
opacity: 0,
|
|
1966
|
+
pointerEvents: "none"
|
|
1967
|
+
} : { position: "relative", minHeight: DEFAULT_BUTTON_HEIGHT },
|
|
1968
|
+
children: [
|
|
1969
|
+
!ready && !recovering && /* @__PURE__ */ jsx6(
|
|
1970
|
+
"div",
|
|
1971
|
+
{
|
|
1972
|
+
"data-testid": "flopay-direct-paypal-placeholder",
|
|
1973
|
+
style: {
|
|
1974
|
+
position: "absolute",
|
|
1975
|
+
inset: 0,
|
|
1976
|
+
borderRadius: 8,
|
|
1977
|
+
background: "#e5e7eb",
|
|
1978
|
+
animation: "flopay-pulse 1.5s ease-in-out infinite",
|
|
1979
|
+
pointerEvents: "none"
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
),
|
|
1983
|
+
/* @__PURE__ */ jsx6(
|
|
1984
|
+
"div",
|
|
1985
|
+
{
|
|
1986
|
+
ref: containerRef,
|
|
1987
|
+
"data-testid": "flopay-direct-paypal-container",
|
|
1988
|
+
tabIndex: -1,
|
|
1989
|
+
"aria-label": "PayPal payment method",
|
|
1990
|
+
style: {
|
|
1991
|
+
minHeight: DEFAULT_BUTTON_HEIGHT,
|
|
1992
|
+
display: "flex",
|
|
1993
|
+
opacity: ready ? 1 : 0
|
|
1994
|
+
},
|
|
1995
|
+
"aria-busy": submitting || isProcessing
|
|
1996
|
+
},
|
|
1997
|
+
renderGeneration
|
|
1998
|
+
)
|
|
1999
|
+
]
|
|
2000
|
+
},
|
|
2001
|
+
renderGeneration
|
|
2002
|
+
),
|
|
2003
|
+
showRetryFocusTarget && /* @__PURE__ */ jsx6(
|
|
2004
|
+
"button",
|
|
2005
|
+
{
|
|
2006
|
+
ref: focusTargetRef,
|
|
2007
|
+
type: "button",
|
|
2008
|
+
"data-testid": "flopay-direct-paypal-focus-target",
|
|
2009
|
+
onClick: focusPayPalSurface,
|
|
2010
|
+
style: DIRECT_PAYPAL_RECOVERY_ACTION_STYLE,
|
|
2011
|
+
children: "Try PayPal again"
|
|
2012
|
+
}
|
|
2013
|
+
)
|
|
1896
2014
|
] })
|
|
1897
2015
|
);
|
|
1898
2016
|
}
|
|
@@ -1905,10 +2023,26 @@ function InstrumentedDirectPayPalButton(props) {
|
|
|
1905
2023
|
|
|
1906
2024
|
// src/split-card-form.tsx
|
|
1907
2025
|
import { FloPayError as FloPayError3, isSetupIntentClientSecret as isSetupIntentClientSecret2, resolveTheme } from "@flopay/shared";
|
|
1908
|
-
import { Fragment as Fragment2, jsx as jsx7, jsxs as
|
|
2026
|
+
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1909
2027
|
var STRIPE_RESUME_KEY = "flopay_stripe_resume";
|
|
1910
2028
|
var LEGACY_WALLET_RESUME_KEY = "flopay_wallet_resume";
|
|
1911
2029
|
var PAYPAL_RESUME_KEY = "flopay_paypal_resume";
|
|
2030
|
+
async function reportNonCardDecline({
|
|
2031
|
+
billingApiUrl,
|
|
2032
|
+
sessionId,
|
|
2033
|
+
nonce,
|
|
2034
|
+
request
|
|
2035
|
+
}) {
|
|
2036
|
+
if (!sessionId || !nonce) return;
|
|
2037
|
+
try {
|
|
2038
|
+
await new PaymentAPI2(billingApiUrl, { telemetry: false }).reportSessionIntentDecline(
|
|
2039
|
+
sessionId,
|
|
2040
|
+
nonce,
|
|
2041
|
+
request
|
|
2042
|
+
);
|
|
2043
|
+
} catch {
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
1912
2046
|
function isAccountValidationError(err) {
|
|
1913
2047
|
return err instanceof FloPayError3 && typeof err.statusCode === "number" && err.statusCode >= 400 && err.statusCode < 500;
|
|
1914
2048
|
}
|
|
@@ -2020,9 +2154,9 @@ function buildExternalMethodRecoveryMessage(method, popupBlocked) {
|
|
|
2020
2154
|
return popupBlocked ? `${base} Allow pop-ups for this site, then try again.` : base;
|
|
2021
2155
|
}
|
|
2022
2156
|
function useExternalAttemptReconciliation(onMissingTerminal) {
|
|
2023
|
-
const generationRef =
|
|
2024
|
-
const invalidatedGenerationRef =
|
|
2025
|
-
const attemptRef =
|
|
2157
|
+
const generationRef = useRef4(0);
|
|
2158
|
+
const invalidatedGenerationRef = useRef4(null);
|
|
2159
|
+
const attemptRef = useRef4(null);
|
|
2026
2160
|
const clearAttemptTimer = useCallback3((targetAttempt = attemptRef.current) => {
|
|
2027
2161
|
const attempt = targetAttempt;
|
|
2028
2162
|
if (attempt?.timer) {
|
|
@@ -2096,7 +2230,7 @@ function useExternalAttemptReconciliation(onMissingTerminal) {
|
|
|
2096
2230
|
attempt.yieldedControl = true;
|
|
2097
2231
|
clearAttemptTimer(attempt);
|
|
2098
2232
|
}, [clearAttemptTimer]);
|
|
2099
|
-
|
|
2233
|
+
useEffect4(() => {
|
|
2100
2234
|
const handleVisibilityChange = () => {
|
|
2101
2235
|
if (document.visibilityState === "visible") {
|
|
2102
2236
|
scheduleRecoveryIfReturned();
|
|
@@ -2160,7 +2294,7 @@ function ExpressCheckoutReadySwap({
|
|
|
2160
2294
|
children
|
|
2161
2295
|
}) {
|
|
2162
2296
|
if (state === "unavailable" || state === "load_error") return null;
|
|
2163
|
-
return /* @__PURE__ */
|
|
2297
|
+
return /* @__PURE__ */ jsxs5("div", { style: { position: "relative", minHeight: 44 }, children: [
|
|
2164
2298
|
/* @__PURE__ */ jsx7(
|
|
2165
2299
|
"div",
|
|
2166
2300
|
{
|
|
@@ -2198,11 +2332,9 @@ function ExpressCheckoutReadySwap({
|
|
|
2198
2332
|
function isExpressCheckoutRowVisible(state) {
|
|
2199
2333
|
return state !== "unavailable" && state !== "load_error";
|
|
2200
2334
|
}
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
}
|
|
2205
|
-
);
|
|
2335
|
+
function SplitCardForm(props) {
|
|
2336
|
+
return /* @__PURE__ */ jsx7(SplitCardFormInner, { ...props });
|
|
2337
|
+
}
|
|
2206
2338
|
function PayPalButtonInner({
|
|
2207
2339
|
sessionId,
|
|
2208
2340
|
nonce,
|
|
@@ -2222,18 +2354,17 @@ function PayPalButtonInner({
|
|
|
2222
2354
|
const stripe = useStripeRaw();
|
|
2223
2355
|
const elements = useStripeElements();
|
|
2224
2356
|
const [loadState, setLoadState] = useState3("loading");
|
|
2225
|
-
|
|
2357
|
+
useEffect4(() => {
|
|
2226
2358
|
onLoadStateChange?.(loadState);
|
|
2227
2359
|
}, [loadState, onLoadStateChange]);
|
|
2228
2360
|
const [submitting, setSubmitting] = useState3(false);
|
|
2229
|
-
const paypalResumeAttempted =
|
|
2230
|
-
const focusTargetRef =
|
|
2231
|
-
const recoveryActionRef =
|
|
2361
|
+
const paypalResumeAttempted = useRef4(false);
|
|
2362
|
+
const focusTargetRef = useRef4(null);
|
|
2363
|
+
const recoveryActionRef = useRef4(null);
|
|
2232
2364
|
const [surfaceKey, setSurfaceKey] = useState3(0);
|
|
2233
2365
|
const [showRecoveryAction, setShowRecoveryAction] = useState3(false);
|
|
2234
|
-
const pendingProviderFocusRef =
|
|
2235
|
-
const attemptContextBySurfaceRef =
|
|
2236
|
-
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
2366
|
+
const pendingProviderFocusRef = useRef4(false);
|
|
2367
|
+
const attemptContextBySurfaceRef = useRef4(/* @__PURE__ */ new Map());
|
|
2237
2368
|
const {
|
|
2238
2369
|
startAttempt,
|
|
2239
2370
|
finishAttempt,
|
|
@@ -2248,7 +2379,7 @@ function PayPalButtonInner({
|
|
|
2248
2379
|
setShowRecoveryAction(true);
|
|
2249
2380
|
setSurfaceKey((key) => key + 1);
|
|
2250
2381
|
});
|
|
2251
|
-
|
|
2382
|
+
useEffect4(() => {
|
|
2252
2383
|
if (showRecoveryAction) recoveryActionRef.current?.focus();
|
|
2253
2384
|
}, [showRecoveryAction, surfaceKey]);
|
|
2254
2385
|
const resetSurface = useCallback3(() => {
|
|
@@ -2266,7 +2397,7 @@ function PayPalButtonInner({
|
|
|
2266
2397
|
(target ?? focusTargetRef.current)?.focus();
|
|
2267
2398
|
}, 0);
|
|
2268
2399
|
}, []);
|
|
2269
|
-
|
|
2400
|
+
useEffect4(() => {
|
|
2270
2401
|
if (!pendingProviderFocusRef.current) return;
|
|
2271
2402
|
pendingProviderFocusRef.current = false;
|
|
2272
2403
|
focusProviderSurface();
|
|
@@ -2276,7 +2407,7 @@ function PayPalButtonInner({
|
|
|
2276
2407
|
onErrorChange?.(null);
|
|
2277
2408
|
focusProviderSurface();
|
|
2278
2409
|
}, [focusProviderSurface, onErrorChange]);
|
|
2279
|
-
|
|
2410
|
+
useEffect4(() => {
|
|
2280
2411
|
if (!stripe || paypalResumeAttempted.current) return;
|
|
2281
2412
|
const params = new URLSearchParams(window.location.search);
|
|
2282
2413
|
const paymentIntentId = params.get("payment_intent");
|
|
@@ -2404,33 +2535,21 @@ function PayPalButtonInner({
|
|
|
2404
2535
|
event.paymentFailed({ reason: "fail" });
|
|
2405
2536
|
return;
|
|
2406
2537
|
}
|
|
2407
|
-
const
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
});
|
|
2421
|
-
if (!intentResponse.ok) {
|
|
2422
|
-
const intentError = await buildFloPayApiErrorFromResponse(
|
|
2423
|
-
intentResponse,
|
|
2424
|
-
"Failed to create payment intent"
|
|
2425
|
-
);
|
|
2426
|
-
onErrorChange?.(intentError.message);
|
|
2427
|
-
onDecline?.(buildDeclineEvent("paypal", intentError));
|
|
2428
|
-
event.paymentFailed({ reason: "fail", message: intentError.message });
|
|
2429
|
-
return;
|
|
2538
|
+
const intent = await new PaymentAPI2(billingApiUrl).createSessionIntent(
|
|
2539
|
+
effectiveSessionId,
|
|
2540
|
+
effectiveNonce ?? "",
|
|
2541
|
+
{
|
|
2542
|
+
provider: "stripe",
|
|
2543
|
+
paymentMethodCategory: "wallet",
|
|
2544
|
+
paymentMethodType: "paypal",
|
|
2545
|
+
paymentMethodId: paymentMethod?.id ?? null,
|
|
2546
|
+
intentKind: "payment"
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
if (intent.provider !== "stripe") {
|
|
2550
|
+
throw new Error("Invalid provider returned for PayPal intent");
|
|
2430
2551
|
}
|
|
2431
|
-
const
|
|
2432
|
-
const intentClientSecret = intentJson.data?.id;
|
|
2433
|
-
if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
|
|
2552
|
+
const intentClientSecret = intent.clientSecret;
|
|
2434
2553
|
const confirmParams = {
|
|
2435
2554
|
return_url: window.location.href
|
|
2436
2555
|
};
|
|
@@ -2467,6 +2586,17 @@ function PayPalButtonInner({
|
|
|
2467
2586
|
code: getProviderErrorCode(confirmError),
|
|
2468
2587
|
declineCode: getProviderDeclineCode(confirmError)
|
|
2469
2588
|
}));
|
|
2589
|
+
await reportNonCardDecline({
|
|
2590
|
+
billingApiUrl,
|
|
2591
|
+
sessionId: effectiveSessionId,
|
|
2592
|
+
nonce: effectiveNonce,
|
|
2593
|
+
request: {
|
|
2594
|
+
provider: "stripe",
|
|
2595
|
+
paymentMethodCategory: "wallet",
|
|
2596
|
+
paymentMethodType: "paypal",
|
|
2597
|
+
providerDeclineReason: getProviderErrorCode(confirmError) ?? "provider_declined"
|
|
2598
|
+
}
|
|
2599
|
+
});
|
|
2470
2600
|
} else {
|
|
2471
2601
|
recoverTechnicalFailure(confirmError, "stripe_paypal_confirm_failed", attemptContext?.generation);
|
|
2472
2602
|
}
|
|
@@ -2495,15 +2625,15 @@ function PayPalButtonInner({
|
|
|
2495
2625
|
} finally {
|
|
2496
2626
|
setSubmitting(false);
|
|
2497
2627
|
}
|
|
2498
|
-
}, [stripe, elements, sessionId, nonce, email,
|
|
2499
|
-
return /* @__PURE__ */
|
|
2628
|
+
}, [stripe, elements, sessionId, nonce, email, billingApiUrl, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick, recoverTechnicalFailure, finishAttempt, isAttemptCurrent, isAttemptInvalidated, surfaceKey]);
|
|
2629
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2500
2630
|
/* @__PURE__ */ jsx7(
|
|
2501
2631
|
ExpressCheckoutReadySwap,
|
|
2502
2632
|
{
|
|
2503
2633
|
state: loadState,
|
|
2504
2634
|
placeholderTestId: "flopay-paypal-placeholder",
|
|
2505
2635
|
borderRadius: placeholderBorderRadius,
|
|
2506
|
-
children: /* @__PURE__ */
|
|
2636
|
+
children: /* @__PURE__ */ jsxs5(
|
|
2507
2637
|
"div",
|
|
2508
2638
|
{
|
|
2509
2639
|
ref: focusTargetRef,
|
|
@@ -2571,6 +2701,7 @@ function WalletButtonInner({
|
|
|
2571
2701
|
nonce,
|
|
2572
2702
|
email,
|
|
2573
2703
|
billingApiUrl,
|
|
2704
|
+
intentKind,
|
|
2574
2705
|
expressMethods,
|
|
2575
2706
|
onTokenizedBody,
|
|
2576
2707
|
onErrorChange,
|
|
@@ -2585,20 +2716,19 @@ function WalletButtonInner({
|
|
|
2585
2716
|
const stripe = useStripeRaw();
|
|
2586
2717
|
const elements = useStripeElements();
|
|
2587
2718
|
const [loadState, setLoadState] = useState3("loading");
|
|
2588
|
-
|
|
2719
|
+
useEffect4(() => {
|
|
2589
2720
|
onLoadStateChange?.(loadState);
|
|
2590
2721
|
}, [loadState, onLoadStateChange]);
|
|
2591
2722
|
const [submitting, setSubmitting] = useState3(false);
|
|
2592
|
-
const
|
|
2593
|
-
const
|
|
2594
|
-
const recoveryActionRef = useRef5(null);
|
|
2723
|
+
const focusTargetRef = useRef4(null);
|
|
2724
|
+
const recoveryActionRef = useRef4(null);
|
|
2595
2725
|
const [surfaceKey, setSurfaceKey] = useState3(0);
|
|
2596
2726
|
const [showRecoveryAction, setShowRecoveryAction] = useState3(false);
|
|
2597
2727
|
const [recoveryActionMethod, setRecoveryActionMethod] = useState3("google_pay");
|
|
2598
|
-
const pendingProviderFocusRef =
|
|
2599
|
-
const lastWalletProviderMethodRef =
|
|
2600
|
-
const lastWalletMethodRef =
|
|
2601
|
-
const attemptContextBySurfaceRef =
|
|
2728
|
+
const pendingProviderFocusRef = useRef4(false);
|
|
2729
|
+
const lastWalletProviderMethodRef = useRef4("google_pay");
|
|
2730
|
+
const lastWalletMethodRef = useRef4("google_pay");
|
|
2731
|
+
const attemptContextBySurfaceRef = useRef4(/* @__PURE__ */ new Map());
|
|
2602
2732
|
const {
|
|
2603
2733
|
startAttempt,
|
|
2604
2734
|
finishAttempt,
|
|
@@ -2614,7 +2744,7 @@ function WalletButtonInner({
|
|
|
2614
2744
|
setShowRecoveryAction(true);
|
|
2615
2745
|
setSurfaceKey((key) => key + 1);
|
|
2616
2746
|
});
|
|
2617
|
-
|
|
2747
|
+
useEffect4(() => {
|
|
2618
2748
|
if (showRecoveryAction) recoveryActionRef.current?.focus();
|
|
2619
2749
|
}, [showRecoveryAction, surfaceKey]);
|
|
2620
2750
|
const resetSurface = useCallback3(() => {
|
|
@@ -2633,7 +2763,7 @@ function WalletButtonInner({
|
|
|
2633
2763
|
(target ?? focusTargetRef.current)?.focus();
|
|
2634
2764
|
}, 0);
|
|
2635
2765
|
}, []);
|
|
2636
|
-
|
|
2766
|
+
useEffect4(() => {
|
|
2637
2767
|
if (!pendingProviderFocusRef.current) return;
|
|
2638
2768
|
pendingProviderFocusRef.current = false;
|
|
2639
2769
|
focusProviderSurface();
|
|
@@ -2690,31 +2820,21 @@ function WalletButtonInner({
|
|
|
2690
2820
|
if (!effectiveSessionId || !effectiveEmail) {
|
|
2691
2821
|
throw new Error("Missing sessionId or email for wallet payment");
|
|
2692
2822
|
}
|
|
2693
|
-
const
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
const intentError = await buildFloPayApiErrorFromResponse(
|
|
2707
|
-
intentResponse,
|
|
2708
|
-
"Failed to create payment intent"
|
|
2709
|
-
);
|
|
2710
|
-
onErrorChange?.(intentError.message);
|
|
2711
|
-
onDecline?.(buildDeclineEvent(method, intentError));
|
|
2712
|
-
event.paymentFailed({ reason: "fail", message: intentError.message });
|
|
2713
|
-
return;
|
|
2823
|
+
const intent = await new PaymentAPI2(billingApiUrl).createSessionIntent(
|
|
2824
|
+
effectiveSessionId,
|
|
2825
|
+
effectiveNonce ?? "",
|
|
2826
|
+
{
|
|
2827
|
+
provider: "stripe",
|
|
2828
|
+
paymentMethodCategory: "wallet",
|
|
2829
|
+
paymentMethodType: walletType ?? buttonMethod,
|
|
2830
|
+
paymentMethodId: paymentMethod.id,
|
|
2831
|
+
intentKind
|
|
2832
|
+
}
|
|
2833
|
+
);
|
|
2834
|
+
if (intent.provider !== "stripe") {
|
|
2835
|
+
throw new Error("Invalid provider returned for wallet intent");
|
|
2714
2836
|
}
|
|
2715
|
-
const
|
|
2716
|
-
const intentClientSecret = intentJson.data?.id;
|
|
2717
|
-
if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
|
|
2837
|
+
const intentClientSecret = intent.clientSecret;
|
|
2718
2838
|
const { error: confirmError, intentId } = isSetupIntentClientSecret2(intentClientSecret) ? await stripe.confirmCardSetup(intentClientSecret, { payment_method: paymentMethod.id }).then((r) => ({ error: r.error, intentId: r.setupIntent?.id })) : await stripe.confirmCardPayment(intentClientSecret, { payment_method: paymentMethod.id }).then((r) => ({ error: r.error, intentId: r.paymentIntent?.id }));
|
|
2719
2839
|
if (confirmError) {
|
|
2720
2840
|
if (isProviderDecline(confirmError)) {
|
|
@@ -2724,6 +2844,17 @@ function WalletButtonInner({
|
|
|
2724
2844
|
code: getProviderErrorCode(confirmError),
|
|
2725
2845
|
declineCode: getProviderDeclineCode(confirmError)
|
|
2726
2846
|
}));
|
|
2847
|
+
await reportNonCardDecline({
|
|
2848
|
+
billingApiUrl,
|
|
2849
|
+
sessionId: effectiveSessionId,
|
|
2850
|
+
nonce: effectiveNonce,
|
|
2851
|
+
request: {
|
|
2852
|
+
provider: "stripe",
|
|
2853
|
+
paymentMethodCategory: "wallet",
|
|
2854
|
+
paymentMethodType: walletType ?? buttonMethod,
|
|
2855
|
+
providerDeclineReason: getProviderErrorCode(confirmError) ?? "provider_declined"
|
|
2856
|
+
}
|
|
2857
|
+
});
|
|
2727
2858
|
} else {
|
|
2728
2859
|
recoverTechnicalFailure(walletType, confirmError, "stripe_wallet_confirm_failed", attemptContext?.generation);
|
|
2729
2860
|
}
|
|
@@ -2744,7 +2875,7 @@ function WalletButtonInner({
|
|
|
2744
2875
|
setSubmitting(false);
|
|
2745
2876
|
}
|
|
2746
2877
|
},
|
|
2747
|
-
[stripe, elements, sessionId, nonce, email,
|
|
2878
|
+
[stripe, elements, sessionId, nonce, email, billingApiUrl, intentKind, onTokenizedBody, onErrorChange, onDecline, runBeforeButtonClick, recoverTechnicalFailure, finishAttempt, isAttemptCurrent, isAttemptInvalidated, surfaceKey]
|
|
2748
2879
|
);
|
|
2749
2880
|
const expressMethodMap = useMemo3(() => {
|
|
2750
2881
|
const allKeys = ["applePay", "googlePay", "paypal", "link", "amazonPay", "klarna"];
|
|
@@ -2764,14 +2895,14 @@ function WalletButtonInner({
|
|
|
2764
2895
|
() => expressMethods.map(stripeExpressMethodToOptionKey),
|
|
2765
2896
|
[expressMethods]
|
|
2766
2897
|
);
|
|
2767
|
-
return /* @__PURE__ */
|
|
2898
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
2768
2899
|
/* @__PURE__ */ jsx7(
|
|
2769
2900
|
ExpressCheckoutReadySwap,
|
|
2770
2901
|
{
|
|
2771
2902
|
state: loadState,
|
|
2772
2903
|
placeholderTestId: "flopay-wallet-placeholder",
|
|
2773
2904
|
borderRadius: placeholderBorderRadius,
|
|
2774
|
-
children: /* @__PURE__ */
|
|
2905
|
+
children: /* @__PURE__ */ jsxs5(
|
|
2775
2906
|
"div",
|
|
2776
2907
|
{
|
|
2777
2908
|
ref: focusTargetRef,
|
|
@@ -2780,7 +2911,7 @@ function WalletButtonInner({
|
|
|
2780
2911
|
"aria-label": "Wallet payment methods",
|
|
2781
2912
|
style: { borderRadius: 8, outlineOffset: 4 },
|
|
2782
2913
|
children: [
|
|
2783
|
-
showRecoveryAction && /* @__PURE__ */
|
|
2914
|
+
showRecoveryAction && /* @__PURE__ */ jsxs5(
|
|
2784
2915
|
"button",
|
|
2785
2916
|
{
|
|
2786
2917
|
ref: recoveryActionRef,
|
|
@@ -2877,6 +3008,7 @@ function StripeMethodButton({
|
|
|
2877
3008
|
fontFamily
|
|
2878
3009
|
}) {
|
|
2879
3010
|
const brand = resolveStripeMethodBrandVariant(method, themeId);
|
|
3011
|
+
const vendoredLogoUrl = PAYMENT_METHOD_LOGO_URLS[method];
|
|
2880
3012
|
const resolvedBackground = brand?.backgroundColor ?? backgroundColor ?? "#ffffff";
|
|
2881
3013
|
const resolvedBorder = brand?.borderColor ?? borderColor ?? "#d1d5db";
|
|
2882
3014
|
const resolvedTextColor = brand?.textColor ?? textColor ?? "#262833";
|
|
@@ -2928,8 +3060,8 @@ function StripeMethodButton({
|
|
|
2928
3060
|
// status text on top of the pulsing background. Keeps the loading
|
|
2929
3061
|
// affordance shape-equivalent across both kinds of tile.
|
|
2930
3062
|
/* @__PURE__ */ jsx7("span", { style: { margin: "0 auto" }, children: `Connecting to ${getStripeMethodDisplayName(method)}\u2026` })
|
|
2931
|
-
) : /* @__PURE__ */
|
|
2932
|
-
/* @__PURE__ */
|
|
3063
|
+
) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
3064
|
+
/* @__PURE__ */ jsxs5(
|
|
2933
3065
|
"span",
|
|
2934
3066
|
{
|
|
2935
3067
|
style: {
|
|
@@ -2942,15 +3074,36 @@ function StripeMethodButton({
|
|
|
2942
3074
|
gap: hasVendoredStripeMethodLogo(method) ? 8 : 0
|
|
2943
3075
|
},
|
|
2944
3076
|
children: [
|
|
2945
|
-
|
|
3077
|
+
vendoredLogoUrl ? (
|
|
3078
|
+
// Vendored brand logo — an <img> asset (not inline SVG) so its
|
|
3079
|
+
// bytes ship as a sibling file, out of the JS bundle. 3:2 box
|
|
3080
|
+
// matching the datatrans logos' 120×80 viewBox; each ships its own
|
|
3081
|
+
// white rounded-rect background so it stays legible on any tile.
|
|
3082
|
+
/* @__PURE__ */ jsx7(
|
|
3083
|
+
"img",
|
|
3084
|
+
{
|
|
3085
|
+
src: vendoredLogoUrl,
|
|
3086
|
+
alt: "",
|
|
3087
|
+
"aria-hidden": "true",
|
|
3088
|
+
loading: "lazy",
|
|
3089
|
+
decoding: "async",
|
|
3090
|
+
width: 30,
|
|
3091
|
+
height: 20,
|
|
3092
|
+
style: {
|
|
3093
|
+
width: 30,
|
|
3094
|
+
height: 20,
|
|
3095
|
+
flexShrink: 0,
|
|
3096
|
+
borderRadius: 3,
|
|
3097
|
+
objectFit: "contain",
|
|
3098
|
+
display: "inline-flex"
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
)
|
|
3102
|
+
) : brand?.logoSvg ? /* @__PURE__ */ jsx7(
|
|
2946
3103
|
"span",
|
|
2947
3104
|
{
|
|
2948
3105
|
"aria-hidden": "true",
|
|
2949
3106
|
style: {
|
|
2950
|
-
// 3:2 box matching the vendored datatrans logos' 120×80
|
|
2951
|
-
// viewBox. Each logo ships with its own white rounded-rect
|
|
2952
|
-
// background baked in, so the mark stays legible on any
|
|
2953
|
-
// brand-coloured tile without an extra chip wrapper here.
|
|
2954
3107
|
width: 30,
|
|
2955
3108
|
height: 20,
|
|
2956
3109
|
flexShrink: 0,
|
|
@@ -2960,7 +3113,7 @@ function StripeMethodButton({
|
|
|
2960
3113
|
},
|
|
2961
3114
|
dangerouslySetInnerHTML: { __html: brand.logoSvg }
|
|
2962
3115
|
}
|
|
2963
|
-
),
|
|
3116
|
+
) : null,
|
|
2964
3117
|
/* @__PURE__ */ jsx7("span", { children: getStripeMethodDisplayName(method) })
|
|
2965
3118
|
]
|
|
2966
3119
|
}
|
|
@@ -3015,10 +3168,9 @@ function StripeMethodInlineForm({
|
|
|
3015
3168
|
const stripe = useStripeRaw();
|
|
3016
3169
|
const elements = useStripeElements();
|
|
3017
3170
|
const [submitting, setSubmitting] = useState3(false);
|
|
3018
|
-
const submittingRef =
|
|
3171
|
+
const submittingRef = useRef4(false);
|
|
3019
3172
|
const [isMethodComplete, setIsMethodComplete] = useState3(false);
|
|
3020
3173
|
const [loadState, setLoadState] = useState3("loading");
|
|
3021
|
-
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
3022
3174
|
const handlePay = useCallback3(async () => {
|
|
3023
3175
|
if (!stripe || !elements || isProcessing || submittingRef.current || !isMethodComplete) return;
|
|
3024
3176
|
submittingRef.current = true;
|
|
@@ -3049,27 +3201,22 @@ function StripeMethodInlineForm({
|
|
|
3049
3201
|
if (!effectiveSessionId || !effectiveEmail) {
|
|
3050
3202
|
throw new Error("Missing sessionId or email for payment.");
|
|
3051
3203
|
}
|
|
3052
|
-
const
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
}
|
|
3063
|
-
|
|
3064
|
-
if (
|
|
3065
|
-
|
|
3066
|
-
onErrorChange?.(intentError.message);
|
|
3067
|
-
onDecline?.(buildDeclineEvent("card", intentError));
|
|
3068
|
-
return;
|
|
3204
|
+
const paymentMethodType = paymentMethod.type || method;
|
|
3205
|
+
const intent = await new PaymentAPI2(billingApiUrl).createSessionIntent(
|
|
3206
|
+
effectiveSessionId,
|
|
3207
|
+
effectiveNonce ?? "",
|
|
3208
|
+
{
|
|
3209
|
+
provider: "stripe",
|
|
3210
|
+
paymentMethodCategory: "apm",
|
|
3211
|
+
paymentMethodType,
|
|
3212
|
+
paymentMethodId: null,
|
|
3213
|
+
intentKind: "payment"
|
|
3214
|
+
}
|
|
3215
|
+
);
|
|
3216
|
+
if (intent.provider !== "stripe") {
|
|
3217
|
+
throw new Error("Invalid provider returned for APM intent");
|
|
3069
3218
|
}
|
|
3070
|
-
const
|
|
3071
|
-
const intentClientSecret = intentJson.data?.id;
|
|
3072
|
-
if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
|
|
3219
|
+
const intentClientSecret = intent.clientSecret;
|
|
3073
3220
|
if (typeof window !== "undefined") {
|
|
3074
3221
|
try {
|
|
3075
3222
|
localStorage.setItem(STRIPE_RESUME_KEY, JSON.stringify({
|
|
@@ -3102,11 +3249,21 @@ function StripeMethodInlineForm({
|
|
|
3102
3249
|
const message = confirmError.message ?? "Payment failed.";
|
|
3103
3250
|
onErrorChange?.(message);
|
|
3104
3251
|
onDecline?.(buildDeclineEvent("card", message, { code: confirmError.code }));
|
|
3252
|
+
await reportNonCardDecline({
|
|
3253
|
+
billingApiUrl,
|
|
3254
|
+
sessionId: effectiveSessionId,
|
|
3255
|
+
nonce: effectiveNonce,
|
|
3256
|
+
request: {
|
|
3257
|
+
provider: "stripe",
|
|
3258
|
+
paymentMethodCategory: "apm",
|
|
3259
|
+
paymentMethodType: method,
|
|
3260
|
+
providerDeclineReason: confirmError.code ?? "provider_declined"
|
|
3261
|
+
}
|
|
3262
|
+
});
|
|
3105
3263
|
return;
|
|
3106
3264
|
}
|
|
3107
|
-
const SUCCESSFUL_PI_STATUSES = /* @__PURE__ */ new Set(["succeeded", "requires_capture", "processing"]);
|
|
3108
3265
|
const piStatus = paymentIntent?.status;
|
|
3109
|
-
if (!
|
|
3266
|
+
if (!isSuccessfulPaymentIntentStatus(piStatus)) {
|
|
3110
3267
|
if (typeof window !== "undefined") {
|
|
3111
3268
|
try {
|
|
3112
3269
|
localStorage.removeItem(STRIPE_RESUME_KEY);
|
|
@@ -3152,16 +3309,16 @@ function StripeMethodInlineForm({
|
|
|
3152
3309
|
sessionId,
|
|
3153
3310
|
nonce,
|
|
3154
3311
|
email,
|
|
3155
|
-
|
|
3312
|
+
billingApiUrl,
|
|
3156
3313
|
method,
|
|
3157
3314
|
onTokenizedBody,
|
|
3158
3315
|
onErrorChange,
|
|
3159
3316
|
onDecline
|
|
3160
3317
|
]);
|
|
3161
3318
|
void onCancel;
|
|
3162
|
-
return /* @__PURE__ */
|
|
3319
|
+
return /* @__PURE__ */ jsxs5("div", { "data-testid": `flopay-stripe-method-form-${method}`, style: { display: "flex", flexDirection: "column" }, children: [
|
|
3163
3320
|
/* @__PURE__ */ jsx7(
|
|
3164
|
-
|
|
3321
|
+
PaymentElement,
|
|
3165
3322
|
{
|
|
3166
3323
|
onReady: () => setLoadState("ready"),
|
|
3167
3324
|
onLoadError: () => {
|
|
@@ -3216,22 +3373,7 @@ function StripeMethodInlineForm({
|
|
|
3216
3373
|
children: submitting ? "Processing\u2026" : `Pay with ${getStripeMethodDisplayName(method)}`
|
|
3217
3374
|
}
|
|
3218
3375
|
),
|
|
3219
|
-
errorText && /* @__PURE__ */
|
|
3220
|
-
margin: "0.75rem 0 0",
|
|
3221
|
-
padding: "0.625rem 0.875rem",
|
|
3222
|
-
background: "#FEF2F2",
|
|
3223
|
-
border: "1px solid #FECACA",
|
|
3224
|
-
borderRadius: "8px",
|
|
3225
|
-
color: "#991B1B",
|
|
3226
|
-
fontSize: "0.85rem",
|
|
3227
|
-
fontWeight: 600,
|
|
3228
|
-
display: "flex",
|
|
3229
|
-
alignItems: "center",
|
|
3230
|
-
gap: "0.5rem"
|
|
3231
|
-
}, children: [
|
|
3232
|
-
/* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx7("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" }) }),
|
|
3233
|
-
errorText
|
|
3234
|
-
] })
|
|
3376
|
+
errorText && /* @__PURE__ */ jsx7(ErrorBanner, { margin: "0.75rem 0 0", children: errorText })
|
|
3235
3377
|
] });
|
|
3236
3378
|
}
|
|
3237
3379
|
function StripePaymentElementInner({
|
|
@@ -3258,11 +3400,10 @@ function StripePaymentElementInner({
|
|
|
3258
3400
|
onExpandApm,
|
|
3259
3401
|
expandedApmMethod
|
|
3260
3402
|
}) {
|
|
3261
|
-
const baseUrl = billingApiUrl.replace(/\/+$/, "");
|
|
3262
3403
|
const [expandedMethod, setExpandedMethod] = useState3(null);
|
|
3263
3404
|
const [submittingMethod, setSubmittingMethod] = useState3(null);
|
|
3264
|
-
const submittingRef =
|
|
3265
|
-
|
|
3405
|
+
const submittingRef = useRef4(false);
|
|
3406
|
+
useEffect4(() => {
|
|
3266
3407
|
if (paymentElementMethods.length > 0 && stripeInstance) {
|
|
3267
3408
|
onLoadStateChange?.("ready");
|
|
3268
3409
|
} else if (!stripeInstance) {
|
|
@@ -3289,31 +3430,21 @@ function StripePaymentElementInner({
|
|
|
3289
3430
|
if (!effectiveSessionId || !effectiveEmail) {
|
|
3290
3431
|
throw new Error("Missing sessionId or email for payment.");
|
|
3291
3432
|
}
|
|
3292
|
-
const
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
sessionId: effectiveSessionId,
|
|
3299
|
-
email: effectiveEmail,
|
|
3300
|
-
// Wire type, not pm_xxx — backend scopes `payment_method_types`
|
|
3301
|
-
// to this single method so unrelated account-wide methods
|
|
3302
|
-
// (apple_pay without domain verification, …) don't poison PI
|
|
3303
|
-
// creation.
|
|
3433
|
+
const intent = await new PaymentAPI2(billingApiUrl).createSessionIntent(
|
|
3434
|
+
effectiveSessionId,
|
|
3435
|
+
effectiveNonce ?? "",
|
|
3436
|
+
{
|
|
3437
|
+
provider: "stripe",
|
|
3438
|
+
paymentMethodCategory: "apm",
|
|
3304
3439
|
paymentMethodType: method,
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
onDecline?.(buildDeclineEvent("card", intentError));
|
|
3312
|
-
return;
|
|
3440
|
+
paymentMethodId: null,
|
|
3441
|
+
intentKind: "payment"
|
|
3442
|
+
}
|
|
3443
|
+
);
|
|
3444
|
+
if (intent.provider !== "stripe") {
|
|
3445
|
+
throw new Error("Invalid provider returned for APM intent");
|
|
3313
3446
|
}
|
|
3314
|
-
const
|
|
3315
|
-
const intentClientSecret = intentJson.data?.id;
|
|
3316
|
-
if (!intentClientSecret) throw new Error("No client_secret in payment intent response");
|
|
3447
|
+
const intentClientSecret = intent.clientSecret;
|
|
3317
3448
|
if (typeof window !== "undefined") {
|
|
3318
3449
|
try {
|
|
3319
3450
|
localStorage.setItem(STRIPE_RESUME_KEY, JSON.stringify({
|
|
@@ -3354,11 +3485,21 @@ function StripePaymentElementInner({
|
|
|
3354
3485
|
const message = confirmError.message ?? "Payment failed.";
|
|
3355
3486
|
onErrorChange?.(message);
|
|
3356
3487
|
onDecline?.(buildDeclineEvent("card", message, { code: confirmError.code }));
|
|
3488
|
+
await reportNonCardDecline({
|
|
3489
|
+
billingApiUrl,
|
|
3490
|
+
sessionId: effectiveSessionId,
|
|
3491
|
+
nonce: effectiveNonce,
|
|
3492
|
+
request: {
|
|
3493
|
+
provider: "stripe",
|
|
3494
|
+
paymentMethodCategory: "apm",
|
|
3495
|
+
paymentMethodType: method,
|
|
3496
|
+
providerDeclineReason: confirmError.code ?? "provider_declined"
|
|
3497
|
+
}
|
|
3498
|
+
});
|
|
3357
3499
|
return;
|
|
3358
3500
|
}
|
|
3359
|
-
const SUCCESSFUL_PI_STATUSES = /* @__PURE__ */ new Set(["succeeded", "requires_capture", "processing"]);
|
|
3360
3501
|
const piStatus = paymentIntent?.status;
|
|
3361
|
-
if (!
|
|
3502
|
+
if (!isSuccessfulPaymentIntentStatus(piStatus)) {
|
|
3362
3503
|
if (typeof window !== "undefined") {
|
|
3363
3504
|
try {
|
|
3364
3505
|
localStorage.removeItem(STRIPE_RESUME_KEY);
|
|
@@ -3402,7 +3543,7 @@ function StripePaymentElementInner({
|
|
|
3402
3543
|
sessionId,
|
|
3403
3544
|
nonce,
|
|
3404
3545
|
email,
|
|
3405
|
-
|
|
3546
|
+
billingApiUrl,
|
|
3406
3547
|
billingName,
|
|
3407
3548
|
onTokenizedBody,
|
|
3408
3549
|
onErrorChange,
|
|
@@ -3439,7 +3580,7 @@ function StripePaymentElementInner({
|
|
|
3439
3580
|
const isExpanded = expandedApmMethod === method;
|
|
3440
3581
|
const isSubmittingThis = submittingMethod === method;
|
|
3441
3582
|
const otherInProgress = submittingMethod !== null && submittingMethod !== method || expandedApmMethod !== null && expandedApmMethod !== void 0 && expandedApmMethod !== method;
|
|
3442
|
-
return /* @__PURE__ */
|
|
3583
|
+
return /* @__PURE__ */ jsxs5(React7.Fragment, { children: [
|
|
3443
3584
|
/* @__PURE__ */ jsx7(
|
|
3444
3585
|
StripeMethodButton,
|
|
3445
3586
|
{
|
|
@@ -3503,15 +3644,10 @@ function SplitCardFormInner({
|
|
|
3503
3644
|
firstName,
|
|
3504
3645
|
lastName,
|
|
3505
3646
|
chv,
|
|
3506
|
-
submitLabel = "CONFIRM PAYMENT",
|
|
3507
3647
|
className,
|
|
3508
|
-
children,
|
|
3509
3648
|
isProcessing: externalProcessing,
|
|
3510
3649
|
error: externalError,
|
|
3511
3650
|
onErrorChange,
|
|
3512
|
-
onFullNameChange,
|
|
3513
|
-
onFirstNameChange,
|
|
3514
|
-
onLastNameChange,
|
|
3515
3651
|
showPayPal = true,
|
|
3516
3652
|
showStripe = true,
|
|
3517
3653
|
enabledPaymentMethods,
|
|
@@ -3550,17 +3686,14 @@ function SplitCardFormInner({
|
|
|
3550
3686
|
directPaypal,
|
|
3551
3687
|
isSubscription = false,
|
|
3552
3688
|
session,
|
|
3553
|
-
debug = false
|
|
3554
|
-
innerRef
|
|
3689
|
+
debug = false
|
|
3555
3690
|
}) {
|
|
3556
3691
|
const flopay = useFloPay();
|
|
3557
3692
|
const paypalFlopay = usePayPalFloPay();
|
|
3558
|
-
const
|
|
3559
|
-
const checkout = useContext3(CheckoutContext);
|
|
3693
|
+
const checkout = useContext2(CheckoutContext);
|
|
3560
3694
|
const contextBillingUrl = useBillingApiUrl();
|
|
3561
3695
|
const [processing, setProcessing] = useState3(false);
|
|
3562
3696
|
const [error, setError] = useState3(null);
|
|
3563
|
-
const [is3DSActive, setIs3DSActive] = useState3(false);
|
|
3564
3697
|
const [selectedCountry, setSelectedCountry] = useState3(countryProp ?? "US");
|
|
3565
3698
|
const [zipCode, setZipCode] = useState3(zipProp ?? "");
|
|
3566
3699
|
const [addressLine1, setAddressLine1] = useState3(addressLine1Prop ?? "");
|
|
@@ -3568,18 +3701,19 @@ function SplitCardFormInner({
|
|
|
3568
3701
|
const [city, setCity] = useState3(cityProp ?? "");
|
|
3569
3702
|
const [stateValue, setStateValue] = useState3(stateProp ?? "");
|
|
3570
3703
|
const [accountPatch, setAccountPatch] = useState3({});
|
|
3571
|
-
const zipCodeRef =
|
|
3572
|
-
const selectedCountryRef =
|
|
3573
|
-
const addressLine1Ref =
|
|
3574
|
-
const addressLine2Ref =
|
|
3575
|
-
const cityRef =
|
|
3576
|
-
const stateRef =
|
|
3704
|
+
const zipCodeRef = useRef4(zipProp ?? "");
|
|
3705
|
+
const selectedCountryRef = useRef4(countryProp ?? "US");
|
|
3706
|
+
const addressLine1Ref = useRef4(addressLine1Prop ?? "");
|
|
3707
|
+
const addressLine2Ref = useRef4(addressLine2Prop ?? "");
|
|
3708
|
+
const cityRef = useRef4(cityProp ?? "");
|
|
3709
|
+
const stateRef = useRef4(stateProp ?? "");
|
|
3577
3710
|
const avsConfig = useMemo3(() => resolveAVSConfig(enableAVSProp), [enableAVSProp]);
|
|
3578
3711
|
const enableAVS = avsConfig !== null;
|
|
3579
3712
|
const vaultBlockReady = Boolean(session?.vault?.html);
|
|
3580
3713
|
const vaultGatewayAdvertised = Boolean(session?.gateways?.pcivault);
|
|
3714
|
+
const vaultCapabilityAvailable = vaultBlockReady || vaultGatewayAdvertised;
|
|
3581
3715
|
const vaultActive = Boolean(
|
|
3582
|
-
showStripe &&
|
|
3716
|
+
showStripe && vaultCapabilityAvailable && flopay && sessionId
|
|
3583
3717
|
);
|
|
3584
3718
|
const cardCapture = useMemo3(() => {
|
|
3585
3719
|
if (!flopay || !vaultActive || !sessionId) return null;
|
|
@@ -3611,7 +3745,9 @@ function SplitCardFormInner({
|
|
|
3611
3745
|
const [vaultMount, setVaultMount] = useState3(
|
|
3612
3746
|
() => toVaultMount(session?.vault)
|
|
3613
3747
|
);
|
|
3614
|
-
const [viewState, setViewState] = useState3(
|
|
3748
|
+
const [viewState, setViewState] = useState3(
|
|
3749
|
+
initialCardOpen && vaultCapabilityAvailable ? "card" : "buttons"
|
|
3750
|
+
);
|
|
3615
3751
|
const [expandedApmMethod, setExpandedApmMethod] = useState3(null);
|
|
3616
3752
|
const showCardForm = viewState === "expanding" || viewState === "card";
|
|
3617
3753
|
const TRANSITION_MS = 280;
|
|
@@ -3635,18 +3771,16 @@ function SplitCardFormInner({
|
|
|
3635
3771
|
setExpandedApmMethod(null);
|
|
3636
3772
|
}, TRANSITION_MS);
|
|
3637
3773
|
}, []);
|
|
3638
|
-
|
|
3639
|
-
if (layout === "buttons" && initialCardOpen) {
|
|
3774
|
+
useEffect4(() => {
|
|
3775
|
+
if (layout === "buttons" && initialCardOpen && vaultCapabilityAvailable) {
|
|
3640
3776
|
setViewState("card");
|
|
3641
3777
|
}
|
|
3642
|
-
}, [layout, initialCardOpen]);
|
|
3643
|
-
const [fullName, setFullName] = useState3("");
|
|
3644
|
-
const [formReady, setFormReady] = useState3(false);
|
|
3778
|
+
}, [layout, initialCardOpen, vaultCapabilityAvailable]);
|
|
3645
3779
|
const [overlayStatus, setOverlayStatus] = useState3(null);
|
|
3646
|
-
const processingRef =
|
|
3780
|
+
const processingRef = useRef4(false);
|
|
3647
3781
|
const [paypalDirectRetry, setPaypalDirectRetry] = useState3(null);
|
|
3648
|
-
const paypalDirectRetryRef =
|
|
3649
|
-
|
|
3782
|
+
const paypalDirectRetryRef = useRef4(paypalDirectRetry);
|
|
3783
|
+
useEffect4(() => {
|
|
3650
3784
|
paypalDirectRetryRef.current = paypalDirectRetry;
|
|
3651
3785
|
}, [paypalDirectRetry]);
|
|
3652
3786
|
const resolvedBillingApiUrl = billingApiUrl || contextBillingUrl;
|
|
@@ -3694,7 +3828,6 @@ function SplitCardFormInner({
|
|
|
3694
3828
|
}, [appearance, bStyles, layout]);
|
|
3695
3829
|
const isInlineSessionPatchProcessing = checkout.inlineSessionPatchProcessing ?? false;
|
|
3696
3830
|
const isSubmitting = (externalProcessing ?? processing) || isInlineSessionPatchProcessing;
|
|
3697
|
-
const isSelfContained = !onTokenizedBody;
|
|
3698
3831
|
const baseUrl = resolvedBillingApiUrl.replace(/\/+$/, "");
|
|
3699
3832
|
const resolvedAccount = useMemo3(() => mergeAccountPatch({
|
|
3700
3833
|
userId,
|
|
@@ -3704,6 +3837,7 @@ function SplitCardFormInner({
|
|
|
3704
3837
|
country: countryProp,
|
|
3705
3838
|
zip: zipProp
|
|
3706
3839
|
}, accountPatch), [userId, email, firstName, lastName, countryProp, zipProp, accountPatch]);
|
|
3840
|
+
const resolvedBillingName = [resolvedAccount.firstName, resolvedAccount.lastName].filter((part) => Boolean(part?.trim())).join(" ");
|
|
3707
3841
|
const stripeInstance = useMemo3(() => {
|
|
3708
3842
|
if (!flopay) return null;
|
|
3709
3843
|
return flopay.getRawProvider();
|
|
@@ -3760,11 +3894,11 @@ function SplitCardFormInner({
|
|
|
3760
3894
|
},
|
|
3761
3895
|
[onErrorChange]
|
|
3762
3896
|
);
|
|
3763
|
-
|
|
3897
|
+
useEffect4(() => {
|
|
3764
3898
|
if (!vaultActive || !cardCapture) return;
|
|
3765
3899
|
cardCapture.setCardFieldOrder?.(cardFieldOrder ?? null, avsConfig == null);
|
|
3766
3900
|
}, [vaultActive, cardCapture, cardFieldOrder, avsConfig]);
|
|
3767
|
-
|
|
3901
|
+
useEffect4(() => {
|
|
3768
3902
|
if (viewState === "expanding" || viewState === "collapsing" || viewState === "apm-expanding" || viewState === "apm-collapsing") {
|
|
3769
3903
|
updateError(null);
|
|
3770
3904
|
}
|
|
@@ -3799,7 +3933,7 @@ function SplitCardFormInner({
|
|
|
3799
3933
|
},
|
|
3800
3934
|
[layout, onError, updateError]
|
|
3801
3935
|
);
|
|
3802
|
-
|
|
3936
|
+
useEffect4(() => {
|
|
3803
3937
|
if (!vaultActive || !sessionId) {
|
|
3804
3938
|
setVaultMount(null);
|
|
3805
3939
|
return;
|
|
@@ -3837,13 +3971,12 @@ function SplitCardFormInner({
|
|
|
3837
3971
|
nonce,
|
|
3838
3972
|
updateError
|
|
3839
3973
|
]);
|
|
3840
|
-
const vaultOutcomeRef =
|
|
3974
|
+
const vaultOutcomeRef = useRef4({
|
|
3841
3975
|
onComplete,
|
|
3842
3976
|
onError,
|
|
3843
3977
|
updateError,
|
|
3844
3978
|
emitDecline,
|
|
3845
3979
|
resolvedAccount,
|
|
3846
|
-
fullName,
|
|
3847
3980
|
avsConfig,
|
|
3848
3981
|
avsCheckProp,
|
|
3849
3982
|
sessionId,
|
|
@@ -3856,16 +3989,15 @@ function SplitCardFormInner({
|
|
|
3856
3989
|
updateError,
|
|
3857
3990
|
emitDecline,
|
|
3858
3991
|
resolvedAccount,
|
|
3859
|
-
fullName,
|
|
3860
3992
|
avsConfig,
|
|
3861
3993
|
avsCheckProp,
|
|
3862
3994
|
sessionId,
|
|
3863
3995
|
nonce,
|
|
3864
3996
|
baseUrl
|
|
3865
3997
|
};
|
|
3866
|
-
const vaultCompletedRef =
|
|
3998
|
+
const vaultCompletedRef = useRef4(false);
|
|
3867
3999
|
const buildVaultAccountSnapshot = useCallback3(() => {
|
|
3868
|
-
const { resolvedAccount: resolvedAccount2, avsConfig: avsConfig2,
|
|
4000
|
+
const { resolvedAccount: resolvedAccount2, avsConfig: avsConfig2, avsCheckProp: avsCheckProp2 } = vaultOutcomeRef.current;
|
|
3869
4001
|
const cc = selectedCountryRef.current || resolvedAccount2.country || "US";
|
|
3870
4002
|
const stateVisible = avsConfig2 ? isAVSFieldVisible(avsConfig2.state, cc) : false;
|
|
3871
4003
|
const line1Visible = avsConfig2 ? isAVSFieldVisible(avsConfig2.address_line_1, cc) : false;
|
|
@@ -3878,8 +4010,8 @@ function SplitCardFormInner({
|
|
|
3878
4010
|
accountData: {
|
|
3879
4011
|
userId: resolvedAccount2.userId ?? "",
|
|
3880
4012
|
email: resolvedAccount2.email ?? "",
|
|
3881
|
-
firstName: resolvedAccount2.firstName ??
|
|
3882
|
-
lastName: resolvedAccount2.lastName ??
|
|
4013
|
+
firstName: resolvedAccount2.firstName ?? "",
|
|
4014
|
+
lastName: resolvedAccount2.lastName ?? "",
|
|
3883
4015
|
...zipVisible && zipCodeRef.current ? { zip: zipCodeRef.current } : {},
|
|
3884
4016
|
...cityVisible && cityRef.current ? { city: cityRef.current } : {},
|
|
3885
4017
|
...stateValue2 ? { state: stateValue2 } : {},
|
|
@@ -3900,11 +4032,11 @@ function SplitCardFormInner({
|
|
|
3900
4032
|
} : {}
|
|
3901
4033
|
};
|
|
3902
4034
|
}, []);
|
|
3903
|
-
|
|
4035
|
+
useEffect4(() => {
|
|
3904
4036
|
if (!vaultActive || !cardCapture) return;
|
|
3905
4037
|
cardCapture.setSubmitGate?.(avsInvalid);
|
|
3906
4038
|
}, [vaultActive, cardCapture, avsInvalid]);
|
|
3907
|
-
|
|
4039
|
+
useEffect4(() => {
|
|
3908
4040
|
if (!vaultActive || !cardCapture) return;
|
|
3909
4041
|
vaultCompletedRef.current = false;
|
|
3910
4042
|
let cancelled = false;
|
|
@@ -4007,21 +4139,25 @@ function SplitCardFormInner({
|
|
|
4007
4139
|
const [walletLoadState, setWalletLoadState] = useState3("loading");
|
|
4008
4140
|
const [paymentElementLoadState, setPaymentElementLoadState] = useState3("loading");
|
|
4009
4141
|
const [directPaypalReady, setDirectPaypalReady] = useState3(false);
|
|
4142
|
+
const [directPaypalInitializationState, setDirectPaypalInitializationState] = useState3("loading");
|
|
4010
4143
|
const [inAppBrowserDetected, setInAppBrowserDetected] = useState3();
|
|
4011
|
-
|
|
4144
|
+
useEffect4(() => {
|
|
4012
4145
|
setInAppBrowserDetected(isInAppBrowser());
|
|
4013
4146
|
}, []);
|
|
4014
4147
|
const shouldShowPayPal = showPayPal && (directPaypalConfigured || (hasEnabledMethodsPayload ? paypalEnabled : inAppBrowserDetected === false));
|
|
4015
4148
|
const shouldShowWallets = showStripe && showWallets;
|
|
4016
4149
|
const shouldRenderDirectPayPal = shouldShowPayPal && directPaypalConfigured;
|
|
4017
|
-
const
|
|
4150
|
+
const directPaypalUnavailable = directPaypalInitializationState === "retrying" || directPaypalInitializationState === "exhausted";
|
|
4151
|
+
const shouldRenderStripePayPal = shouldShowPayPal && !!paypalStripeInstance && (!directPaypalConfigured || directPaypalUnavailable);
|
|
4018
4152
|
const shouldRenderWallets = shouldShowWallets && !!stripeInstance;
|
|
4019
4153
|
const shouldRenderPaymentElement = showStripe && hasEnabledMethods && paymentElementMethodsForCurrency.length > 0 && !!stripeInstance;
|
|
4020
|
-
const shouldDisplayPayPalRow = shouldRenderDirectPayPal ? directPaypalReady : shouldRenderStripePayPal && isExpressCheckoutRowVisible(paypalLoadState);
|
|
4154
|
+
const shouldDisplayPayPalRow = shouldRenderDirectPayPal && !directPaypalUnavailable ? directPaypalReady : shouldRenderStripePayPal && isExpressCheckoutRowVisible(paypalLoadState);
|
|
4021
4155
|
const shouldDisplayWalletRow = shouldRenderWallets && isExpressCheckoutRowVisible(walletLoadState);
|
|
4022
4156
|
const shouldDisplayPaymentElementRow = shouldRenderPaymentElement && paymentElementLoadState !== "load_error";
|
|
4023
|
-
const
|
|
4024
|
-
|
|
4157
|
+
const expressSurfaceUsable = (state) => state === "loading" || state === "ready";
|
|
4158
|
+
const hasUsableNonCardCapability = shouldRenderDirectPayPal || shouldRenderStripePayPal && expressSurfaceUsable(paypalLoadState) || shouldRenderWallets && expressSurfaceUsable(walletLoadState) || shouldRenderPaymentElement && paymentElementLoadState !== "load_error";
|
|
4159
|
+
const validationFiredRef = useRef4(false);
|
|
4160
|
+
useEffect4(() => {
|
|
4025
4161
|
if (validationFiredRef.current) return;
|
|
4026
4162
|
if (!showStripe && !showPayPal) {
|
|
4027
4163
|
validationFiredRef.current = true;
|
|
@@ -4039,10 +4175,29 @@ function SplitCardFormInner({
|
|
|
4039
4175
|
);
|
|
4040
4176
|
onError?.(err);
|
|
4041
4177
|
updateError(err.message);
|
|
4178
|
+
} else if (inAppBrowserDetected !== void 0 && !vaultCapabilityAvailable && !hasUsableNonCardCapability) {
|
|
4179
|
+
validationFiredRef.current = true;
|
|
4180
|
+
const err = new FloPayError3(
|
|
4181
|
+
"FloPay: this checkout session has no vault card capability and no usable supported non-card payment methods. Upgrade the billing backend integration.",
|
|
4182
|
+
"api_error",
|
|
4183
|
+
{ code: "UnsupportedBackendVaultCapability" }
|
|
4184
|
+
);
|
|
4185
|
+
onError?.(err);
|
|
4186
|
+
updateError(err.message);
|
|
4042
4187
|
}
|
|
4043
|
-
}, [
|
|
4044
|
-
|
|
4045
|
-
|
|
4188
|
+
}, [
|
|
4189
|
+
showStripe,
|
|
4190
|
+
showPayPal,
|
|
4191
|
+
directPaypalConfigured,
|
|
4192
|
+
paypalStripeInstance,
|
|
4193
|
+
inAppBrowserDetected,
|
|
4194
|
+
vaultCapabilityAvailable,
|
|
4195
|
+
hasUsableNonCardCapability,
|
|
4196
|
+
onError,
|
|
4197
|
+
updateError
|
|
4198
|
+
]);
|
|
4199
|
+
const deprecationLoggedRef = useRef4(false);
|
|
4200
|
+
useEffect4(() => {
|
|
4046
4201
|
if (deprecationLoggedRef.current) return;
|
|
4047
4202
|
if (!hasEnabledMethods) return;
|
|
4048
4203
|
const stale = [];
|
|
@@ -4054,13 +4209,6 @@ function SplitCardFormInner({
|
|
|
4054
4209
|
`[FloPay] ${stale.join(" / ")} ${stale.length === 1 ? "is" : "are"} deprecated: the Apple Pay / Google Pay surface is now driven by \`gateways.stripe.enabledPaymentMethods\` on the session response. Remove the legacy prop(s) to silence this warning.`
|
|
4055
4210
|
);
|
|
4056
4211
|
}, [hasEnabledMethods, showApplePay, showGooglePay]);
|
|
4057
|
-
const handleNameChange = useCallback3((value) => {
|
|
4058
|
-
setFullName(value);
|
|
4059
|
-
onFullNameChange?.(value);
|
|
4060
|
-
const parts = value.trim().split(/\s+/);
|
|
4061
|
-
onFirstNameChange?.(parts[0] ?? "");
|
|
4062
|
-
onLastNameChange?.(parts.length > 1 ? parts.slice(1).join(" ") : "");
|
|
4063
|
-
}, [onFullNameChange, onFirstNameChange, onLastNameChange]);
|
|
4064
4212
|
const applyInlineSessionPatch = useCallback3(
|
|
4065
4213
|
(patch, method) => {
|
|
4066
4214
|
if (!checkout.applyInlineSessionPatch) {
|
|
@@ -4141,8 +4289,8 @@ function SplitCardFormInner({
|
|
|
4141
4289
|
accountData: {
|
|
4142
4290
|
userId: effectiveAccount.userId ?? "",
|
|
4143
4291
|
email: effectiveAccount.email ?? "",
|
|
4144
|
-
firstName: effectiveAccount.firstName ??
|
|
4145
|
-
lastName: effectiveAccount.lastName ??
|
|
4292
|
+
firstName: effectiveAccount.firstName ?? "",
|
|
4293
|
+
lastName: effectiveAccount.lastName ?? "",
|
|
4146
4294
|
...avsConfig ? (() => {
|
|
4147
4295
|
const c = selectedCountryRef.current;
|
|
4148
4296
|
const stateVisible = isAVSFieldVisible(avsConfig.state, c);
|
|
@@ -4190,70 +4338,6 @@ function SplitCardFormInner({
|
|
|
4190
4338
|
return;
|
|
4191
4339
|
}
|
|
4192
4340
|
const json = await response.json().catch(() => null);
|
|
4193
|
-
if (json?.type === "3ds_required") {
|
|
4194
|
-
const secret = json["threeDSecureToken"];
|
|
4195
|
-
if (!flopay || !secret) {
|
|
4196
|
-
setOverlayStatus("error");
|
|
4197
|
-
updateError("3DS authentication required but no token provided.");
|
|
4198
|
-
return;
|
|
4199
|
-
}
|
|
4200
|
-
setIs3DSActive(true);
|
|
4201
|
-
try {
|
|
4202
|
-
const retryBillingAddress = {};
|
|
4203
|
-
const retryCC = selectedCountryRef.current;
|
|
4204
|
-
const retryCountry = enableAVS ? retryCC : effectiveAccount.country;
|
|
4205
|
-
if (retryCountry) retryBillingAddress["country"] = retryCountry;
|
|
4206
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.postal_code, retryCC) && zipCodeRef.current.trim()) {
|
|
4207
|
-
retryBillingAddress["postal_code"] = zipCodeRef.current.trim();
|
|
4208
|
-
} else if (!avsConfig && effectiveAccount.zip) {
|
|
4209
|
-
retryBillingAddress["postal_code"] = effectiveAccount.zip;
|
|
4210
|
-
}
|
|
4211
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.address_line_1, retryCC) && addressLine1Ref.current.trim()) retryBillingAddress["line1"] = addressLine1Ref.current.trim();
|
|
4212
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.address_line_2, retryCC) && addressLine2Ref.current.trim()) retryBillingAddress["line2"] = addressLine2Ref.current.trim();
|
|
4213
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.city, retryCC) && cityRef.current.trim()) retryBillingAddress["city"] = cityRef.current.trim();
|
|
4214
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.state, retryCC) && stateRef.current.trim()) {
|
|
4215
|
-
retryBillingAddress["state"] = stateRef.current.trim();
|
|
4216
|
-
} else if (avsConfig && isAVSFieldVisible(avsConfig.address_line_1, retryCC) && !isAVSFieldVisible(avsConfig.state, retryCC) && isAVSFieldVisible(avsConfig.postal_code, retryCC)) {
|
|
4217
|
-
const derivedRetryState = getStateFromPostalCode(retryCC, zipCodeRef.current.trim());
|
|
4218
|
-
if (derivedRetryState) retryBillingAddress["state"] = derivedRetryState;
|
|
4219
|
-
}
|
|
4220
|
-
const result = await flopay.confirmPayment({
|
|
4221
|
-
clientSecret: secret,
|
|
4222
|
-
returnUrl: window.location.href,
|
|
4223
|
-
billingDetails: {
|
|
4224
|
-
...effectiveAccount.email ? { email: effectiveAccount.email } : {},
|
|
4225
|
-
...fullName.trim() ? { name: fullName.trim() } : {},
|
|
4226
|
-
...Object.keys(retryBillingAddress).length > 0 ? { address: retryBillingAddress } : {}
|
|
4227
|
-
}
|
|
4228
|
-
});
|
|
4229
|
-
if (result.error) {
|
|
4230
|
-
setOverlayStatus("error");
|
|
4231
|
-
updateError(result.error.message);
|
|
4232
|
-
onError?.(result.error);
|
|
4233
|
-
emitDecline("card", result.error);
|
|
4234
|
-
return;
|
|
4235
|
-
}
|
|
4236
|
-
if (result.status === "succeeded" || result.status === "processing") {
|
|
4237
|
-
processingRef.current = false;
|
|
4238
|
-
await processPaymentInternal({
|
|
4239
|
-
id: result.paymentIntentId,
|
|
4240
|
-
type: "card",
|
|
4241
|
-
threeDSecureActionResultTokenId: result.paymentIntentId
|
|
4242
|
-
}, {
|
|
4243
|
-
// Preserve the session/nonce/account overrides resolved
|
|
4244
|
-
// above so the retry talks to the same (possibly
|
|
4245
|
-
// freshly bootstrapped) session as the original attempt.
|
|
4246
|
-
accountPatch: overrides?.accountPatch,
|
|
4247
|
-
sessionId: effectiveSessionId,
|
|
4248
|
-
nonce: effectiveNonce,
|
|
4249
|
-
completionPaymentMethodId: resolvedCompletionPaymentMethodId
|
|
4250
|
-
});
|
|
4251
|
-
}
|
|
4252
|
-
} finally {
|
|
4253
|
-
setIs3DSActive(false);
|
|
4254
|
-
}
|
|
4255
|
-
return;
|
|
4256
|
-
}
|
|
4257
4341
|
if (json?.type === "paypal_redirect_required") {
|
|
4258
4342
|
const secret = json["threeDSecureToken"];
|
|
4259
4343
|
const savedPmId = json["paymentMethodId"] || tokenizedBody.id || "";
|
|
@@ -4307,6 +4391,8 @@ function SplitCardFormInner({
|
|
|
4307
4391
|
emitDecline("paypal", "paypal_direct_required retry limit exceeded");
|
|
4308
4392
|
return;
|
|
4309
4393
|
}
|
|
4394
|
+
setDirectPaypalReady(false);
|
|
4395
|
+
setDirectPaypalInitializationState("loading");
|
|
4310
4396
|
setPaypalDirectRetry({ orderId, attempts: prevAttempts + 1 });
|
|
4311
4397
|
setOverlayStatus(null);
|
|
4312
4398
|
return;
|
|
@@ -4329,7 +4415,7 @@ function SplitCardFormInner({
|
|
|
4329
4415
|
processingRef.current = false;
|
|
4330
4416
|
}
|
|
4331
4417
|
},
|
|
4332
|
-
[baseUrl, sessionId, nonce, resolvedAccount,
|
|
4418
|
+
[baseUrl, sessionId, nonce, resolvedAccount, resolvedBillingName, chv, flopay, paypalFlopay, onComplete, onError, updateError, emitDecline]
|
|
4333
4419
|
);
|
|
4334
4420
|
const dispatchTokenizedBody = useCallback3(
|
|
4335
4421
|
(tokenizedBody, overrides) => {
|
|
@@ -4341,35 +4427,8 @@ function SplitCardFormInner({
|
|
|
4341
4427
|
},
|
|
4342
4428
|
[onTokenizedBody, processPaymentInternal]
|
|
4343
4429
|
);
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
if (!flopay) return;
|
|
4347
|
-
setIs3DSActive(true);
|
|
4348
|
-
try {
|
|
4349
|
-
const result = await flopay.confirmPayment({
|
|
4350
|
-
clientSecret: secret,
|
|
4351
|
-
returnUrl: window.location.href
|
|
4352
|
-
});
|
|
4353
|
-
if (result.error) {
|
|
4354
|
-
updateError(result.error.message);
|
|
4355
|
-
onError?.(result.error);
|
|
4356
|
-
emitDecline("card", result.error);
|
|
4357
|
-
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
4358
|
-
dispatchTokenizedBody({
|
|
4359
|
-
id: result.paymentIntentId,
|
|
4360
|
-
type: "card",
|
|
4361
|
-
threeDSecureActionResultTokenId: result.paymentIntentId
|
|
4362
|
-
});
|
|
4363
|
-
}
|
|
4364
|
-
} catch (err) {
|
|
4365
|
-
updateError(err instanceof Error ? err.message : "Payment authentication failed.");
|
|
4366
|
-
} finally {
|
|
4367
|
-
setIs3DSActive(false);
|
|
4368
|
-
}
|
|
4369
|
-
}
|
|
4370
|
-
}), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
|
|
4371
|
-
const stripeResumeAttemptedRef = useRef5(false);
|
|
4372
|
-
useEffect5(() => {
|
|
4430
|
+
const stripeResumeAttemptedRef = useRef4(false);
|
|
4431
|
+
useEffect4(() => {
|
|
4373
4432
|
if (typeof window === "undefined" || stripeResumeAttemptedRef.current) return;
|
|
4374
4433
|
const params = new URLSearchParams(window.location.search);
|
|
4375
4434
|
const paymentIntentId = params.get("payment_intent");
|
|
@@ -4430,7 +4489,7 @@ function SplitCardFormInner({
|
|
|
4430
4489
|
return;
|
|
4431
4490
|
}
|
|
4432
4491
|
const piStatus = paymentIntent?.status;
|
|
4433
|
-
const successful = piStatus
|
|
4492
|
+
const successful = isSuccessfulPaymentIntentStatus(piStatus);
|
|
4434
4493
|
if (!paymentIntent || !successful) {
|
|
4435
4494
|
const message = piStatus === "canceled" ? "Payment was canceled." : "Payment was not completed. Please try again.";
|
|
4436
4495
|
updateError(message);
|
|
@@ -4447,165 +4506,10 @@ function SplitCardFormInner({
|
|
|
4447
4506
|
}, persistedOverrides);
|
|
4448
4507
|
})();
|
|
4449
4508
|
}, [sessionId, dispatchTokenizedBody, flopay, updateError, emitDecline]);
|
|
4450
|
-
const handleSubmit = useCallback3(
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
if (vaultActive) return;
|
|
4455
|
-
if (layout !== "buttons") {
|
|
4456
|
-
onButtonClick?.("card");
|
|
4457
|
-
}
|
|
4458
|
-
setProcessing(true);
|
|
4459
|
-
setOverlayStatus("processing");
|
|
4460
|
-
updateError(null);
|
|
4461
|
-
let handedOff = false;
|
|
4462
|
-
try {
|
|
4463
|
-
if (avsConfig) {
|
|
4464
|
-
const country = selectedCountryRef.current;
|
|
4465
|
-
const postal = computePostalCodeState(
|
|
4466
|
-
country,
|
|
4467
|
-
zipCodeRef.current,
|
|
4468
|
-
isAVSFieldVisible(avsConfig.postal_code, country)
|
|
4469
|
-
);
|
|
4470
|
-
if (postal.empty) {
|
|
4471
|
-
setZipTouched(true);
|
|
4472
|
-
updateError(getPostalCodeLabel(country) + " is required");
|
|
4473
|
-
return;
|
|
4474
|
-
}
|
|
4475
|
-
if (postal.malformed) {
|
|
4476
|
-
setZipTouched(true);
|
|
4477
|
-
updateError(malformedPostcodeMessage(country));
|
|
4478
|
-
return;
|
|
4479
|
-
}
|
|
4480
|
-
if (isAVSFieldVisible(avsConfig.address_line_1, country) && !addressLine1Ref.current.trim()) {
|
|
4481
|
-
updateError("Street address is required");
|
|
4482
|
-
return;
|
|
4483
|
-
}
|
|
4484
|
-
if (isAVSFieldVisible(avsConfig.city, country) && !cityRef.current.trim()) {
|
|
4485
|
-
updateError("City is required");
|
|
4486
|
-
return;
|
|
4487
|
-
}
|
|
4488
|
-
if (isAVSFieldVisible(avsConfig.state, country) && !stateRef.current.trim()) {
|
|
4489
|
-
updateError(getStateLabel(country) + " is required");
|
|
4490
|
-
return;
|
|
4491
|
-
}
|
|
4492
|
-
}
|
|
4493
|
-
if (!vaultActive) {
|
|
4494
|
-
const submitResult = await flopay.submitElements();
|
|
4495
|
-
if (submitResult.error) {
|
|
4496
|
-
updateError(submitResult.error.message);
|
|
4497
|
-
onError?.(submitResult.error);
|
|
4498
|
-
return;
|
|
4499
|
-
}
|
|
4500
|
-
}
|
|
4501
|
-
const billingAddress = {};
|
|
4502
|
-
const cc = selectedCountryRef.current;
|
|
4503
|
-
const avsCountry = enableAVS ? cc : resolvedAccount.country;
|
|
4504
|
-
if (avsCountry) billingAddress["country"] = avsCountry;
|
|
4505
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.postal_code, cc) && zipCodeRef.current.trim()) {
|
|
4506
|
-
billingAddress["postal_code"] = zipCodeRef.current.trim();
|
|
4507
|
-
} else if (!avsConfig && resolvedAccount.zip) {
|
|
4508
|
-
billingAddress["postal_code"] = resolvedAccount.zip;
|
|
4509
|
-
}
|
|
4510
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.address_line_1, cc) && addressLine1Ref.current.trim()) billingAddress["line1"] = addressLine1Ref.current.trim();
|
|
4511
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.address_line_2, cc) && addressLine2Ref.current.trim()) billingAddress["line2"] = addressLine2Ref.current.trim();
|
|
4512
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.city, cc) && cityRef.current.trim()) billingAddress["city"] = cityRef.current.trim();
|
|
4513
|
-
if (avsConfig && isAVSFieldVisible(avsConfig.state, cc) && stateRef.current.trim()) {
|
|
4514
|
-
billingAddress["state"] = stateRef.current.trim();
|
|
4515
|
-
} else if (avsConfig && isAVSFieldVisible(avsConfig.address_line_1, cc) && !isAVSFieldVisible(avsConfig.state, cc) && isAVSFieldVisible(avsConfig.postal_code, cc)) {
|
|
4516
|
-
const derivedSubmitState = getStateFromPostalCode(cc, zipCodeRef.current.trim());
|
|
4517
|
-
if (derivedSubmitState) billingAddress["state"] = derivedSubmitState;
|
|
4518
|
-
}
|
|
4519
|
-
const billingDetails = {
|
|
4520
|
-
...resolvedAccount.email ? { email: resolvedAccount.email } : {},
|
|
4521
|
-
...fullName.trim() ? { name: fullName.trim() } : {},
|
|
4522
|
-
...Object.keys(billingAddress).length > 0 ? { address: billingAddress } : {}
|
|
4523
|
-
};
|
|
4524
|
-
if (!sessionId || !resolvedAccount.email) {
|
|
4525
|
-
throw new FloPayError3("Missing sessionId or email", "validation_error");
|
|
4526
|
-
}
|
|
4527
|
-
{
|
|
4528
|
-
const pmResult = await flopay.createPaymentMethod(billingDetails);
|
|
4529
|
-
if (pmResult.error || !pmResult.paymentMethodId) {
|
|
4530
|
-
updateError(pmResult.error?.message ?? "Failed to create payment method.");
|
|
4531
|
-
return;
|
|
4532
|
-
}
|
|
4533
|
-
const intentHeaders = { "Content-Type": "application/json" };
|
|
4534
|
-
if (nonce) intentHeaders["x-checkout-session-token"] = nonce;
|
|
4535
|
-
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
4536
|
-
method: "POST",
|
|
4537
|
-
headers: intentHeaders,
|
|
4538
|
-
body: JSON.stringify({
|
|
4539
|
-
sessionId,
|
|
4540
|
-
email: resolvedAccount.email,
|
|
4541
|
-
paymentMethodType: pmResult.paymentMethodId,
|
|
4542
|
-
isPaypal: false
|
|
4543
|
-
})
|
|
4544
|
-
});
|
|
4545
|
-
if (!intentResponse.ok) {
|
|
4546
|
-
const intentError = await buildFloPayApiErrorFromResponse(
|
|
4547
|
-
intentResponse,
|
|
4548
|
-
"Failed to create payment intent"
|
|
4549
|
-
);
|
|
4550
|
-
setOverlayStatus("error");
|
|
4551
|
-
updateError(intentError.message);
|
|
4552
|
-
onError?.(intentError);
|
|
4553
|
-
emitDecline("card", intentError);
|
|
4554
|
-
await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
|
|
4555
|
-
return;
|
|
4556
|
-
}
|
|
4557
|
-
const intentJson = await intentResponse.json();
|
|
4558
|
-
const intentClientSecret = intentJson.data?.id;
|
|
4559
|
-
if (!intentClientSecret) throw new FloPayError3("No client_secret in payment intent response", "api_error");
|
|
4560
|
-
const confirmResult = await flopay.confirmCardPayment({
|
|
4561
|
-
clientSecret: intentClientSecret,
|
|
4562
|
-
paymentMethodId: pmResult.paymentMethodId
|
|
4563
|
-
});
|
|
4564
|
-
if (confirmResult.error) {
|
|
4565
|
-
setOverlayStatus("error");
|
|
4566
|
-
updateError(confirmResult.error.message);
|
|
4567
|
-
onError?.(confirmResult.error);
|
|
4568
|
-
emitDecline("card", confirmResult.error);
|
|
4569
|
-
await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
|
|
4570
|
-
return;
|
|
4571
|
-
}
|
|
4572
|
-
const rawProvider = typeof flopay.getRawProvider === "function" ? flopay.getRawProvider() : null;
|
|
4573
|
-
const confirmedPaymentIntent = await retrievePaymentIntentFromProvider(
|
|
4574
|
-
rawProvider,
|
|
4575
|
-
intentClientSecret
|
|
4576
|
-
);
|
|
4577
|
-
const paymentIntentId = confirmResult.paymentIntentId ?? confirmedPaymentIntent?.id;
|
|
4578
|
-
const paymentMethodId = confirmResult.paymentMethodId ?? resolvePaymentIntentPaymentMethodId(confirmedPaymentIntent) ?? pmResult.paymentMethodId;
|
|
4579
|
-
if (!paymentIntentId) {
|
|
4580
|
-
const error2 = new FloPayError3("No payment intent returned after confirmation.", "api_error");
|
|
4581
|
-
setOverlayStatus("error");
|
|
4582
|
-
updateError(error2.message);
|
|
4583
|
-
onError?.(error2);
|
|
4584
|
-
await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
|
|
4585
|
-
return;
|
|
4586
|
-
}
|
|
4587
|
-
handedOff = isSelfContained;
|
|
4588
|
-
dispatchTokenizedBody({
|
|
4589
|
-
id: paymentMethodId,
|
|
4590
|
-
type: "card",
|
|
4591
|
-
threeDSecureActionResultTokenId: paymentIntentId,
|
|
4592
|
-
originalPaymentMethodId: pmResult.paymentMethodId
|
|
4593
|
-
});
|
|
4594
|
-
}
|
|
4595
|
-
} catch (err) {
|
|
4596
|
-
setOverlayStatus("error");
|
|
4597
|
-
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
4598
|
-
await new Promise((r) => setTimeout(r, PROCESSING_OVERLAY_ERROR_DELAY_MS));
|
|
4599
|
-
} finally {
|
|
4600
|
-
if (!handedOff) {
|
|
4601
|
-
setProcessing(false);
|
|
4602
|
-
setOverlayStatus(null);
|
|
4603
|
-
}
|
|
4604
|
-
}
|
|
4605
|
-
},
|
|
4606
|
-
[flopay, elements, isSubmitting, sessionId, nonce, resolvedAccount.email, baseUrl, isSelfContained, vaultActive, dispatchTokenizedBody, onButtonClick, onError, updateError, emitDecline, layout]
|
|
4607
|
-
);
|
|
4608
|
-
const isReady = flopay !== null && (vaultActive || elements !== null);
|
|
4509
|
+
const handleSubmit = useCallback3((event) => {
|
|
4510
|
+
event.preventDefault();
|
|
4511
|
+
}, []);
|
|
4512
|
+
const isReady = flopay !== null;
|
|
4609
4513
|
if (!isReady) {
|
|
4610
4514
|
return /* @__PURE__ */ jsx7("div", { "data-testid": "flopay-loading", "aria-busy": "true", children: "Loading payment form..." });
|
|
4611
4515
|
}
|
|
@@ -4648,21 +4552,6 @@ function SplitCardFormInner({
|
|
|
4648
4552
|
"--flopay-input-font-family": resolvedInputFontFamily,
|
|
4649
4553
|
"--flopay-input-font-weight": String(resolvedInputFontWeight)
|
|
4650
4554
|
};
|
|
4651
|
-
const stripeElementStyle = {
|
|
4652
|
-
style: {
|
|
4653
|
-
base: {
|
|
4654
|
-
...sharedInputTypography,
|
|
4655
|
-
fontSmoothing: "antialiased",
|
|
4656
|
-
"::placeholder": {
|
|
4657
|
-
color: resolvedPlaceholderColor,
|
|
4658
|
-
fontSize: resolvedInputFontSize,
|
|
4659
|
-
fontFamily: resolvedInputFontFamily,
|
|
4660
|
-
fontWeight: resolvedInputFontWeight
|
|
4661
|
-
}
|
|
4662
|
-
},
|
|
4663
|
-
invalid: { color: "#ef4444" }
|
|
4664
|
-
}
|
|
4665
|
-
};
|
|
4666
4555
|
const containerOverrides = bStyles.cardFormContainer ?? {};
|
|
4667
4556
|
const containerPadding = containerOverrides.padding ?? (isButtons ? "0" : "1rem");
|
|
4668
4557
|
const containerRadius = containerOverrides.borderRadius ?? resolvedBorderRadius;
|
|
@@ -4675,7 +4564,6 @@ function SplitCardFormInner({
|
|
|
4675
4564
|
expectedOrigin: vaultMount.expectedOrigin,
|
|
4676
4565
|
theme: vaultThemeColors,
|
|
4677
4566
|
onReady: () => {
|
|
4678
|
-
setFormReady(true);
|
|
4679
4567
|
if (!avsConfig || typeof document === "undefined") return;
|
|
4680
4568
|
const focusFirstAvs = () => {
|
|
4681
4569
|
const block = document.querySelector('[data-testid="flopay-avs-fields"]');
|
|
@@ -4719,7 +4607,7 @@ function SplitCardFormInner({
|
|
|
4719
4607
|
children: "Loading secure card form\u2026"
|
|
4720
4608
|
}
|
|
4721
4609
|
);
|
|
4722
|
-
const cardFormBlock = /* @__PURE__ */
|
|
4610
|
+
const cardFormBlock = /* @__PURE__ */ jsxs5("div", { style: {
|
|
4723
4611
|
backgroundColor: cardBg,
|
|
4724
4612
|
borderRadius: containerRadius,
|
|
4725
4613
|
...containerOverrides,
|
|
@@ -4741,14 +4629,14 @@ function SplitCardFormInner({
|
|
|
4741
4629
|
font-weight: var(--flopay-input-font-weight);
|
|
4742
4630
|
}
|
|
4743
4631
|
` }),
|
|
4744
|
-
isButtons && showCardForm && /* @__PURE__ */
|
|
4632
|
+
isButtons && showCardForm && /* @__PURE__ */ jsxs5("div", { style: {
|
|
4745
4633
|
display: "flex",
|
|
4746
4634
|
alignItems: "center",
|
|
4747
4635
|
padding: "0.75rem 0 0.625rem",
|
|
4748
4636
|
// Keep the header on top when AVS is ordered above the card on vault.
|
|
4749
4637
|
order: vaultActive ? -2 : 0
|
|
4750
4638
|
}, children: [
|
|
4751
|
-
/* @__PURE__ */
|
|
4639
|
+
/* @__PURE__ */ jsxs5(
|
|
4752
4640
|
"button",
|
|
4753
4641
|
{
|
|
4754
4642
|
type: "button",
|
|
@@ -4806,68 +4694,6 @@ function SplitCardFormInner({
|
|
|
4806
4694
|
order: vaultActive ? -3 : 0,
|
|
4807
4695
|
...bStyles.title
|
|
4808
4696
|
}, children: /* @__PURE__ */ jsx7(TitleContentSlot, { content: cardTitleContent }) }),
|
|
4809
|
-
!vaultActive && /* @__PURE__ */ jsxs4(Fragment2, { children: [
|
|
4810
|
-
/* @__PURE__ */ jsx7("div", { style: {
|
|
4811
|
-
backgroundColor: cardInputBg,
|
|
4812
|
-
border: `1px solid ${resolvedBorder}`,
|
|
4813
|
-
borderTopLeftRadius: resolvedBorderRadius,
|
|
4814
|
-
borderTopRightRadius: resolvedBorderRadius,
|
|
4815
|
-
padding: "10px"
|
|
4816
|
-
}, children: /* @__PURE__ */ jsx7(CardNumberElement, { onReady: () => setFormReady(true), options: stripeElementStyle }) }),
|
|
4817
|
-
/* @__PURE__ */ jsxs4("div", { style: { display: "flex" }, children: [
|
|
4818
|
-
/* @__PURE__ */ jsx7("div", { style: {
|
|
4819
|
-
flex: 1,
|
|
4820
|
-
backgroundColor: cardInputBg,
|
|
4821
|
-
// Longhand only — mixing `border` shorthand with per-side
|
|
4822
|
-
// overrides triggers React's "shorthand vs longhand" warning
|
|
4823
|
-
// because render order isn't deterministic.
|
|
4824
|
-
borderTop: "none",
|
|
4825
|
-
borderRight: "none",
|
|
4826
|
-
borderBottom: `1px solid ${resolvedBorder}`,
|
|
4827
|
-
borderLeft: `1px solid ${resolvedBorder}`,
|
|
4828
|
-
borderBottomLeftRadius: resolvedBorderRadius,
|
|
4829
|
-
padding: "10px"
|
|
4830
|
-
}, children: /* @__PURE__ */ jsx7(CardExpiryElement, { options: stripeElementStyle }) }),
|
|
4831
|
-
/* @__PURE__ */ jsx7("div", { style: {
|
|
4832
|
-
flex: 1,
|
|
4833
|
-
backgroundColor: cardInputBg,
|
|
4834
|
-
borderTop: "none",
|
|
4835
|
-
borderRight: `1px solid ${resolvedBorder}`,
|
|
4836
|
-
borderBottom: `1px solid ${resolvedBorder}`,
|
|
4837
|
-
borderLeft: `1px solid ${resolvedBorder}`,
|
|
4838
|
-
borderBottomRightRadius: resolvedBorderRadius,
|
|
4839
|
-
padding: "10px"
|
|
4840
|
-
}, children: /* @__PURE__ */ jsx7(CardCvcElement, { options: stripeElementStyle }) })
|
|
4841
|
-
] })
|
|
4842
|
-
] }),
|
|
4843
|
-
!vaultActive && /* @__PURE__ */ jsx7("div", { style: {
|
|
4844
|
-
backgroundColor: cardInputBg,
|
|
4845
|
-
border: `1px solid ${resolvedBorder}`,
|
|
4846
|
-
borderRadius: resolvedBorderRadius,
|
|
4847
|
-
marginTop: "0.5rem",
|
|
4848
|
-
padding: "10px"
|
|
4849
|
-
}, children: /* @__PURE__ */ jsx7(
|
|
4850
|
-
"input",
|
|
4851
|
-
{
|
|
4852
|
-
className: "flopay-shared-input",
|
|
4853
|
-
id: "flopay-cc-name",
|
|
4854
|
-
name: "cc-name",
|
|
4855
|
-
placeholder: "Full Name on Card",
|
|
4856
|
-
autoComplete: "cc-name",
|
|
4857
|
-
value: fullName,
|
|
4858
|
-
onChange: (e) => handleNameChange(e.target.value),
|
|
4859
|
-
disabled: isSubmitting,
|
|
4860
|
-
required: true,
|
|
4861
|
-
style: {
|
|
4862
|
-
width: "100%",
|
|
4863
|
-
border: "none",
|
|
4864
|
-
outline: "none",
|
|
4865
|
-
background: "transparent",
|
|
4866
|
-
...sharedInputTypography,
|
|
4867
|
-
...isButtons && bStyles.nameInput ? bStyles.nameInput : {}
|
|
4868
|
-
}
|
|
4869
|
-
}
|
|
4870
|
-
) }),
|
|
4871
4697
|
avsConfig && /* @__PURE__ */ jsx7("div", { style: { order: vaultActive ? -1 : 0 }, "data-testid": "flopay-avs-fields", children: (() => {
|
|
4872
4698
|
const cc = selectedCountry;
|
|
4873
4699
|
const inputWrapStyle = (invalid = false) => ({
|
|
@@ -4885,7 +4711,7 @@ function SplitCardFormInner({
|
|
|
4885
4711
|
...sharedInputTypography
|
|
4886
4712
|
});
|
|
4887
4713
|
const stateOpts = getStateOptions(cc);
|
|
4888
|
-
return /* @__PURE__ */
|
|
4714
|
+
return /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
4889
4715
|
isAVSFieldVisible(avsConfig.address_line_1, cc) && /* @__PURE__ */ jsx7("div", { style: inputWrapStyle(invalidAvsFields.line1), children: /* @__PURE__ */ jsx7(
|
|
4890
4716
|
"input",
|
|
4891
4717
|
{
|
|
@@ -4923,7 +4749,7 @@ function SplitCardFormInner({
|
|
|
4923
4749
|
style: inputFieldStyle()
|
|
4924
4750
|
}
|
|
4925
4751
|
) }),
|
|
4926
|
-
(isAVSFieldVisible(avsConfig.city, cc) || isAVSFieldVisible(avsConfig.state, cc)) && /* @__PURE__ */
|
|
4752
|
+
(isAVSFieldVisible(avsConfig.city, cc) || isAVSFieldVisible(avsConfig.state, cc)) && /* @__PURE__ */ jsxs5("div", { style: {
|
|
4927
4753
|
display: "flex",
|
|
4928
4754
|
gap: "0",
|
|
4929
4755
|
marginTop: "0.5rem"
|
|
@@ -4966,7 +4792,7 @@ function SplitCardFormInner({
|
|
|
4966
4792
|
borderTopRightRadius: resolvedBorderRadius,
|
|
4967
4793
|
borderBottomRightRadius: resolvedBorderRadius,
|
|
4968
4794
|
...isAVSFieldVisible(avsConfig.city, cc) ? { borderTopLeftRadius: 0, borderBottomLeftRadius: 0 } : { borderRadius: resolvedBorderRadius }
|
|
4969
|
-
}, children: stateOpts ? /* @__PURE__ */
|
|
4795
|
+
}, children: stateOpts ? /* @__PURE__ */ jsxs5(
|
|
4970
4796
|
"select",
|
|
4971
4797
|
{
|
|
4972
4798
|
id: "flopay-billing-state",
|
|
@@ -5006,7 +4832,7 @@ function SplitCardFormInner({
|
|
|
5006
4832
|
}
|
|
5007
4833
|
) })
|
|
5008
4834
|
] }),
|
|
5009
|
-
(isAVSFieldVisible(avsConfig.country, cc) || isAVSFieldVisible(avsConfig.postal_code, cc)) && /* @__PURE__ */
|
|
4835
|
+
(isAVSFieldVisible(avsConfig.country, cc) || isAVSFieldVisible(avsConfig.postal_code, cc)) && /* @__PURE__ */ jsxs5("div", { style: {
|
|
5010
4836
|
display: "flex",
|
|
5011
4837
|
flexDirection: avsLayoutProp === "column" ? "column" : "row",
|
|
5012
4838
|
gap: avsLayoutProp === "column" ? "0.5rem" : "0",
|
|
@@ -5038,7 +4864,7 @@ function SplitCardFormInner({
|
|
|
5038
4864
|
autoComplete: "billing country",
|
|
5039
4865
|
"data-testid": "flopay-country",
|
|
5040
4866
|
style: { ...inputFieldStyle(), cursor: "pointer" },
|
|
5041
|
-
children: COUNTRY_OPTIONS.map((c) => /* @__PURE__ */
|
|
4867
|
+
children: COUNTRY_OPTIONS.map((c) => /* @__PURE__ */ jsxs5("option", { value: c.code, children: [
|
|
5042
4868
|
c.flag,
|
|
5043
4869
|
" ",
|
|
5044
4870
|
c.name
|
|
@@ -5094,53 +4920,7 @@ function SplitCardFormInner({
|
|
|
5094
4920
|
})() }),
|
|
5095
4921
|
vaultActive && cardPreFormSlot && /* @__PURE__ */ jsx7("div", { style: { order: -2, width: "100%" }, children: cardPreFormSlot }),
|
|
5096
4922
|
vaultActive && vaultCardFieldsNode,
|
|
5097
|
-
displayError && /* @__PURE__ */
|
|
5098
|
-
margin: "0.75rem 0",
|
|
5099
|
-
padding: "0.625rem 0.875rem",
|
|
5100
|
-
background: "#FEF2F2",
|
|
5101
|
-
border: "1px solid #FECACA",
|
|
5102
|
-
borderRadius: "8px",
|
|
5103
|
-
color: "#991B1B",
|
|
5104
|
-
fontSize: "0.85rem",
|
|
5105
|
-
fontWeight: 600,
|
|
5106
|
-
display: "flex",
|
|
5107
|
-
alignItems: "center",
|
|
5108
|
-
gap: "0.5rem",
|
|
5109
|
-
...bStyles.errorBanner ? bStyles.errorBanner : {}
|
|
5110
|
-
}, children: [
|
|
5111
|
-
/* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx7("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" }) }),
|
|
5112
|
-
displayError
|
|
5113
|
-
] }),
|
|
5114
|
-
!vaultActive && (children ?? /* @__PURE__ */ jsx7(
|
|
5115
|
-
"button",
|
|
5116
|
-
{
|
|
5117
|
-
type: "submit",
|
|
5118
|
-
disabled: !formReady || isSubmitting,
|
|
5119
|
-
"data-testid": "flopay-submit",
|
|
5120
|
-
onMouseEnter: (e) => {
|
|
5121
|
-
if (formReady && !isSubmitting) e.currentTarget.style.backgroundColor = resolvedPrimaryHoverColor;
|
|
5122
|
-
},
|
|
5123
|
-
onMouseLeave: (e) => {
|
|
5124
|
-
e.currentTarget.style.backgroundColor = bStyles.submitButton?.backgroundColor ?? resolvedPrimaryColor;
|
|
5125
|
-
},
|
|
5126
|
-
style: {
|
|
5127
|
-
width: "100%",
|
|
5128
|
-
padding: "0.875rem",
|
|
5129
|
-
marginTop: "1rem",
|
|
5130
|
-
backgroundColor: resolvedPrimaryColor,
|
|
5131
|
-
color: "white",
|
|
5132
|
-
border: "none",
|
|
5133
|
-
borderRadius: resolvedBorderRadius,
|
|
5134
|
-
fontSize: bStyles.submitButtonFontSize ?? "1rem",
|
|
5135
|
-
fontWeight: 600,
|
|
5136
|
-
cursor: !formReady || isSubmitting ? "not-allowed" : "pointer",
|
|
5137
|
-
opacity: !formReady || isSubmitting ? 0.5 : 1,
|
|
5138
|
-
transition: "background-color 0.15s",
|
|
5139
|
-
...bStyles.submitButton
|
|
5140
|
-
},
|
|
5141
|
-
children: isSubmitting ? "PROCESSING..." : submitLabel
|
|
5142
|
-
}
|
|
5143
|
-
))
|
|
4923
|
+
displayError && /* @__PURE__ */ jsx7(ErrorBanner, { margin: "0.75rem 0", styleOverride: bStyles.errorBanner, children: displayError })
|
|
5144
4924
|
] });
|
|
5145
4925
|
const gateDebugStyle = {
|
|
5146
4926
|
margin: 0,
|
|
@@ -5202,11 +4982,11 @@ function SplitCardFormInner({
|
|
|
5202
4982
|
const cardButtonSizing = cardButtonContent === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
|
|
5203
4983
|
const buttonsAnim = viewState === "expanding" || viewState === "apm-expanding" ? `flopay-buttons-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : viewState === "collapsing" || viewState === "apm-collapsing" ? `flopay-buttons-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : void 0;
|
|
5204
4984
|
const cardAnim = viewState === "expanding" ? `flopay-card-enter ${TRANSITION_MS}ms cubic-bezier(0, 0, 0.2, 1) both` : viewState === "collapsing" ? `flopay-card-exit ${TRANSITION_MS}ms cubic-bezier(0.4, 0, 0.2, 1) both` : void 0;
|
|
5205
|
-
return /* @__PURE__ */
|
|
4985
|
+
return /* @__PURE__ */ jsxs5("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
5206
4986
|
/* @__PURE__ */ jsx7(FloPayKeyframes, {}),
|
|
5207
4987
|
overlayStatus && /* @__PURE__ */ jsx7(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
|
|
5208
|
-
/* @__PURE__ */
|
|
5209
|
-
/* @__PURE__ */
|
|
4988
|
+
/* @__PURE__ */ jsxs5("div", { style: { display: "grid" }, children: [
|
|
4989
|
+
/* @__PURE__ */ jsxs5(
|
|
5210
4990
|
"div",
|
|
5211
4991
|
{
|
|
5212
4992
|
"data-testid": "flopay-buttons-panel",
|
|
@@ -5223,7 +5003,7 @@ function SplitCardFormInner({
|
|
|
5223
5003
|
children: [
|
|
5224
5004
|
renderDirectPaypalGateDebug("flopay-direct-paypal-gate-debug"),
|
|
5225
5005
|
renderStripeGateDebug("flopay-stripe-gate-debug"),
|
|
5226
|
-
shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */
|
|
5006
|
+
shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
5227
5007
|
paypalDirectRetry && /* @__PURE__ */ jsx7(
|
|
5228
5008
|
"div",
|
|
5229
5009
|
{
|
|
@@ -5260,6 +5040,7 @@ function SplitCardFormInner({
|
|
|
5260
5040
|
runBeforeButtonClick,
|
|
5261
5041
|
isProcessing: isSubmitting,
|
|
5262
5042
|
onLoadStateChange: setDirectPaypalReady,
|
|
5043
|
+
onInitializationStateChange: setDirectPaypalInitializationState,
|
|
5263
5044
|
session: session ?? null,
|
|
5264
5045
|
existingOrderId: paypalDirectRetry?.orderId,
|
|
5265
5046
|
debug
|
|
@@ -5292,6 +5073,7 @@ function SplitCardFormInner({
|
|
|
5292
5073
|
nonce,
|
|
5293
5074
|
email: resolvedAccount.email,
|
|
5294
5075
|
billingApiUrl: resolvedBillingApiUrl,
|
|
5076
|
+
intentKind: totalAmount > 0 ? "payment" : "setup",
|
|
5295
5077
|
expressMethods: walletExpressMethods,
|
|
5296
5078
|
onTokenizedBody: dispatchTokenizedBody,
|
|
5297
5079
|
onErrorChange: updateError,
|
|
@@ -5335,7 +5117,7 @@ function SplitCardFormInner({
|
|
|
5335
5117
|
}
|
|
5336
5118
|
}
|
|
5337
5119
|
),
|
|
5338
|
-
|
|
5120
|
+
vaultActive && /* @__PURE__ */ jsx7(
|
|
5339
5121
|
"button",
|
|
5340
5122
|
{
|
|
5341
5123
|
type: "button",
|
|
@@ -5394,27 +5176,11 @@ function SplitCardFormInner({
|
|
|
5394
5176
|
children: /* @__PURE__ */ jsx7(CardButtonContentSlot, { content: cardButtonContent })
|
|
5395
5177
|
}
|
|
5396
5178
|
),
|
|
5397
|
-
displayError && viewState === "buttons" && /* @__PURE__ */
|
|
5398
|
-
margin: "0.25rem 0",
|
|
5399
|
-
padding: "0.625rem 0.875rem",
|
|
5400
|
-
background: "#FEF2F2",
|
|
5401
|
-
border: "1px solid #FECACA",
|
|
5402
|
-
borderRadius: "8px",
|
|
5403
|
-
color: "#991B1B",
|
|
5404
|
-
fontSize: "0.85rem",
|
|
5405
|
-
fontWeight: 600,
|
|
5406
|
-
display: "flex",
|
|
5407
|
-
alignItems: "center",
|
|
5408
|
-
gap: "0.5rem",
|
|
5409
|
-
...bStyles.errorBanner ? bStyles.errorBanner : {}
|
|
5410
|
-
}, children: [
|
|
5411
|
-
/* @__PURE__ */ jsx7("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx7("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" }) }),
|
|
5412
|
-
displayError
|
|
5413
|
-
] })
|
|
5179
|
+
displayError && viewState === "buttons" && /* @__PURE__ */ jsx7(ErrorBanner, { margin: "0.25rem 0", styleOverride: bStyles.errorBanner, children: displayError })
|
|
5414
5180
|
]
|
|
5415
5181
|
}
|
|
5416
5182
|
),
|
|
5417
|
-
|
|
5183
|
+
vaultActive && isCardView && /* @__PURE__ */ jsx7("div", { style: {
|
|
5418
5184
|
gridArea: "1 / 1",
|
|
5419
5185
|
...cardAnim ? { animation: cardAnim } : {},
|
|
5420
5186
|
...viewState === "collapsing" ? { pointerEvents: "none" } : {}
|
|
@@ -5423,18 +5189,18 @@ function SplitCardFormInner({
|
|
|
5423
5189
|
gridArea: "1 / 1",
|
|
5424
5190
|
...apmAnim ? { animation: apmAnim } : {},
|
|
5425
5191
|
...viewState === "apm-collapsing" ? { pointerEvents: "none" } : {}
|
|
5426
|
-
}, children: /* @__PURE__ */
|
|
5192
|
+
}, children: /* @__PURE__ */ jsxs5("div", { style: {
|
|
5427
5193
|
backgroundColor: cardBg,
|
|
5428
5194
|
borderRadius: containerRadius,
|
|
5429
5195
|
...containerOverrides,
|
|
5430
5196
|
padding: containerPadding
|
|
5431
5197
|
}, children: [
|
|
5432
|
-
/* @__PURE__ */
|
|
5198
|
+
/* @__PURE__ */ jsxs5("div", { style: {
|
|
5433
5199
|
display: "flex",
|
|
5434
5200
|
alignItems: "center",
|
|
5435
5201
|
padding: "0.75rem 0 0.625rem"
|
|
5436
5202
|
}, children: [
|
|
5437
|
-
/* @__PURE__ */
|
|
5203
|
+
/* @__PURE__ */ jsxs5(
|
|
5438
5204
|
"button",
|
|
5439
5205
|
{
|
|
5440
5206
|
type: "button",
|
|
@@ -5517,24 +5283,24 @@ function SplitCardFormInner({
|
|
|
5517
5283
|
] });
|
|
5518
5284
|
}
|
|
5519
5285
|
if (isApmView && expandedApmMethod && apmInlineOptions && stripeInstance && showStripe) {
|
|
5520
|
-
return /* @__PURE__ */
|
|
5286
|
+
return /* @__PURE__ */ jsxs5("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
5521
5287
|
/* @__PURE__ */ jsx7(FloPayKeyframes, {}),
|
|
5522
5288
|
overlayStatus && /* @__PURE__ */ jsx7(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
|
|
5523
5289
|
/* @__PURE__ */ jsx7("div", { style: {
|
|
5524
5290
|
...apmAnim ? { animation: apmAnim } : {},
|
|
5525
5291
|
...viewState === "apm-collapsing" ? { pointerEvents: "none" } : {}
|
|
5526
|
-
}, children: /* @__PURE__ */
|
|
5292
|
+
}, children: /* @__PURE__ */ jsxs5("div", { style: {
|
|
5527
5293
|
backgroundColor: cardBg,
|
|
5528
5294
|
borderRadius: containerRadius,
|
|
5529
5295
|
...containerOverrides,
|
|
5530
5296
|
padding: containerPadding
|
|
5531
5297
|
}, children: [
|
|
5532
|
-
/* @__PURE__ */
|
|
5298
|
+
/* @__PURE__ */ jsxs5("div", { style: {
|
|
5533
5299
|
display: "flex",
|
|
5534
5300
|
alignItems: "center",
|
|
5535
5301
|
padding: "0.75rem 0 0.625rem"
|
|
5536
5302
|
}, children: [
|
|
5537
|
-
/* @__PURE__ */
|
|
5303
|
+
/* @__PURE__ */ jsxs5(
|
|
5538
5304
|
"button",
|
|
5539
5305
|
{
|
|
5540
5306
|
type: "button",
|
|
@@ -5615,13 +5381,13 @@ function SplitCardFormInner({
|
|
|
5615
5381
|
] }) })
|
|
5616
5382
|
] });
|
|
5617
5383
|
}
|
|
5618
|
-
return /* @__PURE__ */
|
|
5384
|
+
return /* @__PURE__ */ jsxs5("form", { onSubmit: handleSubmit, className, style: { position: "relative" }, children: [
|
|
5619
5385
|
/* @__PURE__ */ jsx7(FloPayKeyframes, {}),
|
|
5620
5386
|
overlayStatus && /* @__PURE__ */ jsx7(ProcessingOverlay, { status: overlayStatus, errorMessage: displayError }),
|
|
5621
|
-
/* @__PURE__ */
|
|
5387
|
+
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
5622
5388
|
renderDirectPaypalGateDebug("flopay-direct-paypal-gate-debug-default"),
|
|
5623
5389
|
renderStripeGateDebug("flopay-stripe-gate-debug-default"),
|
|
5624
|
-
shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */
|
|
5390
|
+
shouldRenderDirectPayPal && directPaypal && /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
5625
5391
|
paypalDirectRetry && /* @__PURE__ */ jsx7(
|
|
5626
5392
|
"div",
|
|
5627
5393
|
{
|
|
@@ -5658,6 +5424,7 @@ function SplitCardFormInner({
|
|
|
5658
5424
|
runBeforeButtonClick,
|
|
5659
5425
|
isProcessing: isSubmitting,
|
|
5660
5426
|
onLoadStateChange: setDirectPaypalReady,
|
|
5427
|
+
onInitializationStateChange: setDirectPaypalInitializationState,
|
|
5661
5428
|
session: session ?? null,
|
|
5662
5429
|
existingOrderId: paypalDirectRetry?.orderId,
|
|
5663
5430
|
debug
|
|
@@ -5690,6 +5457,7 @@ function SplitCardFormInner({
|
|
|
5690
5457
|
nonce,
|
|
5691
5458
|
email: resolvedAccount.email,
|
|
5692
5459
|
billingApiUrl: resolvedBillingApiUrl,
|
|
5460
|
+
intentKind: totalAmount > 0 ? "payment" : "setup",
|
|
5693
5461
|
expressMethods: walletExpressMethods,
|
|
5694
5462
|
onTokenizedBody: dispatchTokenizedBody,
|
|
5695
5463
|
onErrorChange: updateError,
|
|
@@ -5734,7 +5502,7 @@ function SplitCardFormInner({
|
|
|
5734
5502
|
}
|
|
5735
5503
|
)
|
|
5736
5504
|
] }),
|
|
5737
|
-
|
|
5505
|
+
vaultActive && (shouldDisplayWalletRow || shouldDisplayPayPalRow || shouldDisplayPaymentElementRow) && /* @__PURE__ */ jsxs5("div", { style: {
|
|
5738
5506
|
display: "flex",
|
|
5739
5507
|
alignItems: "center",
|
|
5740
5508
|
gap: "0.75rem",
|
|
@@ -5746,7 +5514,7 @@ function SplitCardFormInner({
|
|
|
5746
5514
|
/* @__PURE__ */ jsx7("span", { children: "or pay with card" }),
|
|
5747
5515
|
/* @__PURE__ */ jsx7("div", { style: { flex: 1, height: 1, backgroundColor: "#ddd" } })
|
|
5748
5516
|
] }),
|
|
5749
|
-
|
|
5517
|
+
vaultActive && cardFormBlock
|
|
5750
5518
|
] });
|
|
5751
5519
|
}
|
|
5752
5520
|
|
|
@@ -5774,6 +5542,9 @@ async function confirmResumeIntent(stripe, clientSecret, data, isSetupIntent) {
|
|
|
5774
5542
|
const { paymentIntent, error } = await stripe.confirmCardPayment(clientSecret, data);
|
|
5775
5543
|
return { error, intent: paymentIntent ?? null };
|
|
5776
5544
|
}
|
|
5545
|
+
function withCheckoutMethod(error, checkoutMethod) {
|
|
5546
|
+
return Object.assign(error, { checkoutMethod });
|
|
5547
|
+
}
|
|
5777
5548
|
function getRedirectResultFromCheckoutProcessError(error) {
|
|
5778
5549
|
if (!error?.type || !error.threeDSecureToken) {
|
|
5779
5550
|
return null;
|
|
@@ -5995,7 +5766,7 @@ function checkoutProcessErrorToFloPayError(error, fallbackMessage = "Payment fai
|
|
|
5995
5766
|
const checkoutMethod = options?.checkoutMethod ?? error?.checkoutMethod ?? (error?.type === "paypal_redirect_required" ? "paypal" : "card");
|
|
5996
5767
|
const rawMessage = error?.message ?? fallbackMessage;
|
|
5997
5768
|
const message = applyFriendlyMessageOverride(rawMessage) ?? rawMessage;
|
|
5998
|
-
return
|
|
5769
|
+
return withCheckoutMethod(
|
|
5999
5770
|
new FloPayError4(
|
|
6000
5771
|
message,
|
|
6001
5772
|
"api_error",
|
|
@@ -6003,7 +5774,7 @@ function checkoutProcessErrorToFloPayError(error, fallbackMessage = "Payment fai
|
|
|
6003
5774
|
code: error?.gatewayErrorCode
|
|
6004
5775
|
}
|
|
6005
5776
|
),
|
|
6006
|
-
|
|
5777
|
+
checkoutMethod
|
|
6007
5778
|
);
|
|
6008
5779
|
}
|
|
6009
5780
|
function resolveSavedPaymentReturnUrl(session) {
|
|
@@ -6175,13 +5946,13 @@ async function processSavedPaymentForMode({
|
|
|
6175
5946
|
if (recoveredRedirect) {
|
|
6176
5947
|
return recoveredRedirect;
|
|
6177
5948
|
}
|
|
6178
|
-
throw
|
|
5949
|
+
throw withCheckoutMethod(
|
|
6179
5950
|
new FloPayError4(
|
|
6180
5951
|
"Your card requires authentication. Please enter your payment details below.",
|
|
6181
5952
|
"api_error",
|
|
6182
5953
|
{ code: "authentication_required" }
|
|
6183
5954
|
),
|
|
6184
|
-
|
|
5955
|
+
"card"
|
|
6185
5956
|
);
|
|
6186
5957
|
}
|
|
6187
5958
|
throw new FloPayError4(
|
|
@@ -6209,13 +5980,13 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
|
|
|
6209
5980
|
}
|
|
6210
5981
|
if (redirectResult.type === "3ds_required") {
|
|
6211
5982
|
if (!attempt3DS || !redirectResult.threeDSecureToken) {
|
|
6212
|
-
throw
|
|
5983
|
+
throw withCheckoutMethod(
|
|
6213
5984
|
new FloPayError4(
|
|
6214
5985
|
"Your card requires authentication. Please enter your payment details below.",
|
|
6215
5986
|
"api_error",
|
|
6216
5987
|
{ code: "authentication_required" }
|
|
6217
5988
|
),
|
|
6218
|
-
|
|
5989
|
+
"card"
|
|
6219
5990
|
);
|
|
6220
5991
|
}
|
|
6221
5992
|
const isSetupIntent = isSetupIntentClientSecret3(redirectResult.threeDSecureToken);
|
|
@@ -6224,13 +5995,13 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
|
|
|
6224
5995
|
const retrieved = await retrieveResumeIntent(stripe, redirectResult.threeDSecureToken, isSetupIntent);
|
|
6225
5996
|
if (retrieved) {
|
|
6226
5997
|
if (retrieved.error) {
|
|
6227
|
-
throw
|
|
5998
|
+
throw withCheckoutMethod(
|
|
6228
5999
|
new FloPayError4(
|
|
6229
6000
|
retrieved.error.message ?? `Failed to retrieve 3DS ${isSetupIntent ? "setup" : "payment"} status.`,
|
|
6230
6001
|
"api_error",
|
|
6231
6002
|
{ code: retrieved.error.code }
|
|
6232
6003
|
),
|
|
6233
|
-
|
|
6004
|
+
"card"
|
|
6234
6005
|
);
|
|
6235
6006
|
}
|
|
6236
6007
|
const existingIntent = retrieved.intent;
|
|
@@ -6253,13 +6024,13 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
|
|
|
6253
6024
|
) : null;
|
|
6254
6025
|
if (confirmed) {
|
|
6255
6026
|
if (confirmed.error) {
|
|
6256
|
-
throw
|
|
6027
|
+
throw withCheckoutMethod(
|
|
6257
6028
|
new FloPayError4(
|
|
6258
6029
|
confirmed.error.message ?? "3DS authentication failed.",
|
|
6259
6030
|
"api_error",
|
|
6260
6031
|
{ code: confirmed.error.code }
|
|
6261
6032
|
),
|
|
6262
|
-
|
|
6033
|
+
"card"
|
|
6263
6034
|
);
|
|
6264
6035
|
}
|
|
6265
6036
|
paymentIntent = confirmed.intent;
|
|
@@ -6268,13 +6039,13 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
|
|
|
6268
6039
|
clientSecret: redirectResult.threeDSecureToken
|
|
6269
6040
|
});
|
|
6270
6041
|
if (nextAction.error) {
|
|
6271
|
-
throw
|
|
6042
|
+
throw withCheckoutMethod(
|
|
6272
6043
|
new FloPayError4(
|
|
6273
6044
|
nextAction.error.message ?? "3DS authentication failed.",
|
|
6274
6045
|
"api_error",
|
|
6275
6046
|
{ code: nextAction.error.code }
|
|
6276
6047
|
),
|
|
6277
|
-
|
|
6048
|
+
"card"
|
|
6278
6049
|
);
|
|
6279
6050
|
}
|
|
6280
6051
|
paymentIntent = (isSetupIntent ? nextAction.setupIntent : nextAction.paymentIntent) ?? null;
|
|
@@ -6310,17 +6081,17 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
|
|
|
6310
6081
|
telemetry
|
|
6311
6082
|
});
|
|
6312
6083
|
}
|
|
6313
|
-
throw
|
|
6084
|
+
throw withCheckoutMethod(
|
|
6314
6085
|
new FloPayError4("3DS authentication did not complete successfully.", "api_error"),
|
|
6315
|
-
|
|
6086
|
+
"card"
|
|
6316
6087
|
);
|
|
6317
6088
|
}
|
|
6318
6089
|
if (redirectResult.type === "paypal_redirect_required") {
|
|
6319
6090
|
const paypalStripe = (paypalFlopay ?? flopay)?.getRawProvider();
|
|
6320
6091
|
if (!paypalStripe) {
|
|
6321
|
-
throw
|
|
6092
|
+
throw withCheckoutMethod(
|
|
6322
6093
|
new FloPayError4("PayPal is not available.", "api_error"),
|
|
6323
|
-
|
|
6094
|
+
"paypal"
|
|
6324
6095
|
);
|
|
6325
6096
|
}
|
|
6326
6097
|
if (redirectResult.paymentMethodId) {
|
|
@@ -6328,13 +6099,13 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
|
|
|
6328
6099
|
clientSecret: redirectResult.threeDSecureToken
|
|
6329
6100
|
});
|
|
6330
6101
|
if (error2) {
|
|
6331
|
-
throw
|
|
6102
|
+
throw withCheckoutMethod(
|
|
6332
6103
|
new FloPayError4(
|
|
6333
6104
|
error2.message ?? "PayPal authorization failed.",
|
|
6334
6105
|
"api_error",
|
|
6335
6106
|
{ code: error2.code }
|
|
6336
6107
|
),
|
|
6337
|
-
|
|
6108
|
+
"paypal"
|
|
6338
6109
|
);
|
|
6339
6110
|
}
|
|
6340
6111
|
return {
|
|
@@ -6351,13 +6122,13 @@ async function handleSavedPaymentRedirectResult(redirectResult, {
|
|
|
6351
6122
|
redirect: "if_required"
|
|
6352
6123
|
});
|
|
6353
6124
|
if (error) {
|
|
6354
|
-
throw
|
|
6125
|
+
throw withCheckoutMethod(
|
|
6355
6126
|
new FloPayError4(
|
|
6356
6127
|
error.message ?? "PayPal authorization failed.",
|
|
6357
6128
|
"api_error",
|
|
6358
6129
|
{ code: error.code }
|
|
6359
6130
|
),
|
|
6360
|
-
|
|
6131
|
+
"paypal"
|
|
6361
6132
|
);
|
|
6362
6133
|
}
|
|
6363
6134
|
return {
|
|
@@ -6442,7 +6213,7 @@ async function loadSavedPaymentProviders({
|
|
|
6442
6213
|
}
|
|
6443
6214
|
|
|
6444
6215
|
// src/flopay-checkout.tsx
|
|
6445
|
-
import { Fragment as Fragment3, jsx as jsx8, jsxs as
|
|
6216
|
+
import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
6446
6217
|
var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2 = 44;
|
|
6447
6218
|
var PAYPAL_RESUME_STORAGE_KEY = "flopay_checkout_saved_payment_resume";
|
|
6448
6219
|
var sessionInflightMap = /* @__PURE__ */ new Map();
|
|
@@ -6588,7 +6359,6 @@ function FloPayCheckout({
|
|
|
6588
6359
|
onComplete,
|
|
6589
6360
|
onError,
|
|
6590
6361
|
onDecline,
|
|
6591
|
-
onFullNameChange,
|
|
6592
6362
|
onCountryChange,
|
|
6593
6363
|
onZipChange,
|
|
6594
6364
|
showPayPal = true,
|
|
@@ -6610,7 +6380,6 @@ function FloPayCheckout({
|
|
|
6610
6380
|
cardFieldOrder,
|
|
6611
6381
|
cardPreFormSlot,
|
|
6612
6382
|
avsLayout,
|
|
6613
|
-
submitLabel,
|
|
6614
6383
|
className,
|
|
6615
6384
|
initialErrorMessage = null,
|
|
6616
6385
|
children,
|
|
@@ -6626,9 +6395,9 @@ function FloPayCheckout({
|
|
|
6626
6395
|
const checkoutLayout = children ? "custom_layout" : layout === "buttons" ? "buttons_layout" : "default_layout";
|
|
6627
6396
|
const [unified, setUnified] = useState4(null);
|
|
6628
6397
|
const [flopay, setFloPay] = useState4(null);
|
|
6629
|
-
const flopayRef =
|
|
6398
|
+
const flopayRef = useRef5(null);
|
|
6630
6399
|
const [paypalFlopay, setPaypalFloPay] = useState4(null);
|
|
6631
|
-
const paypalFlopayRef =
|
|
6400
|
+
const paypalFlopayRef = useRef5(null);
|
|
6632
6401
|
const [session, setSession] = useState4(null);
|
|
6633
6402
|
const [resolvedSessionId, setResolvedSessionId] = useState4(sessionIdProp ?? "");
|
|
6634
6403
|
const activeSessionId = sessionIdProp ?? resolvedSessionId;
|
|
@@ -6643,28 +6412,53 @@ function FloPayCheckout({
|
|
|
6643
6412
|
const [createSessionPatch, setCreateSessionPatch] = useState4(void 0);
|
|
6644
6413
|
const [createSessionPatchBaseHash, setCreateSessionPatchBaseHash] = useState4("");
|
|
6645
6414
|
const [cardBootstrapPending, setCardBootstrapPending] = useState4(false);
|
|
6646
|
-
const
|
|
6647
|
-
const
|
|
6648
|
-
const
|
|
6415
|
+
const [paypalInitializationState, setPaypalInitializationState] = useState4("loading");
|
|
6416
|
+
const [paypalManualRetryGeneration, setPaypalManualRetryGeneration] = useState4(0);
|
|
6417
|
+
const autoCheckoutAttempted = useRef5(false);
|
|
6418
|
+
const paypalResumeAttempted = useRef5(false);
|
|
6419
|
+
const paypalInitializationErrorEmittedRef = useRef5(false);
|
|
6420
|
+
const savedPaymentKeysRef = useRef5(null);
|
|
6649
6421
|
const telemetryCheckoutContext = useMemo4(() => ({
|
|
6650
6422
|
checkoutMode: checkoutModeProp ?? currentMode,
|
|
6651
6423
|
layout: children ? "unknown" : layout === "buttons" ? "buttons" : "embedded"
|
|
6652
6424
|
}), [checkoutModeProp, children, currentMode, layout]);
|
|
6653
|
-
const onCompleteRef =
|
|
6425
|
+
const onCompleteRef = useRef5(onComplete);
|
|
6654
6426
|
onCompleteRef.current = onComplete;
|
|
6655
|
-
const onErrorRef =
|
|
6427
|
+
const onErrorRef = useRef5(onError);
|
|
6656
6428
|
onErrorRef.current = onError;
|
|
6657
|
-
const onDeclineRef =
|
|
6429
|
+
const onDeclineRef = useRef5(onDecline);
|
|
6658
6430
|
onDeclineRef.current = onDecline;
|
|
6659
|
-
|
|
6431
|
+
useEffect5(() => {
|
|
6660
6432
|
getFloPayTelemetryBridge(flopay)?.setCheckoutContext(telemetryCheckoutContext);
|
|
6661
6433
|
if (paypalFlopay && paypalFlopay !== flopay) {
|
|
6662
6434
|
getFloPayTelemetryBridge(paypalFlopay)?.setCheckoutContext(telemetryCheckoutContext);
|
|
6663
6435
|
}
|
|
6664
6436
|
}, [flopay, paypalFlopay, telemetryCheckoutContext]);
|
|
6665
|
-
const onSessionCompletedRef =
|
|
6437
|
+
const onSessionCompletedRef = useRef5(onSessionCompleted);
|
|
6666
6438
|
onSessionCompletedRef.current = onSessionCompleted;
|
|
6667
|
-
|
|
6439
|
+
const handlePayPalInitializationStateChange = useCallback4(
|
|
6440
|
+
(state) => {
|
|
6441
|
+
setPaypalInitializationState(state);
|
|
6442
|
+
if (state !== "exhausted" || paypalInitializationErrorEmittedRef.current) return;
|
|
6443
|
+
paypalInitializationErrorEmittedRef.current = true;
|
|
6444
|
+
onErrorRef.current?.(new FloPayError5(
|
|
6445
|
+
"PayPal is temporarily unavailable. Please try again.",
|
|
6446
|
+
"api_error",
|
|
6447
|
+
{ code: "paypal_init_timeout" }
|
|
6448
|
+
));
|
|
6449
|
+
},
|
|
6450
|
+
[]
|
|
6451
|
+
);
|
|
6452
|
+
const retryPayPalInitialization = useCallback4(() => {
|
|
6453
|
+
setPaypalInitializationState("retrying");
|
|
6454
|
+
setPaypalManualRetryGeneration((generation) => generation + 1);
|
|
6455
|
+
}, []);
|
|
6456
|
+
useEffect5(() => {
|
|
6457
|
+
setPaypalInitializationState("loading");
|
|
6458
|
+
setPaypalManualRetryGeneration(0);
|
|
6459
|
+
paypalInitializationErrorEmittedRef.current = false;
|
|
6460
|
+
}, [activeSessionId]);
|
|
6461
|
+
useEffect5(() => {
|
|
6668
6462
|
console.info("[FloPay] Checkout initialized", {
|
|
6669
6463
|
sdk_version: SDK_VERSION2,
|
|
6670
6464
|
checkout_type: checkoutType,
|
|
@@ -6692,11 +6486,11 @@ function FloPayCheckout({
|
|
|
6692
6486
|
} : void 0,
|
|
6693
6487
|
[effectiveCreateSessionBase, effectiveCreateSessionMode]
|
|
6694
6488
|
);
|
|
6695
|
-
|
|
6489
|
+
useEffect5(() => {
|
|
6696
6490
|
setCreateSessionPatch(void 0);
|
|
6697
6491
|
setCreateSessionPatchBaseHash(baseCreateSessionHash);
|
|
6698
6492
|
}, [baseCreateSessionHash]);
|
|
6699
|
-
|
|
6493
|
+
useEffect5(() => {
|
|
6700
6494
|
setModeError(initialErrorMessage);
|
|
6701
6495
|
}, [initialErrorMessage]);
|
|
6702
6496
|
const emitDecline = useCallback4(
|
|
@@ -6973,7 +6767,7 @@ function FloPayCheckout({
|
|
|
6973
6767
|
telemetryCheckoutContext
|
|
6974
6768
|
]
|
|
6975
6769
|
);
|
|
6976
|
-
|
|
6770
|
+
useEffect5(() => {
|
|
6977
6771
|
if (typeof window === "undefined" || paypalResumeAttempted.current) {
|
|
6978
6772
|
return;
|
|
6979
6773
|
}
|
|
@@ -7176,7 +6970,7 @@ function FloPayCheckout({
|
|
|
7176
6970
|
}
|
|
7177
6971
|
})();
|
|
7178
6972
|
}, [emitDecline, invokeMerchantCallback, locale, normalizeSavedPaymentError, resolvedBillingUrl, telemetry]);
|
|
7179
|
-
const initializedHashRef =
|
|
6973
|
+
const initializedHashRef = useRef5(null);
|
|
7180
6974
|
function hashCreateParams(params) {
|
|
7181
6975
|
const key = JSON.stringify({
|
|
7182
6976
|
c: params?.clientId,
|
|
@@ -7213,12 +7007,12 @@ function FloPayCheckout({
|
|
|
7213
7007
|
() => effectiveCreateSession ? hashCreateParams(effectiveCreateSession) : "",
|
|
7214
7008
|
[effectiveCreateSession]
|
|
7215
7009
|
);
|
|
7216
|
-
const createSessionParamsRef =
|
|
7010
|
+
const createSessionParamsRef = useRef5(effectiveCreateSession);
|
|
7217
7011
|
createSessionParamsRef.current = effectiveCreateSession;
|
|
7218
|
-
|
|
7012
|
+
useEffect5(() => {
|
|
7219
7013
|
setResolvedSessionId(sessionIdProp ?? "");
|
|
7220
7014
|
}, [sessionIdProp]);
|
|
7221
|
-
|
|
7015
|
+
useEffect5(() => {
|
|
7222
7016
|
autoCheckoutAttempted.current = false;
|
|
7223
7017
|
setModeError(initialErrorMessage);
|
|
7224
7018
|
setModeOverlayError(null);
|
|
@@ -7410,7 +7204,7 @@ function FloPayCheckout({
|
|
|
7410
7204
|
resolvedSessionId,
|
|
7411
7205
|
session
|
|
7412
7206
|
]);
|
|
7413
|
-
|
|
7207
|
+
useEffect5(() => {
|
|
7414
7208
|
let cancelled = false;
|
|
7415
7209
|
setLoadError(null);
|
|
7416
7210
|
if (createSessionHash) {
|
|
@@ -7666,7 +7460,7 @@ function FloPayCheckout({
|
|
|
7666
7460
|
) : null;
|
|
7667
7461
|
if (isLoading) {
|
|
7668
7462
|
if (loadingNode) {
|
|
7669
|
-
return /* @__PURE__ */
|
|
7463
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
7670
7464
|
loadingNode,
|
|
7671
7465
|
modeOverlay
|
|
7672
7466
|
] });
|
|
@@ -7679,8 +7473,8 @@ function FloPayCheckout({
|
|
|
7679
7473
|
background: "#e5e7eb",
|
|
7680
7474
|
animation: "flopay-loading-pulse 1.5s ease-in-out infinite"
|
|
7681
7475
|
} });
|
|
7682
|
-
return /* @__PURE__ */
|
|
7683
|
-
/* @__PURE__ */
|
|
7476
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
7477
|
+
/* @__PURE__ */ jsxs6("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
7684
7478
|
showPayPal && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
7685
7479
|
showStripe && (showApplePay || showGooglePay) && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
7686
7480
|
showStripe && skeletonBar(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
@@ -7689,8 +7483,8 @@ function FloPayCheckout({
|
|
|
7689
7483
|
modeOverlay
|
|
7690
7484
|
] });
|
|
7691
7485
|
}
|
|
7692
|
-
return /* @__PURE__ */
|
|
7693
|
-
/* @__PURE__ */
|
|
7486
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
7487
|
+
/* @__PURE__ */ jsxs6("div", { style: { display: "flex", justifyContent: "center", padding: 32 }, children: [
|
|
7694
7488
|
/* @__PURE__ */ jsx8("div", { style: {
|
|
7695
7489
|
width: 24,
|
|
7696
7490
|
height: 24,
|
|
@@ -7724,7 +7518,7 @@ function FloPayCheckout({
|
|
|
7724
7518
|
) });
|
|
7725
7519
|
}
|
|
7726
7520
|
if (shouldShowInterimButtons) {
|
|
7727
|
-
return /* @__PURE__ */
|
|
7521
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
7728
7522
|
/* @__PURE__ */ jsx8(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx8(
|
|
7729
7523
|
InterimButtonsView,
|
|
7730
7524
|
{
|
|
@@ -7745,44 +7539,43 @@ function FloPayCheckout({
|
|
|
7745
7539
|
] });
|
|
7746
7540
|
}
|
|
7747
7541
|
if (isPaypalOnlySession && session && directPaypalConfig) {
|
|
7748
|
-
return /* @__PURE__ */
|
|
7749
|
-
/* @__PURE__ */ jsx8(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */
|
|
7750
|
-
modeError && /* @__PURE__ */ jsx8(
|
|
7542
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
7543
|
+
/* @__PURE__ */ jsx8(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsxs6("div", { className, style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
7544
|
+
modeError && /* @__PURE__ */ jsx8(ErrorBanner, { icon: false, children: modeError }),
|
|
7545
|
+
paypalInitializationState === "retrying" && /* @__PURE__ */ jsx8("div", { role: "status", "aria-live": "polite", children: "PayPal is temporarily unavailable. Retrying\u2026" }),
|
|
7546
|
+
paypalInitializationState === "exhausted" && /* @__PURE__ */ jsxs6("div", { role: "alert", children: [
|
|
7547
|
+
/* @__PURE__ */ jsx8("div", { children: "PayPal is temporarily unavailable. Please try again." }),
|
|
7548
|
+
/* @__PURE__ */ jsx8("button", { type: "button", onClick: retryPayPalInitialization, children: "Retry PayPal" })
|
|
7549
|
+
] }),
|
|
7550
|
+
paypalInitializationState !== "exhausted" && /* @__PURE__ */ jsx8(
|
|
7751
7551
|
"div",
|
|
7752
7552
|
{
|
|
7753
|
-
|
|
7754
|
-
"
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7761
|
-
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
onErrorChange: setModeError,
|
|
7780
|
-
onDecline,
|
|
7781
|
-
onButtonClick,
|
|
7782
|
-
session,
|
|
7783
|
-
telemetry,
|
|
7784
|
-
telemetryContext: telemetryCheckoutContext,
|
|
7785
|
-
debug
|
|
7553
|
+
"aria-hidden": paypalInitializationState === "retrying" ? true : void 0,
|
|
7554
|
+
style: paypalInitializationState === "retrying" ? { position: "relative", height: 0, opacity: 0, pointerEvents: "none" } : void 0,
|
|
7555
|
+
children: /* @__PURE__ */ jsx8(
|
|
7556
|
+
InstrumentedDirectPayPalButton,
|
|
7557
|
+
{
|
|
7558
|
+
sessionId: activeSessionId,
|
|
7559
|
+
nonce: session.clientSecret || void 0,
|
|
7560
|
+
billingApiUrl: resolvedBillingUrl,
|
|
7561
|
+
email: session.customer?.email,
|
|
7562
|
+
clientId: directPaypalConfig.clientId,
|
|
7563
|
+
environment: directPaypalConfig.environment,
|
|
7564
|
+
currency: (session.currency ?? "usd").toUpperCase(),
|
|
7565
|
+
isSubscription: session.mode === "subscription",
|
|
7566
|
+
onComplete,
|
|
7567
|
+
onErrorChange: setModeError,
|
|
7568
|
+
onDecline,
|
|
7569
|
+
onButtonClick,
|
|
7570
|
+
onInitializationStateChange: handlePayPalInitializationStateChange,
|
|
7571
|
+
allowAutomaticRetry: paypalManualRetryGeneration === 0,
|
|
7572
|
+
session,
|
|
7573
|
+
telemetry,
|
|
7574
|
+
telemetryContext: telemetryCheckoutContext,
|
|
7575
|
+
debug
|
|
7576
|
+
},
|
|
7577
|
+
`paypal-init-${paypalManualRetryGeneration}`
|
|
7578
|
+
)
|
|
7786
7579
|
}
|
|
7787
7580
|
)
|
|
7788
7581
|
] }) }),
|
|
@@ -7793,8 +7586,8 @@ function FloPayCheckout({
|
|
|
7793
7586
|
return /* @__PURE__ */ jsx8(Fragment3, { children: modeOverlay });
|
|
7794
7587
|
}
|
|
7795
7588
|
if (currentMode === "confirm") {
|
|
7796
|
-
return /* @__PURE__ */
|
|
7797
|
-
/* @__PURE__ */ jsx8(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx8(FloPayProvider, { flopay, paypalFlopay, options: providerOptions, children: /* @__PURE__ */
|
|
7589
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
7590
|
+
/* @__PURE__ */ jsx8(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx8(FloPayProvider, { flopay, paypalFlopay, options: providerOptions, children: /* @__PURE__ */ jsxs6("div", { className, children: [
|
|
7798
7591
|
modeError && /* @__PURE__ */ jsx8(
|
|
7799
7592
|
"div",
|
|
7800
7593
|
{
|
|
@@ -7835,8 +7628,8 @@ function FloPayCheckout({
|
|
|
7835
7628
|
modeOverlay
|
|
7836
7629
|
] });
|
|
7837
7630
|
}
|
|
7838
|
-
return /* @__PURE__ */
|
|
7839
|
-
/* @__PURE__ */ jsx8(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx8(FloPayProvider, { flopay, paypalFlopay, options: providerOptions, children: children ? /* @__PURE__ */
|
|
7631
|
+
return /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
7632
|
+
/* @__PURE__ */ jsx8(CheckoutContext.Provider, { value: checkoutValue, children: /* @__PURE__ */ jsx8(FloPayProvider, { flopay, paypalFlopay, options: providerOptions, children: children ? /* @__PURE__ */ jsxs6(Fragment3, { children: [
|
|
7840
7633
|
modeError && /* @__PURE__ */ jsx8(
|
|
7841
7634
|
"div",
|
|
7842
7635
|
{
|
|
@@ -7878,7 +7671,6 @@ function FloPayCheckout({
|
|
|
7878
7671
|
onDecline,
|
|
7879
7672
|
error: modeError,
|
|
7880
7673
|
onErrorChange: setModeError,
|
|
7881
|
-
onFullNameChange,
|
|
7882
7674
|
showPayPal,
|
|
7883
7675
|
showStripe,
|
|
7884
7676
|
enabledPaymentMethods: unified?.data.stripe?.enabledPaymentMethods,
|
|
@@ -7907,7 +7699,6 @@ function FloPayCheckout({
|
|
|
7907
7699
|
avsCheck: !!enableAVS,
|
|
7908
7700
|
checkoutType: createSessionParams ? "embedded_checkout" : "standard_checkout",
|
|
7909
7701
|
checkoutLayout: children ? "custom_layout" : layout === "buttons" ? "buttons_layout" : "default_layout",
|
|
7910
|
-
submitLabel,
|
|
7911
7702
|
className,
|
|
7912
7703
|
directPaypal: resolveDirectPaypalConfig(unified),
|
|
7913
7704
|
isSubscription: session?.mode === "subscription",
|
|
@@ -7946,10 +7737,6 @@ function SessionInjector({
|
|
|
7946
7737
|
}
|
|
7947
7738
|
function InterimButtonsView({
|
|
7948
7739
|
onButtonClick,
|
|
7949
|
-
onCardButtonClick,
|
|
7950
|
-
cardLoading = false,
|
|
7951
|
-
cardOpen,
|
|
7952
|
-
errorMessage,
|
|
7953
7740
|
showPayPal,
|
|
7954
7741
|
showStripe = true,
|
|
7955
7742
|
showApplePay,
|
|
@@ -7962,12 +7749,6 @@ function InterimButtonsView({
|
|
|
7962
7749
|
cardTitleContent
|
|
7963
7750
|
}) {
|
|
7964
7751
|
const [showCardForm, setShowCardForm] = useState4(false);
|
|
7965
|
-
const isCardOpenControlled = typeof cardOpen === "boolean";
|
|
7966
|
-
useEffect6(() => {
|
|
7967
|
-
if (isCardOpenControlled) {
|
|
7968
|
-
setShowCardForm(cardOpen);
|
|
7969
|
-
}
|
|
7970
|
-
}, [cardOpen, isCardOpenControlled]);
|
|
7971
7752
|
const themeBundle = useMemo4(() => resolveTheme2(theme), [theme]);
|
|
7972
7753
|
const bStyles = useMemo4(() => {
|
|
7973
7754
|
const base = themeBundle?.buttonsLayout ?? resolveButtonsLayoutTheme2(buttonsTheme);
|
|
@@ -7996,25 +7777,20 @@ function InterimButtonsView({
|
|
|
7996
7777
|
const inputBg = bStyles.cardInputBackground ?? "white";
|
|
7997
7778
|
const hideBackButtonLabel = isEmptySlotContent(cardBackButtonContent);
|
|
7998
7779
|
const hideTitle = isEmptySlotContent(cardTitleContent);
|
|
7999
|
-
return /* @__PURE__ */
|
|
7780
|
+
return /* @__PURE__ */ jsxs6("div", { style: {
|
|
8000
7781
|
backgroundColor: bStyles.cardFormContainer?.backgroundColor ?? "white",
|
|
8001
7782
|
borderRadius: "8px",
|
|
8002
7783
|
animation: "flopay-interim-expand 0.35s cubic-bezier(0.4, 0, 0.2, 1) both",
|
|
8003
7784
|
overflow: "hidden",
|
|
8004
7785
|
...bStyles.cardFormContainer
|
|
8005
7786
|
}, children: [
|
|
8006
|
-
/* @__PURE__ */
|
|
8007
|
-
/* @__PURE__ */
|
|
7787
|
+
/* @__PURE__ */ jsxs6("div", { style: { display: "flex", alignItems: "center", padding: "0.75rem 0 0.625rem" }, children: [
|
|
7788
|
+
/* @__PURE__ */ jsxs6(
|
|
8008
7789
|
"button",
|
|
8009
7790
|
{
|
|
8010
7791
|
type: "button",
|
|
8011
|
-
onClick: () =>
|
|
8012
|
-
if (!isCardOpenControlled) {
|
|
8013
|
-
setShowCardForm(false);
|
|
8014
|
-
}
|
|
8015
|
-
},
|
|
7792
|
+
onClick: () => setShowCardForm(false),
|
|
8016
7793
|
"aria-label": "Back to payment methods",
|
|
8017
|
-
disabled: isCardOpenControlled || cardLoading,
|
|
8018
7794
|
style: {
|
|
8019
7795
|
display: "inline-flex",
|
|
8020
7796
|
alignItems: "center",
|
|
@@ -8026,8 +7802,8 @@ function InterimButtonsView({
|
|
|
8026
7802
|
fontWeight: 500,
|
|
8027
7803
|
padding: 0,
|
|
8028
7804
|
flexShrink: 0,
|
|
8029
|
-
opacity:
|
|
8030
|
-
cursor:
|
|
7805
|
+
opacity: 1,
|
|
7806
|
+
cursor: "pointer",
|
|
8031
7807
|
...bStyles.backButton
|
|
8032
7808
|
},
|
|
8033
7809
|
children: [
|
|
@@ -8056,7 +7832,7 @@ function InterimButtonsView({
|
|
|
8056
7832
|
}, children: /* @__PURE__ */ jsx8(TitleContentSlot, { content: cardTitleContent }) })
|
|
8057
7833
|
] }),
|
|
8058
7834
|
/* @__PURE__ */ jsx8("div", { style: { backgroundColor: inputBg, border: `1px solid ${inputBorder}`, borderTopLeftRadius: 8, borderTopRightRadius: 8, padding: 12, height: 45 }, children: /* @__PURE__ */ jsx8("div", { style: { width: "60%", height: 14, borderRadius: 4, background: "#e5e7eb", animation: "flopay-interim-pulse 1.5s ease-in-out infinite" } }) }),
|
|
8059
|
-
/* @__PURE__ */
|
|
7835
|
+
/* @__PURE__ */ jsxs6("div", { style: { display: "flex" }, children: [
|
|
8060
7836
|
/* @__PURE__ */ jsx8("div", { style: {
|
|
8061
7837
|
flex: 1,
|
|
8062
7838
|
backgroundColor: inputBg,
|
|
@@ -8100,23 +7876,17 @@ function InterimButtonsView({
|
|
|
8100
7876
|
` })
|
|
8101
7877
|
] });
|
|
8102
7878
|
}
|
|
8103
|
-
return /* @__PURE__ */
|
|
7879
|
+
return /* @__PURE__ */ jsxs6("div", { style: { display: "flex", flexDirection: "column", gap: "0.5rem" }, children: [
|
|
8104
7880
|
showPayPal && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
8105
7881
|
showStripe && (showApplePay || showGooglePay) && skeleton(DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT2),
|
|
8106
7882
|
showStripe && /* @__PURE__ */ jsx8(
|
|
8107
7883
|
"button",
|
|
8108
7884
|
{
|
|
8109
7885
|
type: "button",
|
|
8110
|
-
onClick:
|
|
8111
|
-
if (cardLoading) return;
|
|
8112
|
-
if (onCardButtonClick) {
|
|
8113
|
-
await onCardButtonClick();
|
|
8114
|
-
return;
|
|
8115
|
-
}
|
|
7886
|
+
onClick: () => {
|
|
8116
7887
|
onButtonClick?.("card");
|
|
8117
7888
|
setShowCardForm(true);
|
|
8118
7889
|
},
|
|
8119
|
-
disabled: cardLoading,
|
|
8120
7890
|
style: {
|
|
8121
7891
|
width: "100%",
|
|
8122
7892
|
...cardButtonSizing,
|
|
@@ -8126,7 +7896,7 @@ function InterimButtonsView({
|
|
|
8126
7896
|
borderRadius: "8px",
|
|
8127
7897
|
fontSize: bStyles.cardButtonFontSize ?? "0.95rem",
|
|
8128
7898
|
fontWeight: 600,
|
|
8129
|
-
cursor:
|
|
7899
|
+
cursor: "pointer",
|
|
8130
7900
|
display: "flex",
|
|
8131
7901
|
alignItems: "center",
|
|
8132
7902
|
justifyContent: "center",
|
|
@@ -8134,7 +7904,7 @@ function InterimButtonsView({
|
|
|
8134
7904
|
boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
|
|
8135
7905
|
transition: "transform 0.1s",
|
|
8136
7906
|
position: "relative",
|
|
8137
|
-
opacity:
|
|
7907
|
+
opacity: 1,
|
|
8138
7908
|
...bStyles.cardButton
|
|
8139
7909
|
},
|
|
8140
7910
|
onMouseDown: (e) => {
|
|
@@ -8146,377 +7916,77 @@ function InterimButtonsView({
|
|
|
8146
7916
|
children: /* @__PURE__ */ jsx8(CardButtonContentSlot, { content: cardButtonContent })
|
|
8147
7917
|
}
|
|
8148
7918
|
),
|
|
8149
|
-
errorMessage && /* @__PURE__ */ jsxs5("div", { style: {
|
|
8150
|
-
margin: "0.25rem 0",
|
|
8151
|
-
padding: "0.625rem 0.875rem",
|
|
8152
|
-
background: "#FEF2F2",
|
|
8153
|
-
border: "1px solid #FECACA",
|
|
8154
|
-
borderRadius: "8px",
|
|
8155
|
-
color: "#991B1B",
|
|
8156
|
-
fontSize: "0.85rem",
|
|
8157
|
-
fontWeight: 600,
|
|
8158
|
-
display: "flex",
|
|
8159
|
-
alignItems: "center",
|
|
8160
|
-
gap: "0.5rem",
|
|
8161
|
-
...bStyles.errorBanner
|
|
8162
|
-
}, children: [
|
|
8163
|
-
/* @__PURE__ */ jsx8("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx8("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" }) }),
|
|
8164
|
-
errorMessage
|
|
8165
|
-
] }),
|
|
8166
7919
|
/* @__PURE__ */ jsx8("style", { children: `@keyframes flopay-interim-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }` })
|
|
8167
7920
|
] });
|
|
8168
7921
|
}
|
|
8169
7922
|
|
|
8170
|
-
// src/
|
|
8171
|
-
import {
|
|
8172
|
-
import {
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
|
|
8188
|
-
|
|
8189
|
-
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
children,
|
|
8196
|
-
firstName,
|
|
8197
|
-
lastName,
|
|
8198
|
-
chv,
|
|
8199
|
-
isProcessing: externalProcessing,
|
|
8200
|
-
error: externalError,
|
|
8201
|
-
onErrorChange,
|
|
8202
|
-
innerRef
|
|
8203
|
-
}) {
|
|
8204
|
-
const flopay = useFloPay();
|
|
8205
|
-
const paypalFlopay = usePayPalFloPay();
|
|
8206
|
-
const elements = useElements();
|
|
8207
|
-
const contextBillingUrl = useBillingApiUrl();
|
|
8208
|
-
const [processing, setProcessing] = useState5(false);
|
|
8209
|
-
const [error, setError] = useState5(null);
|
|
8210
|
-
const [is3DSActive, setIs3DSActive] = useState5(false);
|
|
8211
|
-
const displayError = externalError ?? error;
|
|
8212
|
-
const isSubmitting = externalProcessing ?? processing;
|
|
8213
|
-
const isSelfContained = !onTokenizedBody;
|
|
8214
|
-
const baseUrl = (billingApiUrl || contextBillingUrl).replace(/\/+$/, "");
|
|
8215
|
-
const updateError = useCallback5(
|
|
8216
|
-
(err) => {
|
|
8217
|
-
setError(err);
|
|
8218
|
-
onErrorChange?.(err);
|
|
8219
|
-
},
|
|
8220
|
-
[onErrorChange]
|
|
8221
|
-
);
|
|
8222
|
-
const emitDecline = useCallback5(
|
|
8223
|
-
(input, overrides) => {
|
|
8224
|
-
onDecline?.(buildDeclineEvent("card", input, overrides));
|
|
8225
|
-
},
|
|
8226
|
-
[onDecline]
|
|
8227
|
-
);
|
|
8228
|
-
const processPaymentInternal = useCallback5(
|
|
8229
|
-
async (tokenizedBody, completionPaymentMethodId) => {
|
|
8230
|
-
setProcessing(true);
|
|
8231
|
-
updateError(null);
|
|
8232
|
-
const resolvedCompletionPaymentMethodId = completionPaymentMethodId ?? resolveTokenizedPaymentMethodId(tokenizedBody);
|
|
8233
|
-
const requestTokenizedBody = tokenizedBody.originalPaymentMethodId ? { ...tokenizedBody, originalPaymentMethodId: void 0 } : tokenizedBody;
|
|
8234
|
-
try {
|
|
8235
|
-
const api = new PaymentAPI5(baseUrl);
|
|
8236
|
-
const response = await api.processPayment(userId ?? "", {
|
|
8237
|
-
sessionId,
|
|
8238
|
-
nonce,
|
|
8239
|
-
tokenizedData: requestTokenizedBody,
|
|
8240
|
-
accountData: {
|
|
8241
|
-
userId: userId ?? "",
|
|
8242
|
-
email: email ?? "",
|
|
8243
|
-
firstName: firstName ?? "",
|
|
8244
|
-
lastName: lastName ?? ""
|
|
8245
|
-
},
|
|
8246
|
-
chv
|
|
8247
|
-
});
|
|
8248
|
-
if (response.ok) {
|
|
8249
|
-
onComplete?.({
|
|
8250
|
-
status: "succeeded",
|
|
8251
|
-
paymentIntentId: tokenizedBody.threeDSecureActionResultTokenId,
|
|
8252
|
-
paymentMethodId: resolvedCompletionPaymentMethodId,
|
|
8253
|
-
checkoutMethod: tokenizedBody.isPaypal ? "paypal" : "card"
|
|
8254
|
-
});
|
|
8255
|
-
return;
|
|
8256
|
-
}
|
|
8257
|
-
const json = await response.json().catch(() => null);
|
|
8258
|
-
if (json?.type === "3ds_required") {
|
|
8259
|
-
const secret = json["threeDSecureToken"];
|
|
8260
|
-
if (!flopay || !secret) {
|
|
8261
|
-
updateError("3DS authentication required but no token provided.");
|
|
8262
|
-
return;
|
|
8263
|
-
}
|
|
8264
|
-
setIs3DSActive(true);
|
|
8265
|
-
try {
|
|
8266
|
-
const retryFullName = `${firstName ?? ""} ${lastName ?? ""}`.trim();
|
|
8267
|
-
const result = await flopay.confirmPayment({
|
|
8268
|
-
clientSecret: secret,
|
|
8269
|
-
returnUrl: window.location.href,
|
|
8270
|
-
billingDetails: {
|
|
8271
|
-
...email ? { email } : {},
|
|
8272
|
-
...retryFullName ? { name: retryFullName } : {}
|
|
8273
|
-
}
|
|
8274
|
-
});
|
|
8275
|
-
if (result.error) {
|
|
8276
|
-
updateError(result.error.message);
|
|
8277
|
-
onError?.(result.error);
|
|
8278
|
-
emitDecline(result.error);
|
|
8279
|
-
return;
|
|
8280
|
-
}
|
|
8281
|
-
if (result.status === "succeeded" || result.status === "processing") {
|
|
8282
|
-
await processPaymentInternal({
|
|
8283
|
-
id: result.paymentIntentId,
|
|
8284
|
-
type: "card",
|
|
8285
|
-
threeDSecureActionResultTokenId: result.paymentIntentId
|
|
8286
|
-
}, resolvedCompletionPaymentMethodId);
|
|
8287
|
-
}
|
|
8288
|
-
} finally {
|
|
8289
|
-
setIs3DSActive(false);
|
|
8290
|
-
}
|
|
8291
|
-
return;
|
|
8292
|
-
}
|
|
8293
|
-
if (json?.type === "paypal_redirect_required") {
|
|
8294
|
-
const secret = json["threeDSecureToken"] ?? json["clientSecret"];
|
|
8295
|
-
const pmId = json["paymentMethodId"];
|
|
8296
|
-
const paypalStripe = (paypalFlopay ?? flopay)?.getRawProvider();
|
|
8297
|
-
if (!paypalStripe || !secret) {
|
|
8298
|
-
const message = "PayPal is not available.";
|
|
8299
|
-
updateError(message);
|
|
8300
|
-
onDecline?.(buildDeclineEvent("paypal", message));
|
|
8301
|
-
return;
|
|
8302
|
-
}
|
|
8303
|
-
localStorage.setItem(WALLET_RESUME_KEY, JSON.stringify({
|
|
8304
|
-
sessionId,
|
|
8305
|
-
paymentIntentId: "",
|
|
8306
|
-
tokenType: "card",
|
|
8307
|
-
tokenId: pmId ?? "",
|
|
8308
|
-
status: "pending_redirect"
|
|
8309
|
-
}));
|
|
8310
|
-
const confirmParams = {
|
|
8311
|
-
return_url: window.location.href
|
|
8312
|
-
};
|
|
8313
|
-
if (pmId) confirmParams["payment_method"] = pmId;
|
|
8314
|
-
const { error: confirmError } = await paypalStripe.confirmPayment({
|
|
8315
|
-
clientSecret: secret,
|
|
8316
|
-
confirmParams,
|
|
8317
|
-
redirect: "if_required"
|
|
8318
|
-
});
|
|
8319
|
-
if (confirmError) {
|
|
8320
|
-
const message = confirmError.message ?? "PayPal payment failed.";
|
|
8321
|
-
updateError(message);
|
|
8322
|
-
onDecline?.(buildDeclineEvent("paypal", message, { code: confirmError.code }));
|
|
8323
|
-
}
|
|
8324
|
-
return;
|
|
8325
|
-
}
|
|
8326
|
-
const errorMessage = json?.message ?? "Payment failed. Please try again.";
|
|
8327
|
-
updateError(errorMessage);
|
|
8328
|
-
emitDecline(errorMessage, {
|
|
8329
|
-
code: json?.code,
|
|
8330
|
-
declineCode: json?.declineCode ?? json?.gatewayDeclineReason
|
|
8331
|
-
});
|
|
8332
|
-
} catch (err) {
|
|
8333
|
-
updateError(err instanceof Error ? err.message : "An unexpected error occurred");
|
|
8334
|
-
} finally {
|
|
8335
|
-
setProcessing(false);
|
|
8336
|
-
}
|
|
8337
|
-
},
|
|
8338
|
-
[baseUrl, sessionId, nonce, userId, email, firstName, lastName, chv, flopay, paypalFlopay, onComplete, onError, onDecline, updateError, emitDecline]
|
|
8339
|
-
);
|
|
8340
|
-
const dispatchTokenizedBody = useCallback5(
|
|
8341
|
-
(tokenizedBody) => {
|
|
8342
|
-
if (onTokenizedBody) {
|
|
8343
|
-
onTokenizedBody(tokenizedBody);
|
|
8344
|
-
} else {
|
|
8345
|
-
processPaymentInternal(tokenizedBody);
|
|
8346
|
-
}
|
|
8347
|
-
},
|
|
8348
|
-
[onTokenizedBody, processPaymentInternal]
|
|
8349
|
-
);
|
|
8350
|
-
useImperativeHandle2(innerRef, () => ({
|
|
8351
|
-
async handleNextAction(secret) {
|
|
8352
|
-
if (!flopay) return;
|
|
8353
|
-
setIs3DSActive(true);
|
|
8354
|
-
try {
|
|
8355
|
-
const result = await flopay.confirmPayment({
|
|
8356
|
-
clientSecret: secret,
|
|
8357
|
-
returnUrl: window.location.href
|
|
8358
|
-
});
|
|
8359
|
-
if (result.error) {
|
|
8360
|
-
updateError(result.error.message);
|
|
8361
|
-
onError?.(result.error);
|
|
8362
|
-
emitDecline(result.error);
|
|
8363
|
-
} else if (result.status === "succeeded" || result.status === "processing") {
|
|
8364
|
-
dispatchTokenizedBody({
|
|
8365
|
-
id: result.paymentIntentId,
|
|
8366
|
-
type: "card",
|
|
8367
|
-
threeDSecureActionResultTokenId: result.paymentIntentId
|
|
8368
|
-
});
|
|
8369
|
-
}
|
|
8370
|
-
} catch (err) {
|
|
8371
|
-
updateError(err instanceof Error ? err.message : "Payment authentication failed.");
|
|
8372
|
-
} finally {
|
|
8373
|
-
setIs3DSActive(false);
|
|
8374
|
-
}
|
|
8375
|
-
}
|
|
8376
|
-
}), [flopay, dispatchTokenizedBody, onError, updateError, emitDecline]);
|
|
8377
|
-
useEffect7(() => {
|
|
8378
|
-
if (typeof window === "undefined") return;
|
|
8379
|
-
const stored = localStorage.getItem(WALLET_RESUME_KEY);
|
|
8380
|
-
if (!stored) return;
|
|
8381
|
-
try {
|
|
8382
|
-
const payload = JSON.parse(stored);
|
|
8383
|
-
if (payload.sessionId === sessionId) {
|
|
8384
|
-
localStorage.removeItem(WALLET_RESUME_KEY);
|
|
8385
|
-
dispatchTokenizedBody({
|
|
8386
|
-
id: payload.tokenId,
|
|
8387
|
-
type: payload.tokenType,
|
|
8388
|
-
threeDSecureActionResultTokenId: payload.paymentIntentId
|
|
8389
|
-
});
|
|
8390
|
-
}
|
|
8391
|
-
} catch {
|
|
8392
|
-
localStorage.removeItem(WALLET_RESUME_KEY);
|
|
8393
|
-
}
|
|
8394
|
-
}, [sessionId, dispatchTokenizedBody]);
|
|
8395
|
-
const handleSubmit = useCallback5(
|
|
8396
|
-
async (e) => {
|
|
8397
|
-
e.preventDefault();
|
|
8398
|
-
if (!flopay || !elements || isSubmitting) return;
|
|
8399
|
-
setProcessing(true);
|
|
8400
|
-
updateError(null);
|
|
8401
|
-
let handedOff = false;
|
|
8402
|
-
try {
|
|
8403
|
-
const submitResult = await flopay.submitElements();
|
|
8404
|
-
if (submitResult.error) {
|
|
8405
|
-
updateError(submitResult.error.message);
|
|
8406
|
-
onError?.(submitResult.error);
|
|
8407
|
-
return;
|
|
8408
|
-
}
|
|
8409
|
-
const fullName = `${firstName ?? ""} ${lastName ?? ""}`.trim();
|
|
8410
|
-
const pmResult = await flopay.createPaymentMethod({
|
|
8411
|
-
...email ? { email } : {},
|
|
8412
|
-
...fullName ? { name: fullName } : {}
|
|
8413
|
-
});
|
|
8414
|
-
if (pmResult.error || !pmResult.paymentMethodId) {
|
|
8415
|
-
updateError(pmResult.error?.message ?? "Failed to create payment method.");
|
|
8416
|
-
return;
|
|
8417
|
-
}
|
|
8418
|
-
if (!sessionId || !email) {
|
|
8419
|
-
throw new FloPayError6("Missing sessionId or email", "validation_error");
|
|
8420
|
-
}
|
|
8421
|
-
const intentHeaders = { "Content-Type": "application/json" };
|
|
8422
|
-
if (nonce) intentHeaders["x-checkout-session-token"] = nonce;
|
|
8423
|
-
const intentResponse = await fetch(`${baseUrl}/v1/checkouts/payments/intents`, {
|
|
8424
|
-
method: "POST",
|
|
8425
|
-
headers: intentHeaders,
|
|
8426
|
-
body: JSON.stringify({
|
|
8427
|
-
sessionId,
|
|
8428
|
-
email,
|
|
8429
|
-
paymentMethodType: pmResult.paymentMethodId,
|
|
8430
|
-
isPaypal: false
|
|
8431
|
-
})
|
|
8432
|
-
});
|
|
8433
|
-
if (!intentResponse.ok) {
|
|
8434
|
-
const intentError = await buildFloPayApiErrorFromResponse(
|
|
8435
|
-
intentResponse,
|
|
8436
|
-
"Failed to create payment intent"
|
|
8437
|
-
);
|
|
8438
|
-
updateError(intentError.message);
|
|
8439
|
-
onError?.(intentError);
|
|
8440
|
-
emitDecline(intentError);
|
|
8441
|
-
return;
|
|
8442
|
-
}
|
|
8443
|
-
const intentJson = await intentResponse.json();
|
|
8444
|
-
const intentClientSecret = intentJson.data?.id;
|
|
8445
|
-
if (!intentClientSecret) throw new FloPayError6("No client_secret in payment intent response", "api_error");
|
|
8446
|
-
const confirmResult = await flopay.confirmCardPayment({
|
|
8447
|
-
clientSecret: intentClientSecret,
|
|
8448
|
-
paymentMethodId: pmResult.paymentMethodId
|
|
8449
|
-
});
|
|
8450
|
-
if (confirmResult.error) {
|
|
8451
|
-
updateError(confirmResult.error.message);
|
|
8452
|
-
onError?.(confirmResult.error);
|
|
8453
|
-
emitDecline(confirmResult.error);
|
|
8454
|
-
return;
|
|
7923
|
+
// src/elements.tsx
|
|
7924
|
+
import { useEffect as useEffect6, useRef as useRef6, useContext as useContext3 } from "react";
|
|
7925
|
+
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
7926
|
+
function createElementComponent(elementType, displayName) {
|
|
7927
|
+
function ElementComponent({
|
|
7928
|
+
className,
|
|
7929
|
+
id,
|
|
7930
|
+
style,
|
|
7931
|
+
options,
|
|
7932
|
+
onChange,
|
|
7933
|
+
onReady,
|
|
7934
|
+
onFocus,
|
|
7935
|
+
onBlur,
|
|
7936
|
+
onEscape
|
|
7937
|
+
}) {
|
|
7938
|
+
const containerRef = useRef6(null);
|
|
7939
|
+
const elementRef = useRef6(null);
|
|
7940
|
+
const { elements, reportInteractive } = useContext3(FloPayContext);
|
|
7941
|
+
useEffect6(() => {
|
|
7942
|
+
if (!elements || !containerRef.current) return;
|
|
7943
|
+
let mounted = true;
|
|
7944
|
+
(async () => {
|
|
7945
|
+
let element = elements.getElement(elementType);
|
|
7946
|
+
if (!element) {
|
|
7947
|
+
element = await elements.create(elementType, options);
|
|
8455
7948
|
}
|
|
8456
|
-
|
|
8457
|
-
const confirmedPaymentIntent = await retrievePaymentIntentFromProvider(
|
|
8458
|
-
rawProvider,
|
|
8459
|
-
intentClientSecret
|
|
8460
|
-
);
|
|
8461
|
-
const paymentIntentId = confirmResult.paymentIntentId ?? confirmedPaymentIntent?.id;
|
|
8462
|
-
const paymentMethodId = confirmResult.paymentMethodId ?? resolvePaymentIntentPaymentMethodId(confirmedPaymentIntent) ?? pmResult.paymentMethodId;
|
|
8463
|
-
if (!paymentIntentId) {
|
|
8464
|
-
const error2 = new FloPayError6("No payment intent returned after confirmation.", "api_error");
|
|
8465
|
-
updateError(error2.message);
|
|
8466
|
-
onError?.(error2);
|
|
7949
|
+
if (!mounted || !containerRef.current) {
|
|
8467
7950
|
return;
|
|
8468
7951
|
}
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
7952
|
+
element.mount(containerRef.current);
|
|
7953
|
+
elementRef.current = element;
|
|
7954
|
+
if (onChange) element.on("change", onChange);
|
|
7955
|
+
element.on("ready", () => {
|
|
7956
|
+
reportInteractive?.();
|
|
7957
|
+
onReady?.();
|
|
8475
7958
|
});
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
(is3DSActive || isSubmitting) && /* @__PURE__ */ jsx9("div", { "data-testid": "flopay-overlay", style: {
|
|
8489
|
-
position: "absolute",
|
|
8490
|
-
inset: 0,
|
|
8491
|
-
background: "rgba(255,255,255,0.7)",
|
|
8492
|
-
display: "flex",
|
|
8493
|
-
alignItems: "center",
|
|
8494
|
-
justifyContent: "center",
|
|
8495
|
-
zIndex: 10
|
|
8496
|
-
}, children: is3DSActive ? "Verifying payment..." : "Processing..." }),
|
|
8497
|
-
!isReady && /* @__PURE__ */ jsx9("div", { "data-testid": "flopay-loading", "aria-busy": "true", children: "Loading payment form..." }),
|
|
8498
|
-
isReady && /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
8499
|
-
/* @__PURE__ */ jsx9(PaymentElement, { options: { layout } }),
|
|
8500
|
-
showAddress && /* @__PURE__ */ jsx9(AddressElement, { options: { mode: showAddress === true ? "billing" : showAddress } }),
|
|
8501
|
-
displayError && /* @__PURE__ */ jsx9("div", { role: "alert", "data-testid": "flopay-error", style: { color: "red", margin: "0.75rem 0" }, children: displayError }),
|
|
8502
|
-
children ?? /* @__PURE__ */ jsx9(
|
|
8503
|
-
"button",
|
|
8504
|
-
{
|
|
8505
|
-
type: "submit",
|
|
8506
|
-
disabled: isSubmitting || !isReady,
|
|
8507
|
-
"data-testid": "flopay-submit",
|
|
8508
|
-
children: isSubmitting ? "Processing..." : submitLabel
|
|
7959
|
+
if (onFocus) element.on("focus", onFocus);
|
|
7960
|
+
if (onBlur) element.on("blur", onBlur);
|
|
7961
|
+
if (onEscape) element.on("escape", onEscape);
|
|
7962
|
+
})();
|
|
7963
|
+
return () => {
|
|
7964
|
+
mounted = false;
|
|
7965
|
+
if (elementRef.current) {
|
|
7966
|
+
try {
|
|
7967
|
+
elementRef.current.unmount();
|
|
7968
|
+
} catch {
|
|
7969
|
+
}
|
|
7970
|
+
elementRef.current = null;
|
|
8509
7971
|
}
|
|
8510
|
-
|
|
8511
|
-
]
|
|
8512
|
-
|
|
7972
|
+
};
|
|
7973
|
+
}, [elements, reportInteractive]);
|
|
7974
|
+
return /* @__PURE__ */ jsx9("div", { ref: containerRef, className, id, style });
|
|
7975
|
+
}
|
|
7976
|
+
ElementComponent.displayName = displayName;
|
|
7977
|
+
return ElementComponent;
|
|
8513
7978
|
}
|
|
7979
|
+
var PaymentElement2 = createElementComponent(
|
|
7980
|
+
"payment",
|
|
7981
|
+
"PaymentElement"
|
|
7982
|
+
);
|
|
7983
|
+
var AddressElement = createElementComponent("address", "AddressElement");
|
|
8514
7984
|
|
|
8515
7985
|
// src/paypal-button.tsx
|
|
8516
|
-
import { PaymentAPI as
|
|
8517
|
-
import { FloPayError as
|
|
8518
|
-
import { useCallback as
|
|
8519
|
-
import { Fragment as
|
|
7986
|
+
import { PaymentAPI as PaymentAPI5 } from "@flopay/js";
|
|
7987
|
+
import { FloPayError as FloPayError6 } from "@flopay/shared";
|
|
7988
|
+
import { useCallback as useCallback5, useEffect as useEffect7, useRef as useRef7, useState as useState5 } from "react";
|
|
7989
|
+
import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
8520
7990
|
function PayPalButton({
|
|
8521
7991
|
sessionId,
|
|
8522
7992
|
nonce,
|
|
@@ -8534,14 +8004,14 @@ function PayPalButton({
|
|
|
8534
8004
|
const flopay = useFloPay();
|
|
8535
8005
|
const elements = useElements();
|
|
8536
8006
|
const contextBillingUrl = useBillingApiUrl();
|
|
8537
|
-
const [ready, setReady] =
|
|
8538
|
-
const [submitting, setSubmitting] =
|
|
8007
|
+
const [ready, setReady] = useState5(false);
|
|
8008
|
+
const [submitting, setSubmitting] = useState5(false);
|
|
8539
8009
|
const paypalResumeAttempted = useRef7(false);
|
|
8540
8010
|
const baseUrl = (billingApiUrl || contextBillingUrl).replace(/\/+$/, "");
|
|
8541
|
-
const processPaymentInternal =
|
|
8011
|
+
const processPaymentInternal = useCallback5(
|
|
8542
8012
|
async (tokenizedBody) => {
|
|
8543
8013
|
try {
|
|
8544
|
-
const api = new
|
|
8014
|
+
const api = new PaymentAPI5(baseUrl);
|
|
8545
8015
|
const response = await api.processPayment(userId ?? "", {
|
|
8546
8016
|
sessionId,
|
|
8547
8017
|
nonce,
|
|
@@ -8566,7 +8036,7 @@ function PayPalButton({
|
|
|
8566
8036
|
},
|
|
8567
8037
|
[baseUrl, sessionId, nonce, userId, email, firstName, lastName, chv, onComplete, onErrorChange]
|
|
8568
8038
|
);
|
|
8569
|
-
const dispatchTokenizedBody =
|
|
8039
|
+
const dispatchTokenizedBody = useCallback5(
|
|
8570
8040
|
(body) => {
|
|
8571
8041
|
if (onTokenizedBody) {
|
|
8572
8042
|
onTokenizedBody(body);
|
|
@@ -8576,7 +8046,7 @@ function PayPalButton({
|
|
|
8576
8046
|
},
|
|
8577
8047
|
[onTokenizedBody, processPaymentInternal]
|
|
8578
8048
|
);
|
|
8579
|
-
|
|
8049
|
+
useEffect7(() => {
|
|
8580
8050
|
if (!flopay || paypalResumeAttempted.current) return;
|
|
8581
8051
|
const params = new URLSearchParams(window.location.search);
|
|
8582
8052
|
const paymentIntentId = params.get("payment_intent");
|
|
@@ -8624,13 +8094,13 @@ function PayPalButton({
|
|
|
8624
8094
|
}
|
|
8625
8095
|
})();
|
|
8626
8096
|
}, [flopay, dispatchTokenizedBody, onErrorChange]);
|
|
8627
|
-
const handlePayPalConfirm =
|
|
8097
|
+
const handlePayPalConfirm = useCallback5(async () => {
|
|
8628
8098
|
if (!flopay || !elements) return;
|
|
8629
8099
|
try {
|
|
8630
8100
|
setSubmitting(true);
|
|
8631
8101
|
onErrorChange?.(null);
|
|
8632
8102
|
if (!sessionId || !email) {
|
|
8633
|
-
throw new
|
|
8103
|
+
throw new FloPayError6("Missing sessionId or email for PayPal payment", "validation_error");
|
|
8634
8104
|
}
|
|
8635
8105
|
const result = await flopay.confirmPayPalPayment({
|
|
8636
8106
|
billingApiUrl: baseUrl,
|
|
@@ -8660,7 +8130,7 @@ function PayPalButton({
|
|
|
8660
8130
|
if (!flopay || !elements) {
|
|
8661
8131
|
return /* @__PURE__ */ jsx10("div", { style: { height: 45, background: "#f0f0f0", borderRadius: 6, animation: "pulse 1.5s infinite" } });
|
|
8662
8132
|
}
|
|
8663
|
-
return /* @__PURE__ */ jsxs7(
|
|
8133
|
+
return /* @__PURE__ */ jsxs7(Fragment4, { children: [
|
|
8664
8134
|
!ready && /* @__PURE__ */ jsx10("div", { style: { height: 45, background: "#f0f0f0", borderRadius: 6 } }),
|
|
8665
8135
|
/* @__PURE__ */ jsx10("div", { style: ready ? {} : { display: "none" }, children: /* @__PURE__ */ jsx10(
|
|
8666
8136
|
"button",
|
|
@@ -8704,19 +8174,19 @@ function PayPalButton({
|
|
|
8704
8174
|
}
|
|
8705
8175
|
|
|
8706
8176
|
// src/automatic-payment-button.tsx
|
|
8707
|
-
import { useCallback as
|
|
8708
|
-
import { PaymentAPI as
|
|
8709
|
-
import { FloPayError as
|
|
8710
|
-
import { Fragment as
|
|
8177
|
+
import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo5, useRef as useRef8, useState as useState6 } from "react";
|
|
8178
|
+
import { PaymentAPI as PaymentAPI6 } from "@flopay/js";
|
|
8179
|
+
import { FloPayError as FloPayError7, resolveBillingApiUrl as resolveBillingApiUrl4, resolveButtonsLayoutTheme as resolveButtonsLayoutTheme3, resolveTheme as resolveTheme3 } from "@flopay/shared";
|
|
8180
|
+
import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
8711
8181
|
var DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT3 = 44;
|
|
8712
8182
|
function sleep2(ms) {
|
|
8713
8183
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8714
8184
|
}
|
|
8715
8185
|
function coerceError(err, fallbackMessage) {
|
|
8716
|
-
if (err instanceof
|
|
8186
|
+
if (err instanceof FloPayError7) {
|
|
8717
8187
|
return err;
|
|
8718
8188
|
}
|
|
8719
|
-
return new
|
|
8189
|
+
return new FloPayError7(
|
|
8720
8190
|
err instanceof Error ? err.message : fallbackMessage,
|
|
8721
8191
|
"api_error"
|
|
8722
8192
|
);
|
|
@@ -8819,36 +8289,36 @@ function FloPayAutomaticPaymentButton({
|
|
|
8819
8289
|
utmMetadata
|
|
8820
8290
|
]
|
|
8821
8291
|
);
|
|
8822
|
-
const [isProcessing, setIsProcessing] =
|
|
8823
|
-
const [overlayStatus, setOverlayStatus] =
|
|
8824
|
-
const [overlayError, setOverlayError] =
|
|
8825
|
-
const [fallbackSession, setFallbackSession] =
|
|
8292
|
+
const [isProcessing, setIsProcessing] = useState6(false);
|
|
8293
|
+
const [overlayStatus, setOverlayStatus] = useState6(null);
|
|
8294
|
+
const [overlayError, setOverlayError] = useState6(null);
|
|
8295
|
+
const [fallbackSession, setFallbackSession] = useState6(null);
|
|
8826
8296
|
const isMountedRef = useRef8(true);
|
|
8827
8297
|
const threeDsAbortRef = useRef8(null);
|
|
8828
8298
|
const fallbackSessionRef = useRef8(fallbackSession);
|
|
8829
8299
|
const onSuccessRef = useRef8(onSuccess);
|
|
8830
8300
|
const onErrorRef = useRef8(onError);
|
|
8831
8301
|
const onDeclineRef = useRef8(onDecline);
|
|
8832
|
-
|
|
8302
|
+
useEffect8(() => {
|
|
8833
8303
|
fallbackSessionRef.current = fallbackSession;
|
|
8834
8304
|
}, [fallbackSession]);
|
|
8835
|
-
|
|
8305
|
+
useEffect8(() => {
|
|
8836
8306
|
onSuccessRef.current = onSuccess;
|
|
8837
8307
|
}, [onSuccess]);
|
|
8838
|
-
|
|
8308
|
+
useEffect8(() => {
|
|
8839
8309
|
onErrorRef.current = onError;
|
|
8840
8310
|
}, [onError]);
|
|
8841
|
-
|
|
8311
|
+
useEffect8(() => {
|
|
8842
8312
|
onDeclineRef.current = onDecline;
|
|
8843
8313
|
}, [onDecline]);
|
|
8844
|
-
|
|
8314
|
+
useEffect8(() => {
|
|
8845
8315
|
isMountedRef.current = true;
|
|
8846
8316
|
return () => {
|
|
8847
8317
|
isMountedRef.current = false;
|
|
8848
8318
|
threeDsAbortRef.current?.abort();
|
|
8849
8319
|
};
|
|
8850
8320
|
}, []);
|
|
8851
|
-
|
|
8321
|
+
useEffect8(() => {
|
|
8852
8322
|
if (typeof window === "undefined") {
|
|
8853
8323
|
return;
|
|
8854
8324
|
}
|
|
@@ -8862,13 +8332,13 @@ function FloPayAutomaticPaymentButton({
|
|
|
8862
8332
|
window.removeEventListener("keydown", handleKeyDown);
|
|
8863
8333
|
};
|
|
8864
8334
|
}, []);
|
|
8865
|
-
const emitDecline =
|
|
8335
|
+
const emitDecline = useCallback6((error, method = DEFAULT_SAVED_PAYMENT_DECLINE_METHOD) => {
|
|
8866
8336
|
onDeclineRef.current?.(buildDeclineEvent(method, error, {
|
|
8867
8337
|
code: error.code,
|
|
8868
8338
|
declineCode: error.declineCode
|
|
8869
8339
|
}));
|
|
8870
8340
|
}, []);
|
|
8871
|
-
const showSuccess =
|
|
8341
|
+
const showSuccess = useCallback6(async (event) => {
|
|
8872
8342
|
if (!isMountedRef.current) return;
|
|
8873
8343
|
setOverlayError(null);
|
|
8874
8344
|
setOverlayStatus("success");
|
|
@@ -8876,7 +8346,7 @@ function FloPayAutomaticPaymentButton({
|
|
|
8876
8346
|
if (!isMountedRef.current) return;
|
|
8877
8347
|
onSuccessRef.current?.(event);
|
|
8878
8348
|
}, []);
|
|
8879
|
-
const showError =
|
|
8349
|
+
const showError = useCallback6(async (error, options) => {
|
|
8880
8350
|
if (!isMountedRef.current) return;
|
|
8881
8351
|
onErrorRef.current?.(error);
|
|
8882
8352
|
if (options?.emitDecline) {
|
|
@@ -8886,10 +8356,10 @@ function FloPayAutomaticPaymentButton({
|
|
|
8886
8356
|
setOverlayStatus("error");
|
|
8887
8357
|
await sleep2(PROCESSING_OVERLAY_ERROR_DELAY_MS);
|
|
8888
8358
|
}, [emitDecline]);
|
|
8889
|
-
const processResolvedSession =
|
|
8359
|
+
const processResolvedSession = useCallback6(async (apiResult, resolvedSessionId, options) => {
|
|
8890
8360
|
const session = apiResult.data.session ?? null;
|
|
8891
8361
|
if (!session) {
|
|
8892
|
-
throw new
|
|
8362
|
+
throw new FloPayError7("No session data returned", "api_error");
|
|
8893
8363
|
}
|
|
8894
8364
|
const effectiveNonce = session.clientSecret || nonce;
|
|
8895
8365
|
if (session.status === "complete") {
|
|
@@ -8902,19 +8372,19 @@ function FloPayAutomaticPaymentButton({
|
|
|
8902
8372
|
return;
|
|
8903
8373
|
}
|
|
8904
8374
|
if (session.status === "expired") {
|
|
8905
|
-
throw new
|
|
8375
|
+
throw new FloPayError7("Checkout session has expired.", "api_error", {
|
|
8906
8376
|
code: "checkout_session_expired"
|
|
8907
8377
|
});
|
|
8908
8378
|
}
|
|
8909
8379
|
try {
|
|
8910
8380
|
if (apiResult.autoProcessingPending) {
|
|
8911
|
-
const api = new
|
|
8381
|
+
const api = new PaymentAPI6(resolvedBillingUrl);
|
|
8912
8382
|
const completed = await api.waitForCheckoutSessionCompletion(apiResult.autoProcessingPending.sessionId, {
|
|
8913
8383
|
initialDelayMs: apiResult.autoProcessingPending.retryAfterMs
|
|
8914
8384
|
});
|
|
8915
8385
|
const completedSession = completed.data.session;
|
|
8916
8386
|
if (!completedSession || completedSession.status !== "complete") {
|
|
8917
|
-
throw new
|
|
8387
|
+
throw new FloPayError7("Automatic payment failed. Please try again.", "api_error", {
|
|
8918
8388
|
code: completedSession?.status === "expired" ? "checkout_session_expired" : "checkout_processing_timeout"
|
|
8919
8389
|
});
|
|
8920
8390
|
}
|
|
@@ -8957,7 +8427,7 @@ function FloPayAutomaticPaymentButton({
|
|
|
8957
8427
|
}
|
|
8958
8428
|
);
|
|
8959
8429
|
}
|
|
8960
|
-
const api = new
|
|
8430
|
+
const api = new PaymentAPI6(resolvedBillingUrl);
|
|
8961
8431
|
const completed = await api.waitForCheckoutSessionCompletion(resolvedSessionId, {
|
|
8962
8432
|
initialDelayMs: 0,
|
|
8963
8433
|
nonce: effectiveNonce
|
|
@@ -9042,14 +8512,14 @@ function FloPayAutomaticPaymentButton({
|
|
|
9042
8512
|
showError,
|
|
9043
8513
|
showSuccess
|
|
9044
8514
|
]);
|
|
9045
|
-
const handleButtonClick =
|
|
8515
|
+
const handleButtonClick = useCallback6(async (event) => {
|
|
9046
8516
|
buttonProps.onClick?.(event);
|
|
9047
8517
|
if (event.defaultPrevented || disabled || isProcessing) {
|
|
9048
8518
|
return;
|
|
9049
8519
|
}
|
|
9050
8520
|
setFallbackSession(null);
|
|
9051
8521
|
if (sessionId && createSessionDraft) {
|
|
9052
|
-
const error = new
|
|
8522
|
+
const error = new FloPayError7(
|
|
9053
8523
|
"Provide either sessionId or create-session props, not both.",
|
|
9054
8524
|
"validation_error"
|
|
9055
8525
|
);
|
|
@@ -9061,7 +8531,7 @@ function FloPayAutomaticPaymentButton({
|
|
|
9061
8531
|
return;
|
|
9062
8532
|
}
|
|
9063
8533
|
if (!sessionId && !createSessionDraft) {
|
|
9064
|
-
const error = new
|
|
8534
|
+
const error = new FloPayError7(
|
|
9065
8535
|
"Provide a sessionId or the props required to create an automatic payment session.",
|
|
9066
8536
|
"validation_error"
|
|
9067
8537
|
);
|
|
@@ -9076,7 +8546,7 @@ function FloPayAutomaticPaymentButton({
|
|
|
9076
8546
|
setOverlayError(null);
|
|
9077
8547
|
setOverlayStatus("processing");
|
|
9078
8548
|
try {
|
|
9079
|
-
const api = new
|
|
8549
|
+
const api = new PaymentAPI6(resolvedBillingUrl);
|
|
9080
8550
|
if (sessionId) {
|
|
9081
8551
|
const result = await api.getUnifiedCheckoutSession(sessionId, nonce);
|
|
9082
8552
|
await processResolvedSession(result, sessionId);
|
|
@@ -9090,7 +8560,7 @@ function FloPayAutomaticPaymentButton({
|
|
|
9090
8560
|
fromCreateSession: true
|
|
9091
8561
|
});
|
|
9092
8562
|
} catch (err) {
|
|
9093
|
-
if (err instanceof
|
|
8563
|
+
if (err instanceof FloPayError7 && err.code === "session_auto_completed") {
|
|
9094
8564
|
await showSuccess({
|
|
9095
8565
|
result: { status: "succeeded" },
|
|
9096
8566
|
session: null,
|
|
@@ -9124,7 +8594,7 @@ function FloPayAutomaticPaymentButton({
|
|
|
9124
8594
|
showError,
|
|
9125
8595
|
showSuccess
|
|
9126
8596
|
]);
|
|
9127
|
-
const handleFallbackComplete =
|
|
8597
|
+
const handleFallbackComplete = useCallback6((result) => {
|
|
9128
8598
|
const activeFallback = fallbackSessionRef.current;
|
|
9129
8599
|
setFallbackSession(null);
|
|
9130
8600
|
onSuccessRef.current?.({
|
|
@@ -9134,10 +8604,10 @@ function FloPayAutomaticPaymentButton({
|
|
|
9134
8604
|
autoCompleted: false
|
|
9135
8605
|
});
|
|
9136
8606
|
}, []);
|
|
9137
|
-
const handleFallbackError =
|
|
8607
|
+
const handleFallbackError = useCallback6((error) => {
|
|
9138
8608
|
onErrorRef.current?.(error);
|
|
9139
8609
|
}, []);
|
|
9140
|
-
const handleFallbackDecline =
|
|
8610
|
+
const handleFallbackDecline = useCallback6((decline) => {
|
|
9141
8611
|
onDeclineRef.current?.(decline);
|
|
9142
8612
|
}, []);
|
|
9143
8613
|
const themeBundle = useMemo5(() => resolveTheme3(theme), [theme]);
|
|
@@ -9168,7 +8638,7 @@ function FloPayAutomaticPaymentButton({
|
|
|
9168
8638
|
const resolvedPrimaryHoverColor = resolvedAppearanceVars?.colorPrimaryHover ?? darkenHex(resolvedPrimaryColor, 0.12);
|
|
9169
8639
|
const resolvedButtonBorderRadius = resolvedAppearanceVars?.borderRadius ?? "8px";
|
|
9170
8640
|
const cardButtonSizing = children === void 0 ? { boxSizing: "border-box", height: DEFAULT_BUTTONS_LAYOUT_BUTTON_HEIGHT3, padding: "0 1rem" } : { padding: "0.9rem 1rem" };
|
|
9171
|
-
return /* @__PURE__ */ jsxs8(
|
|
8641
|
+
return /* @__PURE__ */ jsxs8(Fragment5, { children: [
|
|
9172
8642
|
/* @__PURE__ */ jsx11(
|
|
9173
8643
|
"button",
|
|
9174
8644
|
{
|
|
@@ -9307,17 +8777,12 @@ function FloPayAutomaticPaymentButton({
|
|
|
9307
8777
|
}
|
|
9308
8778
|
export {
|
|
9309
8779
|
AddressElement,
|
|
9310
|
-
CardCvcElement,
|
|
9311
|
-
CardElement,
|
|
9312
|
-
CardExpiryElement,
|
|
9313
|
-
CardNumberElement,
|
|
9314
|
-
CheckoutForm,
|
|
9315
8780
|
DirectPayPalButton,
|
|
9316
8781
|
FloPayAutomaticPaymentButton,
|
|
9317
8782
|
FloPayCheckout,
|
|
9318
8783
|
FloPayProvider,
|
|
9319
8784
|
PayPalButton,
|
|
9320
|
-
PaymentElement,
|
|
8785
|
+
PaymentElement2 as PaymentElement,
|
|
9321
8786
|
SplitCardForm,
|
|
9322
8787
|
VaultCardFields,
|
|
9323
8788
|
useCheckout,
|