@blocklet/payment-react 1.26.2 → 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.
Files changed (45) hide show
  1. package/es/checkout-v2/components/dialogs/checkout-dialogs.js +2 -0
  2. package/es/checkout-v2/components/left/promotion-input.d.ts +2 -1
  3. package/es/checkout-v2/components/left/promotion-input.js +4 -11
  4. package/es/checkout-v2/components/right/customer-info-card.d.ts +2 -0
  5. package/es/checkout-v2/components/right/customer-info-card.js +22 -14
  6. package/es/checkout-v2/components/right/status-feedback.js +1 -1
  7. package/es/checkout-v2/layouts/checkout-layout.js +13 -3
  8. package/es/checkout-v2/panels/right/payment-panel.js +3 -1
  9. package/es/checkout-v2/views/error-view.d.ts +1 -1
  10. package/es/checkout-v2/views/error-view.js +7 -0
  11. package/es/components/service-suspended-dialog.d.ts +4 -0
  12. package/es/components/service-suspended-dialog.js +61 -0
  13. package/es/locales/en.js +4 -0
  14. package/es/locales/zh.js +4 -0
  15. package/es/payment/form/index.js +17 -0
  16. package/es/payment/index.js +15 -4
  17. package/lib/checkout-v2/components/dialogs/checkout-dialogs.js +4 -0
  18. package/lib/checkout-v2/components/left/promotion-input.d.ts +2 -1
  19. package/lib/checkout-v2/components/left/promotion-input.js +5 -17
  20. package/lib/checkout-v2/components/right/customer-info-card.d.ts +2 -0
  21. package/lib/checkout-v2/components/right/customer-info-card.js +19 -13
  22. package/lib/checkout-v2/components/right/status-feedback.js +1 -1
  23. package/lib/checkout-v2/layouts/checkout-layout.js +28 -5
  24. package/lib/checkout-v2/panels/right/payment-panel.js +3 -1
  25. package/lib/checkout-v2/views/error-view.d.ts +1 -1
  26. package/lib/checkout-v2/views/error-view.js +7 -0
  27. package/lib/components/service-suspended-dialog.d.ts +4 -0
  28. package/lib/components/service-suspended-dialog.js +97 -0
  29. package/lib/locales/en.js +4 -0
  30. package/lib/locales/zh.js +4 -0
  31. package/lib/payment/form/index.js +23 -0
  32. package/lib/payment/index.js +15 -4
  33. package/package.json +4 -4
  34. package/src/checkout-v2/components/dialogs/checkout-dialogs.tsx +4 -0
  35. package/src/checkout-v2/components/left/promotion-input.tsx +6 -14
  36. package/src/checkout-v2/components/right/customer-info-card.tsx +29 -16
  37. package/src/checkout-v2/components/right/status-feedback.tsx +2 -2
  38. package/src/checkout-v2/layouts/checkout-layout.tsx +25 -10
  39. package/src/checkout-v2/panels/right/payment-panel.tsx +2 -0
  40. package/src/checkout-v2/views/error-view.tsx +9 -1
  41. package/src/components/service-suspended-dialog.tsx +64 -0
  42. package/src/locales/en.tsx +4 -0
  43. package/src/locales/zh.tsx +4 -0
  44. package/src/payment/form/index.tsx +20 -0
  45. 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 couponOff = coupon.percent_off > 0 ? t("payment.checkout.coupon.percentage", { percent: coupon.percent_off }) : `${coupon.percent_off || 0}%`;
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, useEffect, useRef } from "react";
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 hasRequiredData = !!(form.values.customer_name && form.values.customer_email);
23
- const [showEditForm, setShowEditForm] = useState(!hasRequiredData);
24
- const autoConfirmedRef = useRef(false);
22
+ const [showEditForm, setShowEditForm] = useState(false);
23
+ const [ready, setReady] = useState(false);
24
+ const checkedRef = useRef(false);
25
25
  useEffect(() => {
26
- if (!autoConfirmedRef.current && form.values.customer_name && form.values.customer_email) {
27
- autoConfirmedRef.current = true;
28
- setShowEditForm(false);
29
- }
30
- }, [form.values.customer_name, form.values.customer_email]);
31
- if (!isLoggedIn) return null;
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) => form.onChange("customer_phone", phone),
112
- onCountryChange: (c) => form.onChange("billing_address.country", 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) => form.onChange(name, e.target.value),
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) => form.onChange("billing_address.country", 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 slideInFromRight = {
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
- width: { xs: "100%", md: "50%" },
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
- width: hideLeft ? "100%" : { xs: "100%", md: "50%" },
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,4 @@
1
+ export default function ServiceSuspendedDialog({ open, onClose }: {
2
+ open: boolean;
3
+ onClose: () => void;
4
+ }): import("react").JSX.Element;
@@ -0,0 +1,61 @@
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
+ PaperProps: {
14
+ sx: {
15
+ borderRadius: 3,
16
+ maxWidth: 400,
17
+ mx: "auto",
18
+ overflow: "hidden"
19
+ }
20
+ },
21
+ children: /* @__PURE__ */ jsxs(DialogContent, { sx: { p: 0 }, children: [
22
+ /* @__PURE__ */ jsxs(Stack, { alignItems: "center", sx: { pt: 4, pb: 3, px: 4, textAlign: "center" }, children: [
23
+ /* @__PURE__ */ jsx(
24
+ Box,
25
+ {
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
+ children: /* @__PURE__ */ jsx(PauseCircleOutlineIcon, { sx: { fontSize: 36, color: "warning.main" } })
37
+ }
38
+ ),
39
+ /* @__PURE__ */ jsx(Typography, { sx: { fontWeight: 700, fontSize: 18, mb: 1, color: "text.primary" }, children: t("payment.checkout.stopAcceptingOrders.title") }),
40
+ /* @__PURE__ */ jsx(Typography, { sx: { color: "text.secondary", fontSize: 14, lineHeight: 1.6 }, children: t("payment.checkout.stopAcceptingOrders.description") })
41
+ ] }),
42
+ /* @__PURE__ */ jsx(Box, { sx: { px: 4, pb: 3 }, children: /* @__PURE__ */ jsx(
43
+ Button,
44
+ {
45
+ fullWidth: true,
46
+ variant: "contained",
47
+ disableElevation: true,
48
+ onClick: onClose,
49
+ sx: {
50
+ borderRadius: 2,
51
+ textTransform: "none",
52
+ fontWeight: 600,
53
+ py: 1
54
+ },
55
+ children: t("common.know")
56
+ }
57
+ ) })
58
+ ] })
59
+ }
60
+ );
61
+ }
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
  },
