@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.
Files changed (73) hide show
  1. package/es/checkout-v2/components/dialogs/checkout-dialogs.js +2 -0
  2. package/es/checkout-v2/components/left/cross-sell-card.js +3 -3
  3. package/es/checkout-v2/components/left/product-item-card.js +9 -3
  4. package/es/checkout-v2/components/left/promotion-input.d.ts +4 -1
  5. package/es/checkout-v2/components/left/promotion-input.js +8 -13
  6. package/es/checkout-v2/components/right/customer-info-card.d.ts +2 -0
  7. package/es/checkout-v2/components/right/customer-info-card.js +22 -14
  8. package/es/checkout-v2/components/right/status-feedback.js +1 -1
  9. package/es/checkout-v2/components/right/submit-button.js +3 -1
  10. package/es/checkout-v2/layouts/checkout-layout.js +13 -3
  11. package/es/checkout-v2/panels/left/composite-panel.js +27 -6
  12. package/es/checkout-v2/panels/right/payment-panel.js +40 -9
  13. package/es/checkout-v2/utils/format.d.ts +1 -1
  14. package/es/checkout-v2/utils/format.js +1 -0
  15. package/es/checkout-v2/views/error-view.d.ts +1 -1
  16. package/es/checkout-v2/views/error-view.js +9 -0
  17. package/es/checkout-v2/views/success-view.js +3 -1
  18. package/es/components/over-due-invoice-payment.js +5 -3
  19. package/es/components/service-suspended-dialog.d.ts +4 -0
  20. package/es/components/service-suspended-dialog.js +61 -0
  21. package/es/libs/util.d.ts +8 -0
  22. package/es/libs/util.js +3 -0
  23. package/es/locales/en.js +4 -0
  24. package/es/locales/zh.js +4 -0
  25. package/es/payment/form/index.js +17 -0
  26. package/es/payment/index.js +15 -4
  27. package/lib/checkout-v2/components/dialogs/checkout-dialogs.js +4 -0
  28. package/lib/checkout-v2/components/left/cross-sell-card.js +2 -2
  29. package/lib/checkout-v2/components/left/product-item-card.js +9 -2
  30. package/lib/checkout-v2/components/left/promotion-input.d.ts +4 -1
  31. package/lib/checkout-v2/components/left/promotion-input.js +12 -19
  32. package/lib/checkout-v2/components/right/customer-info-card.d.ts +2 -0
  33. package/lib/checkout-v2/components/right/customer-info-card.js +19 -13
  34. package/lib/checkout-v2/components/right/status-feedback.js +1 -1
  35. package/lib/checkout-v2/components/right/submit-button.js +3 -1
  36. package/lib/checkout-v2/layouts/checkout-layout.js +28 -5
  37. package/lib/checkout-v2/panels/left/composite-panel.js +20 -5
  38. package/lib/checkout-v2/panels/right/payment-panel.js +46 -7
  39. package/lib/checkout-v2/utils/format.d.ts +1 -1
  40. package/lib/checkout-v2/utils/format.js +7 -0
  41. package/lib/checkout-v2/views/error-view.d.ts +1 -1
  42. package/lib/checkout-v2/views/error-view.js +9 -0
  43. package/lib/checkout-v2/views/success-view.js +2 -0
  44. package/lib/components/over-due-invoice-payment.js +12 -2
  45. package/lib/components/service-suspended-dialog.d.ts +4 -0
  46. package/lib/components/service-suspended-dialog.js +97 -0
  47. package/lib/libs/util.d.ts +8 -0
  48. package/lib/libs/util.js +4 -0
  49. package/lib/locales/en.js +4 -0
  50. package/lib/locales/zh.js +4 -0
  51. package/lib/payment/form/index.js +23 -0
  52. package/lib/payment/index.js +15 -4
  53. package/package.json +4 -4
  54. package/src/checkout-v2/components/dialogs/checkout-dialogs.tsx +4 -0
  55. package/src/checkout-v2/components/left/cross-sell-card.tsx +3 -3
  56. package/src/checkout-v2/components/left/product-item-card.tsx +18 -8
  57. package/src/checkout-v2/components/left/promotion-input.tsx +17 -17
  58. package/src/checkout-v2/components/right/customer-info-card.tsx +29 -16
  59. package/src/checkout-v2/components/right/status-feedback.tsx +2 -2
  60. package/src/checkout-v2/components/right/submit-button.tsx +2 -0
  61. package/src/checkout-v2/layouts/checkout-layout.tsx +25 -10
  62. package/src/checkout-v2/panels/left/composite-panel.tsx +28 -6
  63. package/src/checkout-v2/panels/right/payment-panel.tsx +32 -5
  64. package/src/checkout-v2/utils/format.ts +2 -0
  65. package/src/checkout-v2/views/error-view.tsx +11 -1
  66. package/src/checkout-v2/views/success-view.tsx +3 -1
  67. package/src/components/over-due-invoice-payment.tsx +6 -3
  68. package/src/components/service-suspended-dialog.tsx +64 -0
  69. package/src/libs/util.ts +7 -0
  70. package/src/locales/en.tsx +4 -0
  71. package/src/locales/zh.tsx +4 -0
  72. package/src/payment/form/index.tsx +20 -0
  73. package/src/payment/index.tsx +26 -4
