@blocklet/payment-react 1.26.2 → 1.26.4
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/es/checkout-v2/components/dialogs/checkout-dialogs.js +2 -0
- package/es/checkout-v2/components/left/promotion-input.d.ts +2 -1
- package/es/checkout-v2/components/left/promotion-input.js +4 -11
- package/es/checkout-v2/components/right/customer-info-card.d.ts +2 -0
- package/es/checkout-v2/components/right/customer-info-card.js +22 -14
- package/es/checkout-v2/components/right/status-feedback.js +1 -1
- package/es/checkout-v2/layouts/checkout-layout.js +13 -3
- package/es/checkout-v2/panels/right/payment-panel.js +3 -1
- package/es/checkout-v2/views/error-view.d.ts +1 -1
- package/es/checkout-v2/views/error-view.js +7 -0
- package/es/components/service-suspended-dialog.d.ts +4 -0
- package/es/components/service-suspended-dialog.js +63 -0
- package/es/locales/en.js +4 -0
- package/es/locales/zh.js +4 -0
- package/es/payment/form/index.js +8 -0
- package/es/payment/index.js +15 -4
- package/lib/checkout-v2/components/dialogs/checkout-dialogs.js +4 -0
- package/lib/checkout-v2/components/left/promotion-input.d.ts +2 -1
- package/lib/checkout-v2/components/left/promotion-input.js +5 -17
- package/lib/checkout-v2/components/right/customer-info-card.d.ts +2 -0
- package/lib/checkout-v2/components/right/customer-info-card.js +19 -13
- package/lib/checkout-v2/components/right/status-feedback.js +1 -1
- package/lib/checkout-v2/layouts/checkout-layout.js +28 -5
- package/lib/checkout-v2/panels/right/payment-panel.js +3 -1
- package/lib/checkout-v2/views/error-view.d.ts +1 -1
- package/lib/checkout-v2/views/error-view.js +7 -0
- package/lib/components/service-suspended-dialog.d.ts +4 -0
- package/lib/components/service-suspended-dialog.js +99 -0
- package/lib/locales/en.js +4 -0
- package/lib/locales/zh.js +4 -0
- package/lib/payment/form/index.js +18 -0
- package/lib/payment/index.js +15 -4
- package/package.json +4 -4
- package/src/checkout-v2/components/dialogs/checkout-dialogs.tsx +4 -0
- package/src/checkout-v2/components/left/promotion-input.tsx +6 -14
- package/src/checkout-v2/components/right/customer-info-card.tsx +29 -16
- package/src/checkout-v2/components/right/status-feedback.tsx +2 -2
- package/src/checkout-v2/layouts/checkout-layout.tsx +25 -10
- package/src/checkout-v2/panels/right/payment-panel.tsx +2 -0
- package/src/checkout-v2/views/error-view.tsx +9 -1
- package/src/components/service-suspended-dialog.tsx +66 -0
- package/src/locales/en.tsx +4 -0
- package/src/locales/zh.tsx +4 -0
- package/src/payment/form/index.tsx +12 -0
- package/src/payment/index.tsx +26 -4
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import StripeForm from "../../../payment/form/stripe/index.js";
|
|
11
11
|
import ConfirmDialog from "../../../components/confirm.js";
|
|
12
12
|
import PriceChangeConfirm from "../../../components/price-change-confirm.js";
|
|
13
|
+
import ServiceSuspendedDialog from "../../../components/service-suspended-dialog.js";
|
|
13
14
|
import { formatTokenAmount } from "../../utils/format.js";
|
|
14
15
|
function getRedirectUrl(session) {
|
|
15
16
|
try {
|
|
@@ -92,6 +93,7 @@ export default function CheckoutDialogs() {
|
|
|
92
93
|
color: "primary"
|
|
93
94
|
}
|
|
94
95
|
),
|
|
96
|
+
submit.context?.type === "service_suspended" && /* @__PURE__ */ jsx(ServiceSuspendedDialog, { open: true, onClose: submit.cancel }),
|
|
95
97
|
submit.status === "credit_insufficient" && submit.context?.type === "credit_insufficient" && /* @__PURE__ */ jsx(
|
|
96
98
|
ConfirmDialog,
|
|
97
99
|
{
|
|
@@ -12,10 +12,11 @@ interface PromotionInputProps {
|
|
|
12
12
|
};
|
|
13
13
|
discounts: any[];
|
|
14
14
|
discountAmount: string | null;
|
|
15
|
+
currency?: any;
|
|
15
16
|
/** Start with input field visible (skip the "Add promotion code" button) */
|
|
16
17
|
initialShowInput?: boolean;
|
|
17
18
|
/** Show skeleton for the discount amount while switching */
|
|
18
19
|
isAmountLoading?: boolean;
|
|
19
20
|
}
|
|
20
|
-
export default function PromotionInput({ promotion, discounts, discountAmount, initialShowInput, isAmountLoading, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
21
|
+
export default function PromotionInput({ promotion, discounts, discountAmount, currency, initialShowInput, isAmountLoading, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
21
22
|
export {};
|
|
@@ -16,14 +16,16 @@ import {
|
|
|
16
16
|
Typography
|
|
17
17
|
} from "@mui/material";
|
|
18
18
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
19
|
+
import { formatCouponTerms } from "../../../libs/util.js";
|
|
19
20
|
export default function PromotionInput({
|
|
20
21
|
promotion,
|
|
21
22
|
discounts,
|
|
22
23
|
discountAmount,
|
|
24
|
+
currency = null,
|
|
23
25
|
initialShowInput = false,
|
|
24
26
|
isAmountLoading = false
|
|
25
27
|
}) {
|
|
26
|
-
const { t } = useLocaleContext();
|
|
28
|
+
const { t, locale } = useLocaleContext();
|
|
27
29
|
const [showInput, setShowInput] = useState(false);
|
|
28
30
|
const [code, setCode] = useState("");
|
|
29
31
|
const [applying, setApplying] = useState(false);
|
|
@@ -51,16 +53,7 @@ export default function PromotionInput({
|
|
|
51
53
|
return /* @__PURE__ */ jsx(Box, { children: discounts.map((disc, i) => {
|
|
52
54
|
const discCode = disc.promotion_code_details?.code || disc.verification_data?.code || disc.promotion_code || "";
|
|
53
55
|
const coupon = disc.coupon_details || {};
|
|
54
|
-
const
|
|
55
|
-
let description = "";
|
|
56
|
-
if (coupon.duration === "repeating" && coupon.duration_in_months) {
|
|
57
|
-
const months = coupon.duration_in_months;
|
|
58
|
-
description = `${couponOff} for ${months} month${months > 1 ? "s" : ""}`;
|
|
59
|
-
} else if (coupon.duration === "forever") {
|
|
60
|
-
description = t("payment.checkout.coupon.terms.forever", { couponOff });
|
|
61
|
-
} else if (coupon.duration === "once") {
|
|
62
|
-
description = t("payment.checkout.coupon.terms.once", { couponOff });
|
|
63
|
-
}
|
|
56
|
+
const description = coupon && currency ? formatCouponTerms(coupon, currency, locale) : "";
|
|
64
57
|
return /* @__PURE__ */ jsxs(
|
|
65
58
|
Stack,
|
|
66
59
|
{
|
|
@@ -9,7 +9,9 @@ interface CustomerInfoCardProps {
|
|
|
9
9
|
values: Record<string, any>;
|
|
10
10
|
onChange: (field: string, value: string | boolean | Record<string, string>) => void;
|
|
11
11
|
errors: Partial<Record<string, string>>;
|
|
12
|
+
checkValid: () => Promise<boolean>;
|
|
12
13
|
validateField: (field: string) => Promise<void>;
|
|
14
|
+
prefetched: boolean;
|
|
13
15
|
};
|
|
14
16
|
isLoggedIn: boolean;
|
|
15
17
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState,
|
|
2
|
+
import { useState, useRef, useCallback, useEffect } from "react";
|
|
3
3
|
import { Box, Button, InputBase, InputAdornment, Stack, Typography } from "@mui/material";
|
|
4
4
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
5
5
|
import CountrySelect from "../../../components/country-select.js";
|
|
@@ -19,16 +19,24 @@ const fieldLabelMap = (t) => ({
|
|
|
19
19
|
export default function CustomerInfoCard({ form, isLoggedIn }) {
|
|
20
20
|
const { t } = useLocaleContext();
|
|
21
21
|
const labels = fieldLabelMap(t);
|
|
22
|
-
const
|
|
23
|
-
const [
|
|
24
|
-
const
|
|
22
|
+
const [showEditForm, setShowEditForm] = useState(false);
|
|
23
|
+
const [ready, setReady] = useState(false);
|
|
24
|
+
const checkedRef = useRef(false);
|
|
25
25
|
useEffect(() => {
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
if (checkedRef.current) return;
|
|
27
|
+
if (!form.prefetched && !form.values.customer_name && !form.values.customer_email) return;
|
|
28
|
+
checkedRef.current = true;
|
|
29
|
+
form.checkValid().then((valid) => {
|
|
30
|
+
setShowEditForm(!valid);
|
|
31
|
+
setReady(true);
|
|
32
|
+
});
|
|
33
|
+
}, [form.prefetched]);
|
|
34
|
+
const handleChange = useCallback(
|
|
35
|
+
(field, value) => form.onChange(field, value),
|
|
36
|
+
[form.onChange]
|
|
37
|
+
// eslint-disable-line react-hooks/exhaustive-deps
|
|
38
|
+
);
|
|
39
|
+
if (!isLoggedIn || !ready) return null;
|
|
32
40
|
if (!showEditForm) {
|
|
33
41
|
return /* @__PURE__ */ jsxs(Box, { sx: { mt: 2 }, children: [
|
|
34
42
|
/* @__PURE__ */ jsxs(Stack, { direction: "row", justifyContent: "space-between", alignItems: "center", sx: { mb: 1 }, children: [
|
|
@@ -108,8 +116,8 @@ export default function CustomerInfoCard({ form, isLoggedIn }) {
|
|
|
108
116
|
{
|
|
109
117
|
value: value || "",
|
|
110
118
|
country: form.values.billing_address?.country || "",
|
|
111
|
-
onChange: (phone) =>
|
|
112
|
-
onCountryChange: (c) =>
|
|
119
|
+
onChange: (phone) => handleChange("customer_phone", phone),
|
|
120
|
+
onCountryChange: (c) => handleChange("billing_address.country", c),
|
|
113
121
|
onBlur: () => form.validateField(name),
|
|
114
122
|
label,
|
|
115
123
|
error: form.errors[name]
|
|
@@ -124,13 +132,13 @@ export default function CustomerInfoCard({ form, isLoggedIn }) {
|
|
|
124
132
|
{
|
|
125
133
|
fullWidth: true,
|
|
126
134
|
value: value || "",
|
|
127
|
-
onChange: (e) =>
|
|
135
|
+
onChange: (e) => handleChange(name, e.target.value),
|
|
128
136
|
onBlur: () => form.validateField(name),
|
|
129
137
|
startAdornment: isPostalCode ? /* @__PURE__ */ jsx(InputAdornment, { position: "start", sx: { mr: 0.5, ml: -0.5 }, children: /* @__PURE__ */ jsx(
|
|
130
138
|
CountrySelect,
|
|
131
139
|
{
|
|
132
140
|
value: form.values.billing_address?.country || "",
|
|
133
|
-
onChange: (v) =>
|
|
141
|
+
onChange: (v) => handleChange("billing_address.country", v),
|
|
134
142
|
sx: {
|
|
135
143
|
".MuiOutlinedInput-notchedOutline": { borderColor: "transparent !important" },
|
|
136
144
|
"& .MuiSelect-select": { py: 0, pr: "20px !important" }
|
|
@@ -8,7 +8,7 @@ export default function StatusFeedback({ status, context, onReset }) {
|
|
|
8
8
|
if (status === prevStatusRef.current) return;
|
|
9
9
|
prevStatusRef.current = status;
|
|
10
10
|
if (status === "failed" && context?.type === "error") {
|
|
11
|
-
if (context.code === "CUSTOMER_LIMITED") return;
|
|
11
|
+
if (context.code === "CUSTOMER_LIMITED" || context.code === "STOP_ACCEPTING_ORDERS") return;
|
|
12
12
|
Toast.error(context.message || "Payment failed");
|
|
13
13
|
onReset();
|
|
14
14
|
}
|
|
@@ -22,7 +22,14 @@ const fadeIn = {
|
|
|
22
22
|
},
|
|
23
23
|
animation: { xs: "none", md: "fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.15s both" }
|
|
24
24
|
};
|
|
25
|
-
const
|
|
25
|
+
const isSafari = typeof navigator !== "undefined" && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
|
|
26
|
+
const slideInFromRight = isSafari ? {
|
|
27
|
+
"@keyframes panelFadeIn": {
|
|
28
|
+
from: { opacity: 0, transform: "translateX(24px)" },
|
|
29
|
+
to: { opacity: 1, transform: "none" }
|
|
30
|
+
},
|
|
31
|
+
animation: { xs: "none", md: "panelFadeIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) both" }
|
|
32
|
+
} : {
|
|
26
33
|
"@keyframes slideInRight": {
|
|
27
34
|
from: { transform: "translateX(100%)" },
|
|
28
35
|
to: { transform: "translateX(0)" }
|
|
@@ -79,6 +86,7 @@ export default function CheckoutLayout({ left, right, mode = "inline" }) {
|
|
|
79
86
|
{
|
|
80
87
|
sx: {
|
|
81
88
|
flex: 1,
|
|
89
|
+
minWidth: 0,
|
|
82
90
|
bgcolor: (t) => t.palette.mode === "dark" ? "background.default" : "#f8faff",
|
|
83
91
|
p: { xs: 3, md: 5 },
|
|
84
92
|
pt: { xs: 3, md: 4 },
|
|
@@ -156,7 +164,8 @@ export default function CheckoutLayout({ left, right, mode = "inline" }) {
|
|
|
156
164
|
Box,
|
|
157
165
|
{
|
|
158
166
|
sx: {
|
|
159
|
-
|
|
167
|
+
flex: { xs: "none", md: "0 0 50%" },
|
|
168
|
+
width: { xs: "100%" },
|
|
160
169
|
height: { xs: "auto", md: "100vh" },
|
|
161
170
|
display: "flex",
|
|
162
171
|
justifyContent: { md: "center" },
|
|
@@ -185,7 +194,8 @@ export default function CheckoutLayout({ left, right, mode = "inline" }) {
|
|
|
185
194
|
Box,
|
|
186
195
|
{
|
|
187
196
|
sx: {
|
|
188
|
-
|
|
197
|
+
flex: hideLeft ? "none" : { xs: "none", md: "0 0 50%" },
|
|
198
|
+
width: hideLeft ? "100%" : { xs: "100%" },
|
|
189
199
|
height: hideLeft ? "100vh" : { xs: "auto", md: "100vh" },
|
|
190
200
|
bgcolor: "background.paper",
|
|
191
201
|
boxShadow: hideLeft ? "none" : { md: "-4px 0 16px rgba(0,0,0,0.04)" },
|
|
@@ -413,6 +413,7 @@ export default function PaymentPanel() {
|
|
|
413
413
|
},
|
|
414
414
|
discounts,
|
|
415
415
|
discountAmount: pricing.discount,
|
|
416
|
+
currency,
|
|
416
417
|
isAmountLoading
|
|
417
418
|
}
|
|
418
419
|
)
|
|
@@ -604,7 +605,8 @@ export default function PaymentPanel() {
|
|
|
604
605
|
remove: promotion.remove
|
|
605
606
|
},
|
|
606
607
|
discounts,
|
|
607
|
-
discountAmount: pricing.discount
|
|
608
|
+
discountAmount: pricing.discount,
|
|
609
|
+
currency
|
|
608
610
|
}
|
|
609
611
|
)
|
|
610
612
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
interface ErrorViewProps {
|
|
2
2
|
error: string;
|
|
3
|
-
errorCode?: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | null;
|
|
3
|
+
errorCode?: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | 'STOP_ACCEPTING_ORDERS' | null;
|
|
4
4
|
mode?: string;
|
|
5
5
|
}
|
|
6
6
|
export default function ErrorView({ error, errorCode, mode }: ErrorViewProps): import("react").JSX.Element;
|
|
@@ -114,6 +114,13 @@ function getErrorConfig(errorCode, error, t) {
|
|
|
114
114
|
color: "#94a3b8"
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
|
+
if (errorCode === "STOP_ACCEPTING_ORDERS") {
|
|
118
|
+
return {
|
|
119
|
+
title: t("payment.checkout.stopAcceptingOrders.title"),
|
|
120
|
+
description: t("payment.checkout.stopAcceptingOrders.description"),
|
|
121
|
+
color: "#f59e0b"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
117
124
|
return {
|
|
118
125
|
title: t("payment.checkout.error.title"),
|
|
119
126
|
description: error,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Button, Dialog, DialogContent, Stack, Typography } from "@mui/material";
|
|
3
|
+
import { alpha } from "@mui/material/styles";
|
|
4
|
+
import PauseCircleOutlineIcon from "@mui/icons-material/PauseCircleOutline";
|
|
5
|
+
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
6
|
+
export default function ServiceSuspendedDialog({ open, onClose }) {
|
|
7
|
+
const { t } = useLocaleContext();
|
|
8
|
+
return /* @__PURE__ */ jsx(
|
|
9
|
+
Dialog,
|
|
10
|
+
{
|
|
11
|
+
open,
|
|
12
|
+
onClose,
|
|
13
|
+
slotProps: {
|
|
14
|
+
paper: {
|
|
15
|
+
sx: {
|
|
16
|
+
borderRadius: 3,
|
|
17
|
+
maxWidth: 400,
|
|
18
|
+
mx: "auto",
|
|
19
|
+
overflow: "hidden"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
children: /* @__PURE__ */ jsxs(DialogContent, { sx: { p: 0 }, children: [
|
|
24
|
+
/* @__PURE__ */ jsxs(Stack, { alignItems: "center", sx: { pt: 4, pb: 3, px: 4, textAlign: "center" }, children: [
|
|
25
|
+
/* @__PURE__ */ jsx(
|
|
26
|
+
Box,
|
|
27
|
+
{
|
|
28
|
+
sx: {
|
|
29
|
+
width: 64,
|
|
30
|
+
height: 64,
|
|
31
|
+
borderRadius: "50%",
|
|
32
|
+
display: "flex",
|
|
33
|
+
alignItems: "center",
|
|
34
|
+
justifyContent: "center",
|
|
35
|
+
bgcolor: (theme) => alpha(theme.palette.warning.main, 0.1),
|
|
36
|
+
mb: 2.5
|
|
37
|
+
},
|
|
38
|
+
children: /* @__PURE__ */ jsx(PauseCircleOutlineIcon, { sx: { fontSize: 36, color: "warning.main" } })
|
|
39
|
+
}
|
|
40
|
+
),
|
|
41
|
+
/* @__PURE__ */ jsx(Typography, { sx: { fontWeight: 700, fontSize: 18, mb: 1, color: "text.primary" }, children: t("payment.checkout.stopAcceptingOrders.title") }),
|
|
42
|
+
/* @__PURE__ */ jsx(Typography, { sx: { color: "text.secondary", fontSize: 14, lineHeight: 1.6 }, children: t("payment.checkout.stopAcceptingOrders.description") })
|
|
43
|
+
] }),
|
|
44
|
+
/* @__PURE__ */ jsx(Box, { sx: { px: 4, pb: 3 }, children: /* @__PURE__ */ jsx(
|
|
45
|
+
Button,
|
|
46
|
+
{
|
|
47
|
+
fullWidth: true,
|
|
48
|
+
variant: "contained",
|
|
49
|
+
disableElevation: true,
|
|
50
|
+
onClick: onClose,
|
|
51
|
+
sx: {
|
|
52
|
+
borderRadius: 2,
|
|
53
|
+
textTransform: "none",
|
|
54
|
+
fontWeight: 600,
|
|
55
|
+
py: 1
|
|
56
|
+
},
|
|
57
|
+
children: t("common.know")
|
|
58
|
+
}
|
|
59
|
+
) })
|
|
60
|
+
] })
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
}
|
package/es/locales/en.js
CHANGED
|
@@ -380,6 +380,10 @@ export default flat({
|
|
|
380
380
|
title: "Nothing to show here",
|
|
381
381
|
description: "It seems this checkout session is not configured properly"
|
|
382
382
|
},
|
|
383
|
+
stopAcceptingOrders: {
|
|
384
|
+
title: "Service Suspended",
|
|
385
|
+
description: "New order placement is temporarily unavailable due to a system-level service suspension."
|
|
386
|
+
},
|
|
383
387
|
error: {
|
|
384
388
|
title: "Something went wrong"
|
|
385
389
|
},
|
package/es/locales/zh.js
CHANGED
|
@@ -409,6 +409,10 @@ export default flat({
|
|
|
409
409
|
title: "\u6CA1\u6709\u4EFB\u4F55\u8D2D\u4E70\u9879\u76EE",
|
|
410
410
|
description: "\u53EF\u80FD\u8FD9\u4E2A\u4ED8\u6B3E\u94FE\u63A5\u6CA1\u6709\u6B63\u786E\u914D\u7F6E"
|
|
411
411
|
},
|
|
412
|
+
stopAcceptingOrders: {
|
|
413
|
+
title: "\u6682\u505C\u670D\u52A1",
|
|
414
|
+
description: "\u56E0\u7CFB\u7EDF\u7B56\u7565\u8C03\u6574\uFF0C\u5F53\u524D\u5DF2\u6682\u505C\u65B0\u8BA2\u5355\u670D\u52A1\u3002"
|
|
415
|
+
},
|
|
412
416
|
error: {
|
|
413
417
|
title: "\u51FA\u4E86\u70B9\u95EE\u9898"
|
|
414
418
|
},
|
package/es/payment/form/index.js
CHANGED
|
@@ -48,6 +48,7 @@ import LoadingButton from "../../components/loading-button.js";
|
|
|
48
48
|
import OverdueInvoicePayment from "../../components/over-due-invoice-payment.js";
|
|
49
49
|
import { saveCurrencyPreference } from "../../libs/currency.js";
|
|
50
50
|
import ConfirmDialog from "../../components/confirm.js";
|
|
51
|
+
import ServiceSuspendedDialog from "../../components/service-suspended-dialog.js";
|
|
51
52
|
import PriceChangeConfirm from "../../components/price-change-confirm.js";
|
|
52
53
|
import { getFieldValidation, validatePostalCode } from "../../libs/validator.js";
|
|
53
54
|
const generateIdempotencyKey = (sessionId, currencyId) => {
|
|
@@ -164,6 +165,7 @@ export default function PaymentForm({
|
|
|
164
165
|
stripeContext: void 0,
|
|
165
166
|
customer,
|
|
166
167
|
customerLimited: false,
|
|
168
|
+
serviceSuspended: false,
|
|
167
169
|
stripePaying: false,
|
|
168
170
|
fastCheckoutInfo: null,
|
|
169
171
|
creditInsufficientInfo: null,
|
|
@@ -932,6 +934,10 @@ export default function PaymentForm({
|
|
|
932
934
|
shouldToast = false;
|
|
933
935
|
setState({ customerLimited: true });
|
|
934
936
|
}
|
|
937
|
+
if (errorCode === "STOP_ACCEPTING_ORDERS") {
|
|
938
|
+
shouldToast = false;
|
|
939
|
+
setState({ serviceSuspended: true });
|
|
940
|
+
}
|
|
935
941
|
}
|
|
936
942
|
if (shouldToast) {
|
|
937
943
|
Toast.error(formatError(err));
|
|
@@ -1179,6 +1185,7 @@ export default function PaymentForm({
|
|
|
1179
1185
|
}
|
|
1180
1186
|
}
|
|
1181
1187
|
),
|
|
1188
|
+
state.serviceSuspended && /* @__PURE__ */ jsx(ServiceSuspendedDialog, { open: true, onClose: () => setState({ serviceSuspended: false }) }),
|
|
1182
1189
|
FastCheckoutConfirmDialog,
|
|
1183
1190
|
CreditInsufficientDialog,
|
|
1184
1191
|
PriceUpdatedDialog,
|
|
@@ -1444,6 +1451,7 @@ export default function PaymentForm({
|
|
|
1444
1451
|
}
|
|
1445
1452
|
}
|
|
1446
1453
|
),
|
|
1454
|
+
state.serviceSuspended && /* @__PURE__ */ jsx(ServiceSuspendedDialog, { open: true, onClose: () => setState({ serviceSuspended: false }) }),
|
|
1447
1455
|
FastCheckoutConfirmDialog,
|
|
1448
1456
|
CreditInsufficientDialog,
|
|
1449
1457
|
PriceUpdatedDialog,
|
package/es/payment/index.js
CHANGED
|
@@ -78,6 +78,10 @@ function PaymentInner({
|
|
|
78
78
|
return Array.from(currencyIds);
|
|
79
79
|
}, [paymentMethods]);
|
|
80
80
|
const defaultCurrencyId = useMemo(() => {
|
|
81
|
+
const hasAppliedDiscount = Boolean(state.checkoutSession?.discounts?.length);
|
|
82
|
+
if (hasAppliedDiscount && state.checkoutSession.currency_id && availableCurrencyIds.includes(state.checkoutSession.currency_id)) {
|
|
83
|
+
return state.checkoutSession.currency_id;
|
|
84
|
+
}
|
|
81
85
|
if (query.currencyId && availableCurrencyIds.includes(query.currencyId)) {
|
|
82
86
|
return query.currencyId;
|
|
83
87
|
}
|
|
@@ -95,7 +99,7 @@ function PaymentInner({
|
|
|
95
99
|
return state.checkoutSession.currency_id;
|
|
96
100
|
}
|
|
97
101
|
return availableCurrencyIds?.[0];
|
|
98
|
-
}, [query.currencyId, availableCurrencyIds, session?.user, state.checkoutSession
|
|
102
|
+
}, [query.currencyId, availableCurrencyIds, session?.user, state.checkoutSession, paymentMethods]);
|
|
99
103
|
const defaultMethodId = paymentMethods.find((m) => m.payment_currencies.some((c) => c.id === defaultCurrencyId))?.id;
|
|
100
104
|
const hideSummaryCard = mode.endsWith("-minimal") || !showCheckoutSummary;
|
|
101
105
|
const methods = useForm({
|
|
@@ -122,13 +126,20 @@ function PaymentInner({
|
|
|
122
126
|
}
|
|
123
127
|
});
|
|
124
128
|
useEffect(() => {
|
|
129
|
+
const hasAppliedDiscount = Boolean(state.checkoutSession?.discounts?.length);
|
|
130
|
+
const currentCurrency = methods.getValues("payment_currency");
|
|
131
|
+
const currentMethod = methods.getValues("payment_method");
|
|
125
132
|
if (defaultCurrencyId) {
|
|
126
|
-
|
|
133
|
+
if (!hasAppliedDiscount || !currentCurrency) {
|
|
134
|
+
methods.setValue("payment_currency", defaultCurrencyId);
|
|
135
|
+
}
|
|
127
136
|
}
|
|
128
137
|
if (defaultMethodId) {
|
|
129
|
-
|
|
138
|
+
if (!hasAppliedDiscount || !currentMethod) {
|
|
139
|
+
methods.setValue("payment_method", defaultMethodId);
|
|
140
|
+
}
|
|
130
141
|
}
|
|
131
|
-
}, [defaultCurrencyId, defaultMethodId]);
|
|
142
|
+
}, [defaultCurrencyId, defaultMethodId, state.checkoutSession.discounts]);
|
|
132
143
|
useEffect(() => {
|
|
133
144
|
if (!isMobileSafari()) {
|
|
134
145
|
return () => {
|
|
@@ -11,6 +11,7 @@ var _paymentReactHeadless = require("@blocklet/payment-react-headless");
|
|
|
11
11
|
var _stripe = _interopRequireDefault(require("../../../payment/form/stripe"));
|
|
12
12
|
var _confirm = _interopRequireDefault(require("../../../components/confirm"));
|
|
13
13
|
var _priceChangeConfirm = _interopRequireDefault(require("../../../components/price-change-confirm"));
|
|
14
|
+
var _serviceSuspendedDialog = _interopRequireDefault(require("../../../components/service-suspended-dialog"));
|
|
14
15
|
var _format = require("../../utils/format");
|
|
15
16
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
17
|
function getRedirectUrl(session) {
|
|
@@ -118,6 +119,9 @@ function CheckoutDialogs() {
|
|
|
118
119
|
}),
|
|
119
120
|
loading: false,
|
|
120
121
|
color: "primary"
|
|
122
|
+
}), submit.context?.type === "service_suspended" && /* @__PURE__ */(0, _jsxRuntime.jsx)(_serviceSuspendedDialog.default, {
|
|
123
|
+
open: true,
|
|
124
|
+
onClose: submit.cancel
|
|
121
125
|
}), submit.status === "credit_insufficient" && submit.context?.type === "credit_insufficient" && /* @__PURE__ */(0, _jsxRuntime.jsx)(_confirm.default, {
|
|
122
126
|
onConfirm: submit.cancel,
|
|
123
127
|
onCancel: submit.cancel,
|
|
@@ -12,10 +12,11 @@ interface PromotionInputProps {
|
|
|
12
12
|
};
|
|
13
13
|
discounts: any[];
|
|
14
14
|
discountAmount: string | null;
|
|
15
|
+
currency?: any;
|
|
15
16
|
/** Start with input field visible (skip the "Add promotion code" button) */
|
|
16
17
|
initialShowInput?: boolean;
|
|
17
18
|
/** Show skeleton for the discount amount while switching */
|
|
18
19
|
isAmountLoading?: boolean;
|
|
19
20
|
}
|
|
20
|
-
export default function PromotionInput({ promotion, discounts, discountAmount, initialShowInput, isAmountLoading, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
21
|
+
export default function PromotionInput({ promotion, discounts, discountAmount, currency, initialShowInput, isAmountLoading, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
21
22
|
export {};
|
|
@@ -11,16 +11,19 @@ var _Close = _interopRequireDefault(require("@mui/icons-material/Close"));
|
|
|
11
11
|
var _LocalOffer = _interopRequireDefault(require("@mui/icons-material/LocalOffer"));
|
|
12
12
|
var _material = require("@mui/material");
|
|
13
13
|
var _context = require("@arcblock/ux/lib/Locale/context");
|
|
14
|
+
var _util = require("../../../libs/util");
|
|
14
15
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
16
|
function PromotionInput({
|
|
16
17
|
promotion,
|
|
17
18
|
discounts,
|
|
18
19
|
discountAmount,
|
|
20
|
+
currency = null,
|
|
19
21
|
initialShowInput = false,
|
|
20
22
|
isAmountLoading = false
|
|
21
23
|
}) {
|
|
22
24
|
const {
|
|
23
|
-
t
|
|
25
|
+
t,
|
|
26
|
+
locale
|
|
24
27
|
} = (0, _context.useLocaleContext)();
|
|
25
28
|
const [showInput, setShowInput] = (0, _react.useState)(false);
|
|
26
29
|
const [code, setCode] = (0, _react.useState)("");
|
|
@@ -50,22 +53,7 @@ function PromotionInput({
|
|
|
50
53
|
children: discounts.map((disc, i) => {
|
|
51
54
|
const discCode = disc.promotion_code_details?.code || disc.verification_data?.code || disc.promotion_code || "";
|
|
52
55
|
const coupon = disc.coupon_details || {};
|
|
53
|
-
const
|
|
54
|
-
percent: coupon.percent_off
|
|
55
|
-
}) : `${coupon.percent_off || 0}%`;
|
|
56
|
-
let description = "";
|
|
57
|
-
if (coupon.duration === "repeating" && coupon.duration_in_months) {
|
|
58
|
-
const months = coupon.duration_in_months;
|
|
59
|
-
description = `${couponOff} for ${months} month${months > 1 ? "s" : ""}`;
|
|
60
|
-
} else if (coupon.duration === "forever") {
|
|
61
|
-
description = t("payment.checkout.coupon.terms.forever", {
|
|
62
|
-
couponOff
|
|
63
|
-
});
|
|
64
|
-
} else if (coupon.duration === "once") {
|
|
65
|
-
description = t("payment.checkout.coupon.terms.once", {
|
|
66
|
-
couponOff
|
|
67
|
-
});
|
|
68
|
-
}
|
|
56
|
+
const description = coupon && currency ? (0, _util.formatCouponTerms)(coupon, currency, locale) : "";
|
|
69
57
|
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
|
|
70
58
|
direction: "row",
|
|
71
59
|
justifyContent: "space-between",
|
|
@@ -9,7 +9,9 @@ interface CustomerInfoCardProps {
|
|
|
9
9
|
values: Record<string, any>;
|
|
10
10
|
onChange: (field: string, value: string | boolean | Record<string, string>) => void;
|
|
11
11
|
errors: Partial<Record<string, string>>;
|
|
12
|
+
checkValid: () => Promise<boolean>;
|
|
12
13
|
validateField: (field: string) => Promise<void>;
|
|
14
|
+
prefetched: boolean;
|
|
13
15
|
};
|
|
14
16
|
isLoggedIn: boolean;
|
|
15
17
|
}
|
|
@@ -31,16 +31,22 @@ function CustomerInfoCard({
|
|
|
31
31
|
t
|
|
32
32
|
} = (0, _context.useLocaleContext)();
|
|
33
33
|
const labels = fieldLabelMap(t);
|
|
34
|
-
const
|
|
35
|
-
const [
|
|
36
|
-
const
|
|
34
|
+
const [showEditForm, setShowEditForm] = (0, _react.useState)(false);
|
|
35
|
+
const [ready, setReady] = (0, _react.useState)(false);
|
|
36
|
+
const checkedRef = (0, _react.useRef)(false);
|
|
37
37
|
(0, _react.useEffect)(() => {
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
if (checkedRef.current) return;
|
|
39
|
+
if (!form.prefetched && !form.values.customer_name && !form.values.customer_email) return;
|
|
40
|
+
checkedRef.current = true;
|
|
41
|
+
form.checkValid().then(valid => {
|
|
42
|
+
setShowEditForm(!valid);
|
|
43
|
+
setReady(true);
|
|
44
|
+
});
|
|
45
|
+
}, [form.prefetched]);
|
|
46
|
+
const handleChange = (0, _react.useCallback)((field, value) => form.onChange(field, value), [form.onChange]
|
|
47
|
+
// eslint-disable-line react-hooks/exhaustive-deps
|
|
48
|
+
);
|
|
49
|
+
if (!isLoggedIn || !ready) return null;
|
|
44
50
|
if (!showEditForm) {
|
|
45
51
|
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
|
|
46
52
|
sx: {
|
|
@@ -177,8 +183,8 @@ function CustomerInfoCard({
|
|
|
177
183
|
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_phoneField.default, {
|
|
178
184
|
value: value || "",
|
|
179
185
|
country: form.values.billing_address?.country || "",
|
|
180
|
-
onChange: phone =>
|
|
181
|
-
onCountryChange: c =>
|
|
186
|
+
onChange: phone => handleChange("customer_phone", phone),
|
|
187
|
+
onCountryChange: c => handleChange("billing_address.country", c),
|
|
182
188
|
onBlur: () => form.validateField(name),
|
|
183
189
|
label,
|
|
184
190
|
error: form.errors[name]
|
|
@@ -199,7 +205,7 @@ function CustomerInfoCard({
|
|
|
199
205
|
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.InputBase, {
|
|
200
206
|
fullWidth: true,
|
|
201
207
|
value: value || "",
|
|
202
|
-
onChange: e =>
|
|
208
|
+
onChange: e => handleChange(name, e.target.value),
|
|
203
209
|
onBlur: () => form.validateField(name),
|
|
204
210
|
startAdornment: isPostalCode ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.InputAdornment, {
|
|
205
211
|
position: "start",
|
|
@@ -209,7 +215,7 @@ function CustomerInfoCard({
|
|
|
209
215
|
},
|
|
210
216
|
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_countrySelect.default, {
|
|
211
217
|
value: form.values.billing_address?.country || "",
|
|
212
|
-
onChange: v =>
|
|
218
|
+
onChange: v => handleChange("billing_address.country", v),
|
|
213
219
|
sx: {
|
|
214
220
|
".MuiOutlinedInput-notchedOutline": {
|
|
215
221
|
borderColor: "transparent !important"
|
|
@@ -21,7 +21,7 @@ function StatusFeedback({
|
|
|
21
21
|
if (status === prevStatusRef.current) return;
|
|
22
22
|
prevStatusRef.current = status;
|
|
23
23
|
if (status === "failed" && context?.type === "error") {
|
|
24
|
-
if (context.code === "CUSTOMER_LIMITED") return;
|
|
24
|
+
if (context.code === "CUSTOMER_LIMITED" || context.code === "STOP_ACCEPTING_ORDERS") return;
|
|
25
25
|
_Toast.default.error(context.message || "Payment failed");
|
|
26
26
|
onReset();
|
|
27
27
|
}
|
|
@@ -47,7 +47,23 @@ const fadeIn = {
|
|
|
47
47
|
md: "fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.15s both"
|
|
48
48
|
}
|
|
49
49
|
};
|
|
50
|
-
const
|
|
50
|
+
const isSafari = typeof navigator !== "undefined" && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
|
|
51
|
+
const slideInFromRight = isSafari ? {
|
|
52
|
+
"@keyframes panelFadeIn": {
|
|
53
|
+
from: {
|
|
54
|
+
opacity: 0,
|
|
55
|
+
transform: "translateX(24px)"
|
|
56
|
+
},
|
|
57
|
+
to: {
|
|
58
|
+
opacity: 1,
|
|
59
|
+
transform: "none"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
animation: {
|
|
63
|
+
xs: "none",
|
|
64
|
+
md: "panelFadeIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) both"
|
|
65
|
+
}
|
|
66
|
+
} : {
|
|
51
67
|
"@keyframes slideInRight": {
|
|
52
68
|
from: {
|
|
53
69
|
transform: "translateX(100%)"
|
|
@@ -117,6 +133,7 @@ function CheckoutLayout({
|
|
|
117
133
|
children: [!hideLeft && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
|
|
118
134
|
sx: {
|
|
119
135
|
flex: 1,
|
|
136
|
+
minWidth: 0,
|
|
120
137
|
bgcolor: t => t.palette.mode === "dark" ? "background.default" : "#f8faff",
|
|
121
138
|
p: {
|
|
122
139
|
xs: 3,
|
|
@@ -205,9 +222,12 @@ function CheckoutLayout({
|
|
|
205
222
|
}
|
|
206
223
|
}), !hideLeft && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
|
|
207
224
|
sx: {
|
|
225
|
+
flex: {
|
|
226
|
+
xs: "none",
|
|
227
|
+
md: "0 0 50%"
|
|
228
|
+
},
|
|
208
229
|
width: {
|
|
209
|
-
xs: "100%"
|
|
210
|
-
md: "50%"
|
|
230
|
+
xs: "100%"
|
|
211
231
|
},
|
|
212
232
|
height: {
|
|
213
233
|
xs: "auto",
|
|
@@ -246,9 +266,12 @@ function CheckoutLayout({
|
|
|
246
266
|
})
|
|
247
267
|
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
|
|
248
268
|
sx: {
|
|
269
|
+
flex: hideLeft ? "none" : {
|
|
270
|
+
xs: "none",
|
|
271
|
+
md: "0 0 50%"
|
|
272
|
+
},
|
|
249
273
|
width: hideLeft ? "100%" : {
|
|
250
|
-
xs: "100%"
|
|
251
|
-
md: "50%"
|
|
274
|
+
xs: "100%"
|
|
252
275
|
},
|
|
253
276
|
height: hideLeft ? "100vh" : {
|
|
254
277
|
xs: "auto",
|