@@ -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,16 @@ export default function PaymentForm({
1444
1451
  }
1445
1452
  }
1446
1453
  ),
1454
+ state.serviceSuspended && /* @__PURE__ */ jsx(
1455
+ ConfirmDialog,
1456
+ {
1457
+ onConfirm: () => setState({ serviceSuspended: false }),
1458
+ onCancel: () => setState({ serviceSuspended: false }),
1459
+ title: t("payment.checkout.stopAcceptingOrders.title"),
1460
+ message: t("payment.checkout.stopAcceptingOrders.description"),
1461
+ confirm: t("common.confirm")
1462
+ }
1463
+ ),
1447
1464
  FastCheckoutConfirmDialog,
1448
1465
  CreditInsufficientDialog,
1449
1466
  PriceUpdatedDialog,
@@ -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.currency_id, paymentMethods]);
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
- methods.setValue("payment_currency", defaultCurrencyId);
133
+ if (!hasAppliedDiscount || !currentCurrency) {
134
+ methods.setValue("payment_currency", defaultCurrencyId);
135
+ }
127
136
  }
128
137
  if (defaultMethodId) {
129
- methods.setValue("payment_method", defaultMethodId);
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 couponOff = coupon.percent_off > 0 ? t("payment.checkout.coupon.percentage", {
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 hasRequiredData = !!(form.values.customer_name && form.values.customer_email);
35
- const [showEditForm, setShowEditForm] = (0, _react.useState)(!hasRequiredData);
36
- const autoConfirmedRef = (0, _react.useRef)(false);
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 (!autoConfirmedRef.current && form.values.customer_name && form.values.customer_email) {
39
- autoConfirmedRef.current = true;
40
- setShowEditForm(false);
41
- }
42
- }, [form.values.customer_name, form.values.customer_email]);
43
- if (!isLoggedIn) return null;
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 => form.onChange("customer_phone", phone),
181
- onCountryChange: c => form.onChange("billing_address.country", 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 => form.onChange(name, e.target.value),
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 => form.onChange("billing_address.country", 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
  }