@blocklet/payment-react 1.26.1 → 1.26.3
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/cross-sell-card.js +3 -3
- package/es/checkout-v2/components/left/product-item-card.js +9 -3
- package/es/checkout-v2/components/left/promotion-input.d.ts +4 -1
- package/es/checkout-v2/components/left/promotion-input.js +8 -13
- 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/components/right/submit-button.js +3 -1
- package/es/checkout-v2/layouts/checkout-layout.js +13 -3
- package/es/checkout-v2/panels/left/composite-panel.js +27 -6
- package/es/checkout-v2/panels/right/payment-panel.js +40 -9
- package/es/checkout-v2/utils/format.d.ts +1 -1
- package/es/checkout-v2/utils/format.js +1 -0
- package/es/checkout-v2/views/error-view.d.ts +1 -1
- package/es/checkout-v2/views/error-view.js +9 -0
- package/es/checkout-v2/views/success-view.js +3 -1
- package/es/components/over-due-invoice-payment.js +5 -3
- package/es/components/service-suspended-dialog.d.ts +4 -0
- package/es/components/service-suspended-dialog.js +61 -0
- package/es/libs/util.d.ts +8 -0
- package/es/libs/util.js +3 -0
- package/es/locales/en.js +4 -0
- package/es/locales/zh.js +4 -0
- package/es/payment/form/index.js +17 -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/cross-sell-card.js +2 -2
- package/lib/checkout-v2/components/left/product-item-card.js +9 -2
- package/lib/checkout-v2/components/left/promotion-input.d.ts +4 -1
- package/lib/checkout-v2/components/left/promotion-input.js +12 -19
- 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/components/right/submit-button.js +3 -1
- package/lib/checkout-v2/layouts/checkout-layout.js +28 -5
- package/lib/checkout-v2/panels/left/composite-panel.js +20 -5
- package/lib/checkout-v2/panels/right/payment-panel.js +46 -7
- package/lib/checkout-v2/utils/format.d.ts +1 -1
- package/lib/checkout-v2/utils/format.js +7 -0
- package/lib/checkout-v2/views/error-view.d.ts +1 -1
- package/lib/checkout-v2/views/error-view.js +9 -0
- package/lib/checkout-v2/views/success-view.js +2 -0
- package/lib/components/over-due-invoice-payment.js +12 -2
- package/lib/components/service-suspended-dialog.d.ts +4 -0
- package/lib/components/service-suspended-dialog.js +97 -0
- package/lib/libs/util.d.ts +8 -0
- package/lib/libs/util.js +4 -0
- package/lib/locales/en.js +4 -0
- package/lib/locales/zh.js +4 -0
- package/lib/payment/form/index.js +23 -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/cross-sell-card.tsx +3 -3
- package/src/checkout-v2/components/left/product-item-card.tsx +18 -8
- package/src/checkout-v2/components/left/promotion-input.tsx +17 -17
- 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/components/right/submit-button.tsx +2 -0
- package/src/checkout-v2/layouts/checkout-layout.tsx +25 -10
- package/src/checkout-v2/panels/left/composite-panel.tsx +28 -6
- package/src/checkout-v2/panels/right/payment-panel.tsx +32 -5
- package/src/checkout-v2/utils/format.ts +2 -0
- package/src/checkout-v2/views/error-view.tsx +11 -1
- package/src/checkout-v2/views/success-view.tsx +3 -1
- package/src/components/over-due-invoice-payment.tsx +6 -3
- package/src/components/service-suspended-dialog.tsx +64 -0
- package/src/libs/util.ts +7 -0
- package/src/locales/en.tsx +4 -0
- package/src/locales/zh.tsx +4 -0
- package/src/payment/form/index.tsx +20 -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
|
{
|
|
@@ -3,7 +3,7 @@ import AddShoppingCartIcon from "@mui/icons-material/AddShoppingCart";
|
|
|
3
3
|
import ShoppingCartCheckoutIcon from "@mui/icons-material/ShoppingCartCheckout";
|
|
4
4
|
import { Avatar, Box, Button, Chip, Stack, Typography } from "@mui/material";
|
|
5
5
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
6
|
-
import { formatDynamicUnitPrice, tSafe, INTERVAL_LOCALE_KEY } from "../../utils/format.js";
|
|
6
|
+
import { formatDynamicUnitPrice, tSafe, INTERVAL_LOCALE_KEY, primaryContrastColor } from "../../utils/format.js";
|
|
7
7
|
export default function CrossSellCard({
|
|
8
8
|
crossSellItem,
|
|
9
9
|
currency,
|
|
@@ -40,7 +40,7 @@ export default function CrossSellCard({
|
|
|
40
40
|
fontWeight: 900,
|
|
41
41
|
letterSpacing: "0.12em",
|
|
42
42
|
bgcolor: "primary.main",
|
|
43
|
-
color:
|
|
43
|
+
color: (theme) => primaryContrastColor(theme),
|
|
44
44
|
boxShadow: "0 4px 12px rgba(45,124,243,0.2)",
|
|
45
45
|
"& .MuiChip-label": { px: 1.5 }
|
|
46
46
|
}
|
|
@@ -147,7 +147,7 @@ export default function CrossSellCard({
|
|
|
147
147
|
transition: "all 0.2s",
|
|
148
148
|
"&:hover": {
|
|
149
149
|
bgcolor: "primary.main",
|
|
150
|
-
color:
|
|
150
|
+
color: (theme) => primaryContrastColor(theme),
|
|
151
151
|
borderColor: "primary.main"
|
|
152
152
|
},
|
|
153
153
|
"&:active": { transform: "scale(0.95)" }
|
|
@@ -22,7 +22,13 @@ import {
|
|
|
22
22
|
import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
|
|
23
23
|
import { getPriceUnitAmountByCurrency } from "@blocklet/payment-react-headless";
|
|
24
24
|
import Toast from "@arcblock/ux/lib/Toast";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
INTERVAL_LOCALE_KEY,
|
|
27
|
+
formatDynamicUnitPrice,
|
|
28
|
+
formatTokenAmount,
|
|
29
|
+
formatTrialText,
|
|
30
|
+
primaryContrastColor
|
|
31
|
+
} from "../../utils/format.js";
|
|
26
32
|
export default function ProductItemCard({
|
|
27
33
|
item,
|
|
28
34
|
currency,
|
|
@@ -217,7 +223,7 @@ export default function ProductItemCard({
|
|
|
217
223
|
fontWeight: 900,
|
|
218
224
|
letterSpacing: "0.12em",
|
|
219
225
|
bgcolor: "primary.main",
|
|
220
|
-
color:
|
|
226
|
+
color: (th) => primaryContrastColor(th),
|
|
221
227
|
boxShadow: "0 4px 12px rgba(45,124,243,0.2)",
|
|
222
228
|
"& .MuiChip-label": { px: 1.5 }
|
|
223
229
|
}
|
|
@@ -388,7 +394,7 @@ export default function ProductItemCard({
|
|
|
388
394
|
]
|
|
389
395
|
}
|
|
390
396
|
),
|
|
391
|
-
discountCode && perItemDiscount && /* @__PURE__ */ jsx(Box, { sx: { mt: 1.5 }, children: /* @__PURE__ */ jsx(
|
|
397
|
+
discountCode && perItemDiscount && /* @__PURE__ */ jsx(Box, { sx: { mt: 1.5 }, children: isRateLoading ? /* @__PURE__ */ jsx(Skeleton, { variant: "rounded", width: 160, height: 22, sx: { borderRadius: "6px" } }) : /* @__PURE__ */ jsx(
|
|
392
398
|
Chip,
|
|
393
399
|
{
|
|
394
400
|
icon: /* @__PURE__ */ jsx(LocalOfferIcon, { sx: { color: "warning.main", fontSize: "small" } }),
|
|
@@ -12,8 +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;
|
|
18
|
+
/** Show skeleton for the discount amount while switching */
|
|
19
|
+
isAmountLoading?: boolean;
|
|
17
20
|
}
|
|
18
|
-
export default function PromotionInput({ promotion, discounts, discountAmount, initialShowInput, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
21
|
+
export default function PromotionInput({ promotion, discounts, discountAmount, currency, initialShowInput, isAmountLoading, }: PromotionInputProps): import("react").JSX.Element | null;
|
|
19
22
|
export {};
|
|
@@ -10,18 +10,22 @@ import {
|
|
|
10
10
|
CircularProgress,
|
|
11
11
|
IconButton,
|
|
12
12
|
InputAdornment,
|
|
13
|
+
Skeleton,
|
|
13
14
|
Stack,
|
|
14
15
|
TextField,
|
|
15
16
|
Typography
|
|
16
17
|
} from "@mui/material";
|
|
17
18
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
19
|
+
import { formatCouponTerms } from "../../../libs/util.js";
|
|
18
20
|
export default function PromotionInput({
|
|
19
21
|
promotion,
|
|
20
22
|
discounts,
|
|
21
23
|
discountAmount,
|
|
22
|
-
|
|
24
|
+
currency = null,
|
|
25
|
+
initialShowInput = false,
|
|
26
|
+
isAmountLoading = false
|
|
23
27
|
}) {
|
|
24
|
-
const { t } = useLocaleContext();
|
|
28
|
+
const { t, locale } = useLocaleContext();
|
|
25
29
|
const [showInput, setShowInput] = useState(false);
|
|
26
30
|
const [code, setCode] = useState("");
|
|
27
31
|
const [applying, setApplying] = useState(false);
|
|
@@ -49,16 +53,7 @@ export default function PromotionInput({
|
|
|
49
53
|
return /* @__PURE__ */ jsx(Box, { children: discounts.map((disc, i) => {
|
|
50
54
|
const discCode = disc.promotion_code_details?.code || disc.verification_data?.code || disc.promotion_code || "";
|
|
51
55
|
const coupon = disc.coupon_details || {};
|
|
52
|
-
const
|
|
53
|
-
let description = "";
|
|
54
|
-
if (coupon.duration === "repeating" && coupon.duration_in_months) {
|
|
55
|
-
const months = coupon.duration_in_months;
|
|
56
|
-
description = `${couponOff} for ${months} month${months > 1 ? "s" : ""}`;
|
|
57
|
-
} else if (coupon.duration === "forever") {
|
|
58
|
-
description = t("payment.checkout.coupon.terms.forever", { couponOff });
|
|
59
|
-
} else if (coupon.duration === "once") {
|
|
60
|
-
description = t("payment.checkout.coupon.terms.once", { couponOff });
|
|
61
|
-
}
|
|
56
|
+
const description = coupon && currency ? formatCouponTerms(coupon, currency, locale) : "";
|
|
62
57
|
return /* @__PURE__ */ jsxs(
|
|
63
58
|
Stack,
|
|
64
59
|
{
|
|
@@ -91,7 +86,7 @@ export default function PromotionInput({
|
|
|
91
86
|
]
|
|
92
87
|
}
|
|
93
88
|
),
|
|
94
|
-
/* @__PURE__ */ jsxs(Typography, { sx: { color: "text.primary", fontWeight: 600, fontSize: 14 }, children: [
|
|
89
|
+
isAmountLoading ? /* @__PURE__ */ jsx(Skeleton, { variant: "text", width: 80, height: 22 }) : /* @__PURE__ */ jsxs(Typography, { sx: { color: "text.primary", fontWeight: 600, fontSize: 14 }, children: [
|
|
95
90
|
"-",
|
|
96
91
|
discountAmount || "0"
|
|
97
92
|
] })
|
|
@@ -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
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Button, CircularProgress } from "@mui/material";
|
|
3
|
+
import { primaryContrastColor } from "../../utils/format.js";
|
|
3
4
|
export default function SubmitButton({
|
|
4
5
|
canSubmit,
|
|
5
6
|
isProcessing,
|
|
@@ -21,7 +22,8 @@ export default function SubmitButton({
|
|
|
21
22
|
py: 1.5,
|
|
22
23
|
fontSize: "1.3rem",
|
|
23
24
|
fontWeight: 600,
|
|
24
|
-
textTransform: "none"
|
|
25
|
+
textTransform: "none",
|
|
26
|
+
color: (theme) => primaryContrastColor(theme)
|
|
25
27
|
},
|
|
26
28
|
children: isProcessing ? processingLabel : label
|
|
27
29
|
}
|
|
@@ -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)" },
|
|
@@ -17,7 +17,13 @@ import {
|
|
|
17
17
|
useProduct
|
|
18
18
|
} from "@blocklet/payment-react-headless";
|
|
19
19
|
import { useMobile } from "../../../hooks/mobile.js";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
INTERVAL_LOCALE_KEY,
|
|
22
|
+
formatTrialText,
|
|
23
|
+
getSessionHeaderMeta,
|
|
24
|
+
tSafe,
|
|
25
|
+
primaryContrastColor
|
|
26
|
+
} from "../../utils/format.js";
|
|
21
27
|
import ProductItemCard from "../../components/left/product-item-card.js";
|
|
22
28
|
import BillingToggle from "../../components/left/billing-toggle.js";
|
|
23
29
|
import CrossSellCard from "../../components/left/cross-sell-card.js";
|
|
@@ -51,6 +57,9 @@ export default function CompositePanel() {
|
|
|
51
57
|
const canUpsell = nonCrossSellItems.length <= 1;
|
|
52
58
|
const hasTopUpsell = canUpsell && !!upsellPrimaryItem && ["subscription", "setup"].includes(mode);
|
|
53
59
|
const isUpselled = !!upsellPrimaryItem?.upsell_price;
|
|
60
|
+
const [upsellSwitching, setUpsellSwitching] = useState(false);
|
|
61
|
+
const [pendingUpsell, setPendingUpsell] = useState(null);
|
|
62
|
+
const visualIsUpselled = pendingUpsell !== null ? pendingUpsell : isUpselled;
|
|
54
63
|
const currentInterval = hasTopUpsell ? upsellPrimaryItem.price?.recurring?.interval : null;
|
|
55
64
|
const upsellInterval = hasTopUpsell ? upsellTarget?.recurring?.interval : null;
|
|
56
65
|
let upsellSavings = 0;
|
|
@@ -75,7 +84,7 @@ export default function CompositePanel() {
|
|
|
75
84
|
const isMultiItem = lineItems.items.length > 1;
|
|
76
85
|
const activeSx = {
|
|
77
86
|
bgcolor: "primary.main",
|
|
78
|
-
color:
|
|
87
|
+
color: (theme) => primaryContrastColor(theme),
|
|
79
88
|
boxShadow: "0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)"
|
|
80
89
|
};
|
|
81
90
|
const inactiveSx = {
|
|
@@ -223,17 +232,23 @@ export default function CompositePanel() {
|
|
|
223
232
|
Box,
|
|
224
233
|
{
|
|
225
234
|
onClick: async () => {
|
|
226
|
-
if (isUpselled) {
|
|
235
|
+
if (isUpselled && !upsellSwitching) {
|
|
236
|
+
setPendingUpsell(false);
|
|
237
|
+
setUpsellSwitching(true);
|
|
227
238
|
try {
|
|
228
239
|
await lineItems.downsell(
|
|
229
240
|
upsellPrimaryItem.upsell_price?.id || upsellPrimaryItem.price_id
|
|
230
241
|
);
|
|
231
242
|
} catch (err) {
|
|
243
|
+
setPendingUpsell(null);
|
|
232
244
|
Toast.error(err?.response?.data?.error || err?.message || "Failed");
|
|
245
|
+
} finally {
|
|
246
|
+
setUpsellSwitching(false);
|
|
247
|
+
setPendingUpsell(null);
|
|
233
248
|
}
|
|
234
249
|
}
|
|
235
250
|
},
|
|
236
|
-
sx: capsuleBtnSx(!
|
|
251
|
+
sx: capsuleBtnSx(!visualIsUpselled),
|
|
237
252
|
children: /* @__PURE__ */ jsx(Typography, { component: "span", sx: { fontSize: 14, fontWeight: 700, color: "inherit", lineHeight: 1 }, children: t(INTERVAL_LOCALE_KEY[currentInterval] || "") })
|
|
238
253
|
}
|
|
239
254
|
),
|
|
@@ -241,15 +256,21 @@ export default function CompositePanel() {
|
|
|
241
256
|
Box,
|
|
242
257
|
{
|
|
243
258
|
onClick: async () => {
|
|
244
|
-
if (!isUpselled) {
|
|
259
|
+
if (!isUpselled && !upsellSwitching) {
|
|
260
|
+
setPendingUpsell(true);
|
|
261
|
+
setUpsellSwitching(true);
|
|
245
262
|
try {
|
|
246
263
|
await lineItems.upsell(upsellPrimaryItem.price_id, upsellTarget.id);
|
|
247
264
|
} catch (err) {
|
|
265
|
+
setPendingUpsell(null);
|
|
248
266
|
Toast.error(err?.response?.data?.error || err?.message || "Failed");
|
|
267
|
+
} finally {
|
|
268
|
+
setUpsellSwitching(false);
|
|
269
|
+
setPendingUpsell(null);
|
|
249
270
|
}
|
|
250
271
|
}
|
|
251
272
|
},
|
|
252
|
-
sx: capsuleBtnSx(
|
|
273
|
+
sx: capsuleBtnSx(visualIsUpselled),
|
|
253
274
|
children: /* @__PURE__ */ jsx(Typography, { component: "span", sx: { fontSize: 14, fontWeight: 700, color: "inherit", lineHeight: 1 }, children: t(INTERVAL_LOCALE_KEY[upsellInterval] || "") })
|
|
254
275
|
}
|
|
255
276
|
)
|
|
@@ -40,9 +40,9 @@ import {
|
|
|
40
40
|
import { joinURL } from "ufo";
|
|
41
41
|
import { usePaymentContext } from "../../../contexts/payment.js";
|
|
42
42
|
import { useMobile } from "../../../hooks/mobile.js";
|
|
43
|
-
import { getPrefix } from "../../../libs/util.js";
|
|
43
|
+
import { getPrefix, getStatementDescriptor } from "../../../libs/util.js";
|
|
44
44
|
import OverdueInvoicePayment from "../../../components/over-due-invoice-payment.js";
|
|
45
|
-
import { tSafe, whiteTooltipSx } from "../../utils/format.js";
|
|
45
|
+
import { tSafe, whiteTooltipSx, primaryContrastColor } from "../../utils/format.js";
|
|
46
46
|
import CustomerInfoCard from "../../components/right/customer-info-card.js";
|
|
47
47
|
import SubscriptionDisclaimer from "../../components/right/subscription-disclaimer.js";
|
|
48
48
|
import StatusFeedback from "../../components/right/status-feedback.js";
|
|
@@ -412,7 +412,9 @@ export default function PaymentPanel() {
|
|
|
412
412
|
remove: promotion.remove
|
|
413
413
|
},
|
|
414
414
|
discounts,
|
|
415
|
-
discountAmount: pricing.discount
|
|
415
|
+
discountAmount: pricing.discount,
|
|
416
|
+
currency,
|
|
417
|
+
isAmountLoading
|
|
416
418
|
}
|
|
417
419
|
)
|
|
418
420
|
] }),
|
|
@@ -502,7 +504,7 @@ export default function PaymentPanel() {
|
|
|
502
504
|
] })
|
|
503
505
|
] });
|
|
504
506
|
})(),
|
|
505
|
-
/* @__PURE__ */
|
|
507
|
+
/* @__PURE__ */ jsxs(
|
|
506
508
|
Button,
|
|
507
509
|
{
|
|
508
510
|
variant: "contained",
|
|
@@ -511,15 +513,43 @@ export default function PaymentPanel() {
|
|
|
511
513
|
disabled: !canSubmit || submit.status === "waiting_stripe",
|
|
512
514
|
onClick: handleAction,
|
|
513
515
|
startIcon: isProcessing ? /* @__PURE__ */ jsx(CircularProgress, { size: 20, color: "inherit" }) : null,
|
|
514
|
-
endIcon: !isProcessing ? /* @__PURE__ */ jsx(ArrowForwardIcon, {}) : void 0,
|
|
515
516
|
sx: {
|
|
516
517
|
py: 1.5,
|
|
517
518
|
fontSize: "1.1rem",
|
|
518
519
|
fontWeight: 600,
|
|
519
520
|
textTransform: "none",
|
|
520
|
-
borderRadius: "12px"
|
|
521
|
+
borderRadius: "12px",
|
|
522
|
+
color: (theme) => primaryContrastColor(theme),
|
|
523
|
+
position: "relative",
|
|
524
|
+
overflow: "hidden",
|
|
525
|
+
"&:hover": { bgcolor: "primary.main" },
|
|
526
|
+
"&:hover .arrow-icon": { transform: "translateX(4px)" },
|
|
527
|
+
"&:hover .shine-layer": { transform: "translateX(100%)" }
|
|
521
528
|
},
|
|
522
|
-
children:
|
|
529
|
+
children: [
|
|
530
|
+
/* @__PURE__ */ jsx(Box, { component: "span", sx: { position: "relative", zIndex: 1 }, children: isProcessing ? `${t("payment.checkout.processing")}...` : buttonLabel }),
|
|
531
|
+
!isProcessing && /* @__PURE__ */ jsx(
|
|
532
|
+
ArrowForwardIcon,
|
|
533
|
+
{
|
|
534
|
+
className: "arrow-icon",
|
|
535
|
+
sx: { ml: 1, position: "relative", zIndex: 1, transition: "transform 0.2s ease" }
|
|
536
|
+
}
|
|
537
|
+
),
|
|
538
|
+
/* @__PURE__ */ jsx(
|
|
539
|
+
Box,
|
|
540
|
+
{
|
|
541
|
+
className: "shine-layer",
|
|
542
|
+
sx: {
|
|
543
|
+
position: "absolute",
|
|
544
|
+
inset: 0,
|
|
545
|
+
background: "linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent)",
|
|
546
|
+
transform: "translateX(-100%)",
|
|
547
|
+
transition: "transform 0.7s ease",
|
|
548
|
+
pointerEvents: "none"
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
)
|
|
552
|
+
]
|
|
523
553
|
}
|
|
524
554
|
),
|
|
525
555
|
isMobile && /* @__PURE__ */ jsxs(
|
|
@@ -575,7 +605,8 @@ export default function PaymentPanel() {
|
|
|
575
605
|
remove: promotion.remove
|
|
576
606
|
},
|
|
577
607
|
discounts,
|
|
578
|
-
discountAmount: pricing.discount
|
|
608
|
+
discountAmount: pricing.discount,
|
|
609
|
+
currency
|
|
579
610
|
}
|
|
580
611
|
)
|
|
581
612
|
]
|
|
@@ -588,7 +619,7 @@ export default function PaymentPanel() {
|
|
|
588
619
|
mode,
|
|
589
620
|
subscription,
|
|
590
621
|
staking: pricing.staking,
|
|
591
|
-
appName: session?.
|
|
622
|
+
appName: getStatementDescriptor(session?.line_items || [])
|
|
592
623
|
}
|
|
593
624
|
),
|
|
594
625
|
!isMobile && /* @__PURE__ */ jsxs(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TPaymentCurrency } from '@blocklet/payment-types';
|
|
2
|
+
export { primaryContrastColor } from '../../libs/util';
|
|
2
3
|
export declare const INTERVAL_LOCALE_KEY: Record<string, string>;
|
|
3
4
|
export declare function countryCodeToFlag(code: string): string;
|
|
4
5
|
export declare function formatTokenAmount(unitAmount: string | number | bigint, currency: TPaymentCurrency | null): string;
|
|
@@ -56,4 +57,3 @@ interface ItemMeta {
|
|
|
56
57
|
* Works for the "primary product" header above the item list.
|
|
57
58
|
*/
|
|
58
59
|
export declare function getSessionHeaderMeta(t: TFn, session: any, product: any, items: any[]): ItemMeta;
|
|
59
|
-
export {};
|
|
@@ -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;
|
|
@@ -4,6 +4,7 @@ import { alpha, useTheme } from "@mui/material/styles";
|
|
|
4
4
|
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
|
5
5
|
import Header from "@blocklet/ui-react/lib/Header";
|
|
6
6
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
7
|
+
import { primaryContrastColor } from "../utils/format.js";
|
|
7
8
|
function GeometricDecoration() {
|
|
8
9
|
const theme = useTheme();
|
|
9
10
|
const gridColor = alpha(theme.palette.primary.main, 0.06);
|
|
@@ -113,6 +114,13 @@ function getErrorConfig(errorCode, error, t) {
|
|
|
113
114
|
color: "#94a3b8"
|
|
114
115
|
};
|
|
115
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
|
+
}
|
|
116
124
|
return {
|
|
117
125
|
title: t("payment.checkout.error.title"),
|
|
118
126
|
description: error,
|
|
@@ -173,6 +181,7 @@ function ErrorContent({ error, errorCode = void 0 }) {
|
|
|
173
181
|
fontWeight: 600,
|
|
174
182
|
fontSize: 16,
|
|
175
183
|
letterSpacing: "0.02em",
|
|
184
|
+
color: (th) => primaryContrastColor(th),
|
|
176
185
|
boxShadow: `0 8px 32px -4px ${alpha(primaryColor, 0.3)}`,
|
|
177
186
|
"&:hover": {
|
|
178
187
|
boxShadow: `0 12px 40px -4px ${alpha(primaryColor, 0.4)}`,
|
|
@@ -22,7 +22,7 @@ import { joinURL } from "ufo";
|
|
|
22
22
|
import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
|
|
23
23
|
import { usePaymentMethodContext } from "@blocklet/payment-react-headless";
|
|
24
24
|
import { getPrefix } from "../../libs/util.js";
|
|
25
|
-
import { formatTokenAmount } from "../utils/format.js";
|
|
25
|
+
import { formatTokenAmount, primaryContrastColor } from "../utils/format.js";
|
|
26
26
|
const scaleIn = keyframes`
|
|
27
27
|
from { transform: scale(0); opacity: 0; }
|
|
28
28
|
60% { transform: scale(1.15); }
|
|
@@ -452,6 +452,7 @@ function SubscriptionLinks({
|
|
|
452
452
|
fontWeight: 700,
|
|
453
453
|
fontSize: { xs: 16, md: 17 },
|
|
454
454
|
letterSpacing: "0.02em",
|
|
455
|
+
color: (theme) => primaryContrastColor(theme),
|
|
455
456
|
boxShadow: "0 8px 24px -4px rgba(59,130,246,0.25)",
|
|
456
457
|
"&:hover": {
|
|
457
458
|
boxShadow: "0 12px 28px -4px rgba(59,130,246,0.35)"
|
|
@@ -486,6 +487,7 @@ function InvoiceLink({
|
|
|
486
487
|
fontWeight: 700,
|
|
487
488
|
fontSize: { xs: 16, md: 17 },
|
|
488
489
|
letterSpacing: "0.02em",
|
|
490
|
+
color: (theme) => primaryContrastColor(theme),
|
|
489
491
|
boxShadow: "0 8px 24px -4px rgba(59,130,246,0.25)",
|
|
490
492
|
"&:hover": {
|
|
491
493
|
boxShadow: "0 12px 28px -4px rgba(59,130,246,0.35)"
|
|
@@ -11,7 +11,7 @@ import Dialog from "@arcblock/ux/lib/Dialog/dialog";
|
|
|
11
11
|
import { CheckCircle as CheckCircleIcon } from "@mui/icons-material";
|
|
12
12
|
import debounce from "lodash/debounce";
|
|
13
13
|
import { usePaymentContext } from "../contexts/payment.js";
|
|
14
|
-
import { formatAmount, formatError, getPrefix, isCrossOrigin } from "../libs/util.js";
|
|
14
|
+
import { formatAmount, formatError, getPrefix, isCrossOrigin, primaryContrastColor } from "../libs/util.js";
|
|
15
15
|
import { useSubscription } from "../hooks/subscription.js";
|
|
16
16
|
import api from "../libs/api.js";
|
|
17
17
|
import LoadingButton from "./loading-button.js";
|
|
@@ -290,6 +290,7 @@ function OverdueInvoicePayment({
|
|
|
290
290
|
const { currency } = item;
|
|
291
291
|
const inProcess = payLoading && selectCurrencyId === currency.id;
|
|
292
292
|
const status = paymentStatus[currency.id] || "idle";
|
|
293
|
+
const containedColorSx = (options?.variant || "contained") === "contained" ? { color: (th) => primaryContrastColor(th) } : {};
|
|
293
294
|
if (status === "success") {
|
|
294
295
|
return /* @__PURE__ */ jsx(
|
|
295
296
|
Button,
|
|
@@ -297,6 +298,7 @@ function OverdueInvoicePayment({
|
|
|
297
298
|
variant: options?.variant || "contained",
|
|
298
299
|
size: "small",
|
|
299
300
|
onClick: () => checkAndHandleInvoicePaid(currency.id),
|
|
301
|
+
sx: containedColorSx,
|
|
300
302
|
...primaryButton ? {} : {
|
|
301
303
|
color: "success",
|
|
302
304
|
startIcon: /* @__PURE__ */ jsx(CheckCircleIcon, {})
|
|
@@ -334,7 +336,7 @@ function OverdueInvoicePayment({
|
|
|
334
336
|
disabled: paying || status === "processing",
|
|
335
337
|
loading: paying || status === "processing",
|
|
336
338
|
onClick: onPay,
|
|
337
|
-
sx: options?.sx,
|
|
339
|
+
sx: { ...containedColorSx, ...options?.sx || {} },
|
|
338
340
|
children: buttonText
|
|
339
341
|
}
|
|
340
342
|
)
|
|
@@ -349,7 +351,7 @@ function OverdueInvoicePayment({
|
|
|
349
351
|
disabled: inProcess,
|
|
350
352
|
loading: inProcess,
|
|
351
353
|
onClick: () => handlePay(item),
|
|
352
|
-
sx: options?.sx,
|
|
354
|
+
sx: { ...containedColorSx, ...options?.sx || {} },
|
|
353
355
|
children: status === "error" ? t("payment.subscription.overdue.retry") : t("payment.subscription.overdue.payNow")
|
|
354
356
|
}
|
|
355
357
|
);
|