@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
|
@@ -562,6 +562,7 @@ function PaymentPanel() {
|
|
|
562
562
|
},
|
|
563
563
|
discounts,
|
|
564
564
|
discountAmount: pricing.discount,
|
|
565
|
+
currency,
|
|
565
566
|
isAmountLoading
|
|
566
567
|
})]
|
|
567
568
|
}), (() => {
|
|
@@ -855,7 +856,8 @@ function PaymentPanel() {
|
|
|
855
856
|
remove: promotion.remove
|
|
856
857
|
},
|
|
857
858
|
discounts,
|
|
858
|
-
discountAmount: pricing.discount
|
|
859
|
+
discountAmount: pricing.discount,
|
|
860
|
+
currency
|
|
859
861
|
})]
|
|
860
862
|
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
|
|
861
863
|
sx: {
|
|
@@ -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;
|
|
@@ -150,6 +150,13 @@ function getErrorConfig(errorCode, error, t) {
|
|
|
150
150
|
color: "#94a3b8"
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
|
+
if (errorCode === "STOP_ACCEPTING_ORDERS") {
|
|
154
|
+
return {
|
|
155
|
+
title: t("payment.checkout.stopAcceptingOrders.title"),
|
|
156
|
+
description: t("payment.checkout.stopAcceptingOrders.description"),
|
|
157
|
+
color: "#f59e0b"
|
|
158
|
+
};
|
|
159
|
+
}
|
|
153
160
|
return {
|
|
154
161
|
title: t("payment.checkout.error.title"),
|
|
155
162
|
description: error,
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
module.exports = ServiceSuspendedDialog;
|
|
7
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
8
|
+
var _material = require("@mui/material");
|
|
9
|
+
var _styles = require("@mui/material/styles");
|
|
10
|
+
var _PauseCircleOutline = _interopRequireDefault(require("@mui/icons-material/PauseCircleOutline"));
|
|
11
|
+
var _context = require("@arcblock/ux/lib/Locale/context");
|
|
12
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
13
|
+
function ServiceSuspendedDialog({
|
|
14
|
+
open,
|
|
15
|
+
onClose
|
|
16
|
+
}) {
|
|
17
|
+
const {
|
|
18
|
+
t
|
|
19
|
+
} = (0, _context.useLocaleContext)();
|
|
20
|
+
return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Dialog, {
|
|
21
|
+
open,
|
|
22
|
+
onClose,
|
|
23
|
+
slotProps: {
|
|
24
|
+
paper: {
|
|
25
|
+
sx: {
|
|
26
|
+
borderRadius: 3,
|
|
27
|
+
maxWidth: 400,
|
|
28
|
+
mx: "auto",
|
|
29
|
+
overflow: "hidden"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.DialogContent, {
|
|
34
|
+
sx: {
|
|
35
|
+
p: 0
|
|
36
|
+
},
|
|
37
|
+
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
|
|
38
|
+
alignItems: "center",
|
|
39
|
+
sx: {
|
|
40
|
+
pt: 4,
|
|
41
|
+
pb: 3,
|
|
42
|
+
px: 4,
|
|
43
|
+
textAlign: "center"
|
|
44
|
+
},
|
|
45
|
+
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
|
|
46
|
+
sx: {
|
|
47
|
+
width: 64,
|
|
48
|
+
height: 64,
|
|
49
|
+
borderRadius: "50%",
|
|
50
|
+
display: "flex",
|
|
51
|
+
alignItems: "center",
|
|
52
|
+
justifyContent: "center",
|
|
53
|
+
bgcolor: theme => (0, _styles.alpha)(theme.palette.warning.main, 0.1),
|
|
54
|
+
mb: 2.5
|
|
55
|
+
},
|
|
56
|
+
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_PauseCircleOutline.default, {
|
|
57
|
+
sx: {
|
|
58
|
+
fontSize: 36,
|
|
59
|
+
color: "warning.main"
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
63
|
+
sx: {
|
|
64
|
+
fontWeight: 700,
|
|
65
|
+
fontSize: 18,
|
|
66
|
+
mb: 1,
|
|
67
|
+
color: "text.primary"
|
|
68
|
+
},
|
|
69
|
+
children: t("payment.checkout.stopAcceptingOrders.title")
|
|
70
|
+
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
|
|
71
|
+
sx: {
|
|
72
|
+
color: "text.secondary",
|
|
73
|
+
fontSize: 14,
|
|
74
|
+
lineHeight: 1.6
|
|
75
|
+
},
|
|
76
|
+
children: t("payment.checkout.stopAcceptingOrders.description")
|
|
77
|
+
})]
|
|
78
|
+
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
|
|
79
|
+
sx: {
|
|
80
|
+
px: 4,
|
|
81
|
+
pb: 3
|
|
82
|
+
},
|
|
83
|
+
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
|
|
84
|
+
fullWidth: true,
|
|
85
|
+
variant: "contained",
|
|
86
|
+
disableElevation: true,
|
|
87
|
+
onClick: onClose,
|
|
88
|
+
sx: {
|
|
89
|
+
borderRadius: 2,
|
|
90
|
+
textTransform: "none",
|
|
91
|
+
fontWeight: 600,
|
|
92
|
+
py: 1
|
|
93
|
+
},
|
|
94
|
+
children: t("common.know")
|
|
95
|
+
})
|
|
96
|
+
})]
|
|
97
|
+
})
|
|
98
|
+
});
|
|
99
|
+
}
|
package/lib/locales/en.js
CHANGED
|
@@ -387,6 +387,10 @@ module.exports = (0, _flat.default)({
|
|
|
387
387
|
title: "Nothing to show here",
|
|
388
388
|
description: "It seems this checkout session is not configured properly"
|
|
389
389
|
},
|
|
390
|
+
stopAcceptingOrders: {
|
|
391
|
+
title: "Service Suspended",
|
|
392
|
+
description: "New order placement is temporarily unavailable due to a system-level service suspension."
|
|
393
|
+
},
|
|
390
394
|
error: {
|
|
391
395
|
title: "Something went wrong"
|
|
392
396
|
},
|
package/lib/locales/zh.js
CHANGED
|
@@ -416,6 +416,10 @@ module.exports = (0, _flat.default)({
|
|
|
416
416
|
title: "\u6CA1\u6709\u4EFB\u4F55\u8D2D\u4E70\u9879\u76EE",
|
|
417
417
|
description: "\u53EF\u80FD\u8FD9\u4E2A\u4ED8\u6B3E\u94FE\u63A5\u6CA1\u6709\u6B63\u786E\u914D\u7F6E"
|
|
418
418
|
},
|
|
419
|
+
stopAcceptingOrders: {
|
|
420
|
+
title: "\u6682\u505C\u670D\u52A1",
|
|
421
|
+
description: "\u56E0\u7CFB\u7EDF\u7B56\u7565\u8C03\u6574\uFF0C\u5F53\u524D\u5DF2\u6682\u505C\u65B0\u8BA2\u5355\u670D\u52A1\u3002"
|
|
422
|
+
},
|
|
419
423
|
error: {
|
|
420
424
|
title: "\u51FA\u4E86\u70B9\u95EE\u9898"
|
|
421
425
|
},
|
|
@@ -40,6 +40,7 @@ var _loadingButton = _interopRequireDefault(require("../../components/loading-bu
|
|
|
40
40
|
var _overDueInvoicePayment = _interopRequireDefault(require("../../components/over-due-invoice-payment"));
|
|
41
41
|
var _currency2 = require("../../libs/currency");
|
|
42
42
|
var _confirm = _interopRequireDefault(require("../../components/confirm"));
|
|
43
|
+
var _serviceSuspendedDialog = _interopRequireDefault(require("../../components/service-suspended-dialog"));
|
|
43
44
|
var _priceChangeConfirm = _interopRequireDefault(require("../../components/price-change-confirm"));
|
|
44
45
|
var _validator = require("../../libs/validator");
|
|
45
46
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -181,6 +182,7 @@ function PaymentForm({
|
|
|
181
182
|
stripeContext: void 0,
|
|
182
183
|
customer,
|
|
183
184
|
customerLimited: false,
|
|
185
|
+
serviceSuspended: false,
|
|
184
186
|
stripePaying: false,
|
|
185
187
|
fastCheckoutInfo: null,
|
|
186
188
|
creditInsufficientInfo: null,
|
|
@@ -1039,6 +1041,12 @@ function PaymentForm({
|
|
|
1039
1041
|
customerLimited: true
|
|
1040
1042
|
});
|
|
1041
1043
|
}
|
|
1044
|
+
if (errorCode === "STOP_ACCEPTING_ORDERS") {
|
|
1045
|
+
shouldToast = false;
|
|
1046
|
+
setState({
|
|
1047
|
+
serviceSuspended: true
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1042
1050
|
}
|
|
1043
1051
|
if (shouldToast) {
|
|
1044
1052
|
_Toast.default.error((0, _util2.formatError)(err));
|
|
@@ -1307,6 +1315,11 @@ function PaymentForm({
|
|
|
1307
1315
|
}),
|
|
1308
1316
|
title: t("payment.customer.pastDue.alert.title")
|
|
1309
1317
|
}
|
|
1318
|
+
}), state.serviceSuspended && /* @__PURE__ */(0, _jsxRuntime.jsx)(_serviceSuspendedDialog.default, {
|
|
1319
|
+
open: true,
|
|
1320
|
+
onClose: () => setState({
|
|
1321
|
+
serviceSuspended: false
|
|
1322
|
+
})
|
|
1310
1323
|
}), FastCheckoutConfirmDialog, CreditInsufficientDialog, PriceUpdatedDialog, state.priceChangeConfirm?.open && /* @__PURE__ */(0, _jsxRuntime.jsx)(_priceChangeConfirm.default, {
|
|
1311
1324
|
open: true,
|
|
1312
1325
|
changePercent: state.priceChangeConfirm.changePercent,
|
|
@@ -1617,6 +1630,11 @@ function PaymentForm({
|
|
|
1617
1630
|
}),
|
|
1618
1631
|
title: t("payment.customer.pastDue.alert.title")
|
|
1619
1632
|
}
|
|
1633
|
+
}), state.serviceSuspended && /* @__PURE__ */(0, _jsxRuntime.jsx)(_serviceSuspendedDialog.default, {
|
|
1634
|
+
open: true,
|
|
1635
|
+
onClose: () => setState({
|
|
1636
|
+
serviceSuspended: false
|
|
1637
|
+
})
|
|
1620
1638
|
}), FastCheckoutConfirmDialog, CreditInsufficientDialog, PriceUpdatedDialog, state.priceChangeConfirm?.open && /* @__PURE__ */(0, _jsxRuntime.jsx)(_priceChangeConfirm.default, {
|
|
1621
1639
|
open: true,
|
|
1622
1640
|
changePercent: state.priceChangeConfirm.changePercent,
|
package/lib/payment/index.js
CHANGED
|
@@ -87,6 +87,10 @@ function PaymentInner({
|
|
|
87
87
|
return Array.from(currencyIds);
|
|
88
88
|
}, [paymentMethods]);
|
|
89
89
|
const defaultCurrencyId = (0, _react.useMemo)(() => {
|
|
90
|
+
const hasAppliedDiscount = Boolean(state.checkoutSession?.discounts?.length);
|
|
91
|
+
if (hasAppliedDiscount && state.checkoutSession.currency_id && availableCurrencyIds.includes(state.checkoutSession.currency_id)) {
|
|
92
|
+
return state.checkoutSession.currency_id;
|
|
93
|
+
}
|
|
90
94
|
if (query.currencyId && availableCurrencyIds.includes(query.currencyId)) {
|
|
91
95
|
return query.currencyId;
|
|
92
96
|
}
|
|
@@ -104,7 +108,7 @@ function PaymentInner({
|
|
|
104
108
|
return state.checkoutSession.currency_id;
|
|
105
109
|
}
|
|
106
110
|
return availableCurrencyIds?.[0];
|
|
107
|
-
}, [query.currencyId, availableCurrencyIds, session?.user, state.checkoutSession
|
|
111
|
+
}, [query.currencyId, availableCurrencyIds, session?.user, state.checkoutSession, paymentMethods]);
|
|
108
112
|
const defaultMethodId = paymentMethods.find(m => m.payment_currencies.some(c => c.id === defaultCurrencyId))?.id;
|
|
109
113
|
const hideSummaryCard = mode.endsWith("-minimal") || !showCheckoutSummary;
|
|
110
114
|
const methods = (0, _reactHookForm.useForm)({
|
|
@@ -127,13 +131,20 @@ function PaymentInner({
|
|
|
127
131
|
}
|
|
128
132
|
});
|
|
129
133
|
(0, _react.useEffect)(() => {
|
|
134
|
+
const hasAppliedDiscount = Boolean(state.checkoutSession?.discounts?.length);
|
|
135
|
+
const currentCurrency = methods.getValues("payment_currency");
|
|
136
|
+
const currentMethod = methods.getValues("payment_method");
|
|
130
137
|
if (defaultCurrencyId) {
|
|
131
|
-
|
|
138
|
+
if (!hasAppliedDiscount || !currentCurrency) {
|
|
139
|
+
methods.setValue("payment_currency", defaultCurrencyId);
|
|
140
|
+
}
|
|
132
141
|
}
|
|
133
142
|
if (defaultMethodId) {
|
|
134
|
-
|
|
143
|
+
if (!hasAppliedDiscount || !currentMethod) {
|
|
144
|
+
methods.setValue("payment_method", defaultMethodId);
|
|
145
|
+
}
|
|
135
146
|
}
|
|
136
|
-
}, [defaultCurrencyId, defaultMethodId]);
|
|
147
|
+
}, [defaultCurrencyId, defaultMethodId, state.checkoutSession.discounts]);
|
|
137
148
|
(0, _react.useEffect)(() => {
|
|
138
149
|
if (!(0, _util2.isMobileSafari)()) {
|
|
139
150
|
return () => {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blocklet/payment-react",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.4",
|
|
4
4
|
"description": "Reusable react components for payment kit v2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@arcblock/react-hooks": "^3.5.1",
|
|
60
60
|
"@arcblock/ux": "^3.5.1",
|
|
61
61
|
"@arcblock/ws": "^1.28.5",
|
|
62
|
-
"@blocklet/payment-react-headless": "1.26.
|
|
62
|
+
"@blocklet/payment-react-headless": "1.26.4",
|
|
63
63
|
"@blocklet/theme": "^3.5.1",
|
|
64
64
|
"@blocklet/ui-react": "^3.5.1",
|
|
65
65
|
"@mui/icons-material": "^7.1.2",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"@babel/core": "^7.27.4",
|
|
98
98
|
"@babel/preset-env": "^7.27.2",
|
|
99
99
|
"@babel/preset-react": "^7.27.1",
|
|
100
|
-
"@blocklet/payment-types": "1.26.
|
|
100
|
+
"@blocklet/payment-types": "1.26.4",
|
|
101
101
|
"@storybook/addon-essentials": "^7.6.20",
|
|
102
102
|
"@storybook/addon-interactions": "^7.6.20",
|
|
103
103
|
"@storybook/addon-links": "^7.6.20",
|
|
@@ -128,5 +128,5 @@
|
|
|
128
128
|
"vite-plugin-babel": "^1.3.1",
|
|
129
129
|
"vite-plugin-node-polyfills": "^0.23.0"
|
|
130
130
|
},
|
|
131
|
-
"gitHead": "
|
|
131
|
+
"gitHead": "90fb554f2edfcd1c141e3887eef835b1a545f5ad"
|
|
132
132
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import StripeForm from '../../../payment/form/stripe';
|
|
10
10
|
import ConfirmDialog from '../../../components/confirm';
|
|
11
11
|
import PriceChangeConfirm from '../../../components/price-change-confirm';
|
|
12
|
+
import ServiceSuspendedDialog from '../../../components/service-suspended-dialog';
|
|
12
13
|
import { formatTokenAmount } from '../../utils/format';
|
|
13
14
|
|
|
14
15
|
function getRedirectUrl(session: any): string | undefined {
|
|
@@ -119,6 +120,9 @@ export default function CheckoutDialogs() {
|
|
|
119
120
|
/>
|
|
120
121
|
)}
|
|
121
122
|
|
|
123
|
+
{/* Service Suspended Dialog */}
|
|
124
|
+
{(submit.context as any)?.type === 'service_suspended' && <ServiceSuspendedDialog open onClose={submit.cancel} />}
|
|
125
|
+
|
|
122
126
|
{/* Credit Insufficient Dialog (matches V1 ConfirmDialog) */}
|
|
123
127
|
{submit.status === 'credit_insufficient' && submit.context?.type === 'credit_insufficient' && (
|
|
124
128
|
<ConfirmDialog
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
Typography,
|
|
16
16
|
} from '@mui/material';
|
|
17
17
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
18
|
+
import { formatCouponTerms } from '../../../libs/util';
|
|
18
19
|
|
|
19
20
|
interface PromotionInputProps {
|
|
20
21
|
promotion: {
|
|
@@ -27,6 +28,8 @@ interface PromotionInputProps {
|
|
|
27
28
|
};
|
|
28
29
|
discounts: any[];
|
|
29
30
|
discountAmount: string | null;
|
|
31
|
+
// eslint-disable-next-line react/require-default-props
|
|
32
|
+
currency?: any;
|
|
30
33
|
/** Start with input field visible (skip the "Add promotion code" button) */
|
|
31
34
|
initialShowInput?: boolean;
|
|
32
35
|
/** Show skeleton for the discount amount while switching */
|
|
@@ -37,10 +40,11 @@ export default function PromotionInput({
|
|
|
37
40
|
promotion,
|
|
38
41
|
discounts,
|
|
39
42
|
discountAmount,
|
|
43
|
+
currency = null,
|
|
40
44
|
initialShowInput = false,
|
|
41
45
|
isAmountLoading = false,
|
|
42
46
|
}: PromotionInputProps) {
|
|
43
|
-
const { t } = useLocaleContext();
|
|
47
|
+
const { t, locale } = useLocaleContext();
|
|
44
48
|
const [showInput, setShowInput] = useState(false);
|
|
45
49
|
const [code, setCode] = useState('');
|
|
46
50
|
const [applying, setApplying] = useState(false);
|
|
@@ -77,19 +81,7 @@ export default function PromotionInput({
|
|
|
77
81
|
const discCode =
|
|
78
82
|
disc.promotion_code_details?.code || disc.verification_data?.code || disc.promotion_code || '';
|
|
79
83
|
const coupon = disc.coupon_details || {};
|
|
80
|
-
const
|
|
81
|
-
coupon.percent_off > 0
|
|
82
|
-
? t('payment.checkout.coupon.percentage', { percent: coupon.percent_off })
|
|
83
|
-
: `${coupon.percent_off || 0}%`;
|
|
84
|
-
let description = '';
|
|
85
|
-
if (coupon.duration === 'repeating' && coupon.duration_in_months) {
|
|
86
|
-
const months = coupon.duration_in_months;
|
|
87
|
-
description = `${couponOff} for ${months} month${months > 1 ? 's' : ''}`;
|
|
88
|
-
} else if (coupon.duration === 'forever') {
|
|
89
|
-
description = t('payment.checkout.coupon.terms.forever', { couponOff });
|
|
90
|
-
} else if (coupon.duration === 'once') {
|
|
91
|
-
description = t('payment.checkout.coupon.terms.once', { couponOff });
|
|
92
|
-
}
|
|
84
|
+
const description = coupon && currency ? formatCouponTerms(coupon, currency, locale) : '';
|
|
93
85
|
return (
|
|
94
86
|
<Stack
|
|
95
87
|
key={disc.promotion_code || disc.coupon || i}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState,
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
2
|
import { Box, Button, InputBase, InputAdornment, Stack, Typography } from '@mui/material';
|
|
3
3
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
4
4
|
import CountrySelect from '../../../components/country-select';
|
|
@@ -17,7 +17,9 @@ interface CustomerInfoCardProps {
|
|
|
17
17
|
values: Record<string, any>;
|
|
18
18
|
onChange: (field: string, value: string | boolean | Record<string, string>) => void;
|
|
19
19
|
errors: Partial<Record<string, string>>;
|
|
20
|
+
checkValid: () => Promise<boolean>;
|
|
20
21
|
validateField: (field: string) => Promise<void>;
|
|
22
|
+
prefetched: boolean;
|
|
21
23
|
};
|
|
22
24
|
isLoggedIn: boolean;
|
|
23
25
|
}
|
|
@@ -39,20 +41,31 @@ export default function CustomerInfoCard({ form, isLoggedIn }: CustomerInfoCardP
|
|
|
39
41
|
const { t } = useLocaleContext();
|
|
40
42
|
const labels = fieldLabelMap(t);
|
|
41
43
|
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
44
|
+
// Don't render until first validation completes — avoids flash.
|
|
45
|
+
// Valid → show collapsed summary; invalid → show expanded form.
|
|
46
|
+
// After first check, only manual "Edit"/"Confirm" toggles.
|
|
47
|
+
const [showEditForm, setShowEditForm] = useState(false);
|
|
48
|
+
const [ready, setReady] = useState(false);
|
|
49
|
+
const checkedRef = useRef(false);
|
|
46
50
|
|
|
47
|
-
// When data arrives (e.g. prefetch), auto-switch to confirmed if valid
|
|
48
51
|
useEffect(() => {
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
if (checkedRef.current) return;
|
|
53
|
+
// Wait for data to arrive before making a decision
|
|
54
|
+
if (!form.prefetched && !form.values.customer_name && !form.values.customer_email) return;
|
|
55
|
+
checkedRef.current = true;
|
|
56
|
+
form.checkValid().then((valid) => {
|
|
57
|
+
setShowEditForm(!valid);
|
|
58
|
+
setReady(true);
|
|
59
|
+
});
|
|
60
|
+
}, [form.prefetched]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
54
61
|
|
|
55
|
-
|
|
62
|
+
// Wrap onChange to delegate to parent form
|
|
63
|
+
const handleChange: typeof form.onChange = useCallback(
|
|
64
|
+
(field, value) => form.onChange(field, value),
|
|
65
|
+
[form.onChange] // eslint-disable-line react-hooks/exhaustive-deps
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
if (!isLoggedIn || !ready) return null;
|
|
56
69
|
|
|
57
70
|
// Summary view
|
|
58
71
|
if (!showEditForm) {
|
|
@@ -162,8 +175,8 @@ export default function CustomerInfoCard({ form, isLoggedIn }: CustomerInfoCardP
|
|
|
162
175
|
key={name}
|
|
163
176
|
value={value || ''}
|
|
164
177
|
country={form.values.billing_address?.country || ''}
|
|
165
|
-
onChange={(phone) =>
|
|
166
|
-
onCountryChange={(c) =>
|
|
178
|
+
onChange={(phone) => handleChange('customer_phone', phone)}
|
|
179
|
+
onCountryChange={(c) => handleChange('billing_address.country', c)}
|
|
167
180
|
onBlur={() => form.validateField(name)}
|
|
168
181
|
label={label}
|
|
169
182
|
error={form.errors[name]}
|
|
@@ -177,14 +190,14 @@ export default function CustomerInfoCard({ form, isLoggedIn }: CustomerInfoCardP
|
|
|
177
190
|
<InputBase
|
|
178
191
|
fullWidth
|
|
179
192
|
value={value || ''}
|
|
180
|
-
onChange={(e) =>
|
|
193
|
+
onChange={(e) => handleChange(name, e.target.value)}
|
|
181
194
|
onBlur={() => form.validateField(name)}
|
|
182
195
|
startAdornment={
|
|
183
196
|
isPostalCode ? (
|
|
184
197
|
<InputAdornment position="start" sx={{ mr: 0.5, ml: -0.5 }}>
|
|
185
198
|
<CountrySelect
|
|
186
199
|
value={form.values.billing_address?.country || ''}
|
|
187
|
-
onChange={(v) =>
|
|
200
|
+
onChange={(v) => handleChange('billing_address.country', v)}
|
|
188
201
|
sx={{
|
|
189
202
|
'.MuiOutlinedInput-notchedOutline': { borderColor: 'transparent !important' },
|
|
190
203
|
'& .MuiSelect-select': { py: 0, pr: '20px !important' },
|
|
@@ -19,8 +19,8 @@ export default function StatusFeedback({ status, context, onReset }: StatusFeedb
|
|
|
19
19
|
prevStatusRef.current = status;
|
|
20
20
|
|
|
21
21
|
if (status === 'failed' && context?.type === 'error') {
|
|
22
|
-
// CUSTOMER_LIMITED
|
|
23
|
-
if (context.code === 'CUSTOMER_LIMITED') return;
|
|
22
|
+
// CUSTOMER_LIMITED / STOP_ACCEPTING_ORDERS are handled by CheckoutDialogs
|
|
23
|
+
if (context.code === 'CUSTOMER_LIMITED' || context.code === 'STOP_ACCEPTING_ORDERS') return;
|
|
24
24
|
|
|
25
25
|
Toast.error(context.message || 'Payment failed');
|
|
26
26
|
onReset();
|
|
@@ -27,14 +27,26 @@ const fadeIn = {
|
|
|
27
27
|
animation: { xs: 'none', md: 'fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.15s both' },
|
|
28
28
|
} as const;
|
|
29
29
|
|
|
30
|
-
// Desktop: right panel slides in from
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
// Desktop: right panel slides in from right (non-Safari) or fades in (Safari)
|
|
31
|
+
// Safari has flexbox reflow bugs with translateX(100%), so we use a subtle fade instead
|
|
32
|
+
const isSafari =
|
|
33
|
+
typeof navigator !== 'undefined' && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
|
|
34
|
+
|
|
35
|
+
const slideInFromRight = isSafari
|
|
36
|
+
? ({
|
|
37
|
+
'@keyframes panelFadeIn': {
|
|
38
|
+
from: { opacity: 0, transform: 'translateX(24px)' },
|
|
39
|
+
to: { opacity: 1, transform: 'none' },
|
|
40
|
+
},
|
|
41
|
+
animation: { xs: 'none', md: 'panelFadeIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) both' },
|
|
42
|
+
} as const)
|
|
43
|
+
: ({
|
|
44
|
+
'@keyframes slideInRight': {
|
|
45
|
+
from: { transform: 'translateX(100%)' },
|
|
46
|
+
to: { transform: 'translateX(0)' },
|
|
47
|
+
},
|
|
48
|
+
animation: { xs: 'none', md: 'slideInRight 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards' },
|
|
49
|
+
} as const);
|
|
38
50
|
|
|
39
51
|
interface CheckoutLayoutProps {
|
|
40
52
|
left: React.ReactNode;
|
|
@@ -94,6 +106,7 @@ export default function CheckoutLayout({ left, right, mode = 'inline' }: Checkou
|
|
|
94
106
|
<Box
|
|
95
107
|
sx={{
|
|
96
108
|
flex: 1,
|
|
109
|
+
minWidth: 0,
|
|
97
110
|
bgcolor: (t) => (t.palette.mode === 'dark' ? 'background.default' : '#f8faff'),
|
|
98
111
|
p: { xs: 3, md: 5 },
|
|
99
112
|
pt: { xs: 3, md: 4 },
|
|
@@ -170,7 +183,8 @@ export default function CheckoutLayout({ left, right, mode = 'inline' }: Checkou
|
|
|
170
183
|
{!hideLeft && (
|
|
171
184
|
<Box
|
|
172
185
|
sx={{
|
|
173
|
-
|
|
186
|
+
flex: { xs: 'none', md: '0 0 50%' },
|
|
187
|
+
width: { xs: '100%' },
|
|
174
188
|
height: { xs: 'auto', md: '100vh' },
|
|
175
189
|
display: 'flex',
|
|
176
190
|
justifyContent: { md: 'center' },
|
|
@@ -196,7 +210,8 @@ export default function CheckoutLayout({ left, right, mode = 'inline' }: Checkou
|
|
|
196
210
|
{/* Right panel — full width when left is hidden */}
|
|
197
211
|
<Box
|
|
198
212
|
sx={{
|
|
199
|
-
|
|
213
|
+
flex: hideLeft ? 'none' : { xs: 'none', md: '0 0 50%' },
|
|
214
|
+
width: hideLeft ? '100%' : { xs: '100%' },
|
|
200
215
|
height: hideLeft ? '100vh' : { xs: 'auto', md: '100vh' },
|
|
201
216
|
bgcolor: 'background.paper',
|
|
202
217
|
boxShadow: hideLeft ? 'none' : { md: '-4px 0 16px rgba(0,0,0,0.04)' },
|
|
@@ -459,6 +459,7 @@ export default function PaymentPanel() {
|
|
|
459
459
|
}}
|
|
460
460
|
discounts={discounts}
|
|
461
461
|
discountAmount={pricing.discount}
|
|
462
|
+
currency={currency}
|
|
462
463
|
isAmountLoading={isAmountLoading}
|
|
463
464
|
/>
|
|
464
465
|
</>
|
|
@@ -657,6 +658,7 @@ export default function PaymentPanel() {
|
|
|
657
658
|
}}
|
|
658
659
|
discounts={discounts}
|
|
659
660
|
discountAmount={pricing.discount}
|
|
661
|
+
currency={currency}
|
|
660
662
|
/>
|
|
661
663
|
</Drawer>
|
|
662
664
|
)}
|
|
@@ -7,7 +7,7 @@ import { primaryContrastColor } from '../utils/format';
|
|
|
7
7
|
|
|
8
8
|
interface ErrorViewProps {
|
|
9
9
|
error: string;
|
|
10
|
-
errorCode?: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | null;
|
|
10
|
+
errorCode?: 'SESSION_EXPIRED' | 'EMPTY_LINE_ITEMS' | 'STOP_ACCEPTING_ORDERS' | null;
|
|
11
11
|
mode?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
@@ -128,6 +128,14 @@ function getErrorConfig(
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
if (errorCode === 'STOP_ACCEPTING_ORDERS') {
|
|
132
|
+
return {
|
|
133
|
+
title: t('payment.checkout.stopAcceptingOrders.title'),
|
|
134
|
+
description: t('payment.checkout.stopAcceptingOrders.description'),
|
|
135
|
+
color: '#f59e0b',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
131
139
|
return {
|
|
132
140
|
title: t('payment.checkout.error.title'),
|
|
133
141
|
description: error,
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Box, Button, Dialog, DialogContent, Stack, Typography } from '@mui/material';
|
|
2
|
+
import { alpha } from '@mui/material/styles';
|
|
3
|
+
import PauseCircleOutlineIcon from '@mui/icons-material/PauseCircleOutline';
|
|
4
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
5
|
+
|
|
6
|
+
export default function ServiceSuspendedDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
|
|
7
|
+
const { t } = useLocaleContext();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<Dialog
|
|
11
|
+
open={open}
|
|
12
|
+
onClose={onClose}
|
|
13
|
+
slotProps={{
|
|
14
|
+
paper: {
|
|
15
|
+
sx: {
|
|
16
|
+
borderRadius: 3,
|
|
17
|
+
maxWidth: 400,
|
|
18
|
+
mx: 'auto',
|
|
19
|
+
overflow: 'hidden',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}}>
|
|
23
|
+
<DialogContent sx={{ p: 0 }}>
|
|
24
|
+
<Stack alignItems="center" sx={{ pt: 4, pb: 3, px: 4, textAlign: 'center' }}>
|
|
25
|
+
<Box
|
|
26
|
+
sx={{
|
|
27
|
+
width: 64,
|
|
28
|
+
height: 64,
|
|
29
|
+
borderRadius: '50%',
|
|
30
|
+
display: 'flex',
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
justifyContent: 'center',
|
|
33
|
+
bgcolor: (theme) => alpha(theme.palette.warning.main, 0.1),
|
|
34
|
+
mb: 2.5,
|
|
35
|
+
}}>
|
|
36
|
+
<PauseCircleOutlineIcon sx={{ fontSize: 36, color: 'warning.main' }} />
|
|
37
|
+
</Box>
|
|
38
|
+
|
|
39
|
+
<Typography sx={{ fontWeight: 700, fontSize: 18, mb: 1, color: 'text.primary' }}>
|
|
40
|
+
{t('payment.checkout.stopAcceptingOrders.title')}
|
|
41
|
+
</Typography>
|
|
42
|
+
|
|
43
|
+
<Typography sx={{ color: 'text.secondary', fontSize: 14, lineHeight: 1.6 }}>
|
|
44
|
+
{t('payment.checkout.stopAcceptingOrders.description')}
|
|
45
|
+
</Typography>
|
|
46
|
+
</Stack>
|
|
47
|
+
|
|
48
|
+
<Box sx={{ px: 4, pb: 3 }}>
|
|
49
|
+
<Button
|
|
50
|
+
fullWidth
|
|
51
|
+
variant="contained"
|
|
52
|
+
disableElevation
|
|
53
|
+
onClick={onClose}
|
|
54
|
+
sx={{
|
|
55
|
+
borderRadius: 2,
|
|
56
|
+
textTransform: 'none',
|
|
57
|
+
fontWeight: 600,
|
|
58
|
+
py: 1,
|
|
59
|
+
}}>
|
|
60
|
+
{t('common.know')}
|
|
61
|
+
</Button>
|
|
62
|
+
</Box>
|
|
63
|
+
</DialogContent>
|
|
64
|
+
</Dialog>
|
|
65
|
+
);
|
|
66
|
+
}
|
package/src/locales/en.tsx
CHANGED
|
@@ -396,6 +396,10 @@ export default flat({
|
|
|
396
396
|
title: 'Nothing to show here',
|
|
397
397
|
description: 'It seems this checkout session is not configured properly',
|
|
398
398
|
},
|
|
399
|
+
stopAcceptingOrders: {
|
|
400
|
+
title: 'Service Suspended',
|
|
401
|
+
description: 'New order placement is temporarily unavailable due to a system-level service suspension.',
|
|
402
|
+
},
|
|
399
403
|
error: {
|
|
400
404
|
title: 'Something went wrong',
|
|
401
405
|
},
|
package/src/locales/zh.tsx
CHANGED