@@ -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/libs/util.d.ts CHANGED
@@ -194,3 +194,11 @@ export declare function getTokenBalanceLink(method: TPaymentMethod, address: str
194
194
  export declare function isCreditMetered(price: TPrice): boolean;
195
195
  export declare function showStaking(method: TPaymentMethod, currency: TPaymentCurrency, noStake: boolean): boolean;
196
196
  export declare function formatLinkWithLocale(url: string, locale?: string): string;
197
+ export declare function primaryContrastColor(theme: {
198
+ palette: {
199
+ primary: {
200
+ main: string;
201
+ };
202
+ getContrastText: (bg: string) => string;
203
+ };
204
+ }): string;
package/es/libs/util.js CHANGED
@@ -1372,3 +1372,6 @@ export function formatLinkWithLocale(url, locale) {
1372
1372
  return `${url}${separator}locale=${locale}`;
1373
1373
  }
1374
1374
  }
1375
+ export function primaryContrastColor(theme) {
1376
+ return theme.palette.getContrastText(theme.palette.primary.main);
1377
+ }
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,
@@ -54,7 +54,7 @@ function CrossSellCard({
54
54
  fontWeight: 900,
55
55
  letterSpacing: "0.12em",
56
56
  bgcolor: "primary.main",
57
- color: "#fff",
57
+ color: theme => (0, _format.primaryContrastColor)(theme),
58
58
  boxShadow: "0 4px 12px rgba(45,124,243,0.2)",
59
59
  "& .MuiChip-label": {
60
60
  px: 1.5
@@ -236,7 +236,7 @@ function CrossSellCard({
236
236
  transition: "all 0.2s",
237
237
  "&:hover": {
238
238
  bgcolor: "primary.main",
239
- color: "#fff",
239
+ color: theme => (0, _format.primaryContrastColor)(theme),
240
240
  borderColor: "primary.main"
241
241
  },
242
242
  "&:active": {
@@ -246,7 +246,7 @@ function ProductItemCard({
246
246
  fontWeight: 900,
247
247
  letterSpacing: "0.12em",
248
248
  bgcolor: "primary.main",
249
- color: "#fff",
249
+ color: th => (0, _format.primaryContrastColor)(th),
250
250
  boxShadow: "0 4px 12px rgba(45,124,243,0.2)",
251
251
  "& .MuiChip-label": {
252
252
  px: 1.5
@@ -513,7 +513,14 @@ function ProductItemCard({
513
513
  sx: {
514
514
  mt: 1.5
515
515
  },
516
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, {
516
+ children: isRateLoading ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Skeleton, {
517
+ variant: "rounded",
518
+ width: 160,
519
+ height: 22,
520
+ sx: {
521
+ borderRadius: "6px"
522
+ }
523
+ }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, {
517
524
  icon: /* @__PURE__ */(0, _jsxRuntime.jsx)(_LocalOffer.default, {
518
525
  sx: {
519
526
  color: "warning.main",
@@ -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 {};
@@ -11,15 +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,
19
- initialShowInput = false
20
+ currency = null,
21
+ initialShowInput = false,
22
+ isAmountLoading = false
20
23
  }) {
21
24
  const {
22
- t
25
+ t,
26
+ locale
23
27
  } = (0, _context.useLocaleContext)();
24
28
  const [showInput, setShowInput] = (0, _react.useState)(false);
25
29
  const [code, setCode] = (0, _react.useState)("");
@@ -49,22 +53,7 @@ function PromotionInput({
49
53
  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 couponOff = coupon.percent_off > 0 ? t("payment.checkout.coupon.percentage", {
53
- percent: coupon.percent_off
54
- }) : `${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", {
61
- couponOff
62
- });
63
- } else if (coupon.duration === "once") {
64
- description = t("payment.checkout.coupon.terms.once", {
65
- couponOff
66
- });
67
- }
56
+ const description = coupon && currency ? (0, _util.formatCouponTerms)(coupon, currency, locale) : "";
68
57
  return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
69
58
  direction: "row",
70
59
  justifyContent: "space-between",
@@ -116,7 +105,11 @@ function PromotionInput({
116
105
  }
117
106
  })
118
107
  })]
119
- }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
108
+ }), isAmountLoading ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Skeleton, {
109
+ variant: "text",
110
+ width: 80,
111
+ height: 22
112
+ }) : /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
120
113
  sx: {
121
114
  color: "text.primary",
122
115
  fontWeight: 600,
@@ -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
  }
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  module.exports = SubmitButton;
7
7
  var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _material = require("@mui/material");
9
+ var _format = require("../../utils/format");
9
10
  function SubmitButton({
10
11
  canSubmit,
11
12
  isProcessing,
@@ -28,7 +29,8 @@ function SubmitButton({
28
29
  py: 1.5,
29
30
  fontSize: "1.3rem",
30
31
  fontWeight: 600,
31
- textTransform: "none"
32
+ textTransform: "none",
33
+ color: theme => (0, _format.primaryContrastColor)(theme)
32
34
  },
33
35
  children: isProcessing ? processingLabel : label
34
36
  });
@@ -47,7 +47,23 @@ const fadeIn = {
47
47
  md: "fadeIn 0.6s cubic-bezier(0.16, 1, 0.3, 1) 0.15s both"
48
48
  }
49
49
  };
50
- const slideInFromRight = {
50
+ const isSafari = typeof navigator !== "undefined" && /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent);
51
+ const slideInFromRight = isSafari ? {
52
+ "@keyframes panelFadeIn": {
53
+ from: {
54
+ opacity: 0,
55
+ transform: "translateX(24px)"
56
+ },
57
+ to: {
58
+ opacity: 1,
59
+ transform: "none"
60
+ }
61
+ },
62
+ animation: {
63
+ xs: "none",
64
+ md: "panelFadeIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) both"
65
+ }
66
+ } : {
51
67
  "@keyframes slideInRight": {
52
68
  from: {
53
69
  transform: "translateX(100%)"
@@ -117,6 +133,7 @@ function CheckoutLayout({
117
133
  children: [!hideLeft && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
118
134
  sx: {
119
135
  flex: 1,
136
+ minWidth: 0,
120
137
  bgcolor: t => t.palette.mode === "dark" ? "background.default" : "#f8faff",
121
138
  p: {
122
139
  xs: 3,
@@ -205,9 +222,12 @@ function CheckoutLayout({
205
222
  }
206
223
  }), !hideLeft && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
207
224
  sx: {
225
+ flex: {
226
+ xs: "none",
227
+ md: "0 0 50%"
228
+ },
208
229
  width: {
209
- xs: "100%",
210
- md: "50%"
230
+ xs: "100%"
211
231
  },
212
232
  height: {
213
233
  xs: "auto",
@@ -246,9 +266,12 @@ function CheckoutLayout({
246
266
  })
247
267
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
248
268
  sx: {
269
+ flex: hideLeft ? "none" : {
270
+ xs: "none",
271
+ md: "0 0 50%"
272
+ },
249
273
  width: hideLeft ? "100%" : {
250
- xs: "100%",
251
- md: "50%"
274
+ xs: "100%"
252
275
  },
253
276
  height: hideLeft ? "100vh" : {
254
277
  xs: "auto",
@@ -61,6 +61,9 @@ function CompositePanel() {
61
61
  const canUpsell = nonCrossSellItems.length <= 1;
62
62
  const hasTopUpsell = canUpsell && !!upsellPrimaryItem && ["subscription", "setup"].includes(mode);
63
63
  const isUpselled = !!upsellPrimaryItem?.upsell_price;
64
+ const [upsellSwitching, setUpsellSwitching] = (0, _react.useState)(false);
65
+ const [pendingUpsell, setPendingUpsell] = (0, _react.useState)(null);
66
+ const visualIsUpselled = pendingUpsell !== null ? pendingUpsell : isUpselled;
64
67
  const currentInterval = hasTopUpsell ? upsellPrimaryItem.price?.recurring?.interval : null;
65
68
  const upsellInterval = hasTopUpsell ? upsellTarget?.recurring?.interval : null;
66
69
  let upsellSavings = 0;
@@ -88,7 +91,7 @@ function CompositePanel() {
88
91
  const isMultiItem = lineItems.items.length > 1;
89
92
  const activeSx = {
90
93
  bgcolor: "primary.main",
91
- color: "#fff",
94
+ color: theme => (0, _format.primaryContrastColor)(theme),
92
95
  boxShadow: "0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)"
93
96
  };
94
97
  const inactiveSx = {
@@ -277,15 +280,21 @@ function CompositePanel() {
277
280
  },
278
281
  children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
279
282
  onClick: async () => {
280
- if (isUpselled) {
283
+ if (isUpselled && !upsellSwitching) {
284
+ setPendingUpsell(false);
285
+ setUpsellSwitching(true);
281
286
  try {
282
287
  await lineItems.downsell(upsellPrimaryItem.upsell_price?.id || upsellPrimaryItem.price_id);
283
288
  } catch (err) {
289
+ setPendingUpsell(null);
284
290
  _Toast.default.error(err?.response?.data?.error || err?.message || "Failed");
291
+ } finally {
292
+ setUpsellSwitching(false);
293
+ setPendingUpsell(null);
285
294
  }
286
295
  }
287
296
  },
288
- sx: capsuleBtnSx(!isUpselled),
297
+ sx: capsuleBtnSx(!visualIsUpselled),
289
298
  children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
290
299
  component: "span",
291
300
  sx: {
@@ -298,15 +307,21 @@ function CompositePanel() {
298
307
  })
299
308
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
300
309
  onClick: async () => {
301
- if (!isUpselled) {
310
+ if (!isUpselled && !upsellSwitching) {
311
+ setPendingUpsell(true);
312
+ setUpsellSwitching(true);
302
313
  try {
303
314
  await lineItems.upsell(upsellPrimaryItem.price_id, upsellTarget.id);
304
315
  } catch (err) {
316
+ setPendingUpsell(null);
305
317
  _Toast.default.error(err?.response?.data?.error || err?.message || "Failed");
318
+ } finally {
319
+ setUpsellSwitching(false);
320
+ setPendingUpsell(null);
306
321
  }
307
322
  }
308
323
  },
309
- sx: capsuleBtnSx(isUpselled),
324
+ sx: capsuleBtnSx(visualIsUpselled),
310
325
  children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
311
326
  component: "span",
312
327
  sx: {
@@ -561,7 +561,9 @@ function PaymentPanel() {
561
561
  remove: promotion.remove
562
562
  },
563
563
  discounts,
564
- discountAmount: pricing.discount
564
+ discountAmount: pricing.discount,
565
+ currency,
566
+ isAmountLoading
565
567
  })]
566
568
  }), (() => {
567
569
  const totalStr = pricing.total || "0";
@@ -710,7 +712,7 @@ function PaymentPanel() {
710
712
  })]
711
713
  })]
712
714
  });
713
- })(), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
715
+ })(), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Button, {
714
716
  variant: "contained",
715
717
  size: "large",
716
718
  fullWidth: true,
@@ -720,15 +722,51 @@ function PaymentPanel() {
720
722
  size: 20,
721
723
  color: "inherit"
722
724
  }) : null,
723
- endIcon: !isProcessing ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_ArrowForward.default, {}) : void 0,
724
725
  sx: {
725
726
  py: 1.5,
726
727
  fontSize: "1.1rem",
727
728
  fontWeight: 600,
728
729
  textTransform: "none",
729
- borderRadius: "12px"
730
+ borderRadius: "12px",
731
+ color: theme => (0, _format.primaryContrastColor)(theme),
732
+ position: "relative",
733
+ overflow: "hidden",
734
+ "&:hover": {
735
+ bgcolor: "primary.main"
736
+ },
737
+ "&:hover .arrow-icon": {
738
+ transform: "translateX(4px)"
739
+ },
740
+ "&:hover .shine-layer": {
741
+ transform: "translateX(100%)"
742
+ }
730
743
  },
731
- children: isProcessing ? `${t("payment.checkout.processing")}...` : buttonLabel
744
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
745
+ component: "span",
746
+ sx: {
747
+ position: "relative",
748
+ zIndex: 1
749
+ },
750
+ children: isProcessing ? `${t("payment.checkout.processing")}...` : buttonLabel
751
+ }), !isProcessing && /* @__PURE__ */(0, _jsxRuntime.jsx)(_ArrowForward.default, {
752
+ className: "arrow-icon",
753
+ sx: {
754
+ ml: 1,
755
+ position: "relative",
756
+ zIndex: 1,
757
+ transition: "transform 0.2s ease"
758
+ }
759
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
760
+ className: "shine-layer",
761
+ sx: {
762
+ position: "absolute",
763
+ inset: 0,
764
+ background: "linear-gradient(90deg, transparent, rgba(255,255,255,0.12), transparent)",
765
+ transform: "translateX(-100%)",
766
+ transition: "transform 0.7s ease",
767
+ pointerEvents: "none"
768
+ }
769
+ })]
732
770
  }), isMobile && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
733
771
  direction: "row",
734
772
  alignItems: "center",
@@ -818,7 +856,8 @@ function PaymentPanel() {
818
856
  remove: promotion.remove
819
857
  },
820
858
  discounts,
821
- discountAmount: pricing.discount
859
+ discountAmount: pricing.discount,
860
+ currency
822
861
  })]
823
862
  }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
824
863
  sx: {
@@ -828,7 +867,7 @@ function PaymentPanel() {
828
867
  mode,
829
868
  subscription,
830
869
  staking: pricing.staking,
831
- appName: session?.metadata?.app_name || "New Payment Kit"
870
+ appName: (0, _util.getStatementDescriptor)(session?.line_items || [])
832
871
  }), !isMobile && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
833
872
  direction: "row",
834
873
  alignItems: "center",