@blocklet/payment-react 1.18.34 → 1.18.35

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/libs/util.d.ts CHANGED
@@ -120,3 +120,4 @@ export declare function parseMarkedText(text: string): Array<{
120
120
  type: 'text' | 'marked';
121
121
  content: string;
122
122
  }>;
123
+ export declare function getTokenBalanceLink(method: TPaymentMethod, address: string): string;
package/es/libs/util.js CHANGED
@@ -979,3 +979,16 @@ export function parseMarkedText(text) {
979
979
  }
980
980
  return result.filter((p) => p.content !== "");
981
981
  }
982
+ export function getTokenBalanceLink(method, address) {
983
+ if (!method || !address) {
984
+ return "";
985
+ }
986
+ const explorerHost = method?.settings?.[method?.type]?.explorer_host || "";
987
+ if (method.type === "arcblock" && address) {
988
+ return joinURL(explorerHost, "accounts", address, "tokens");
989
+ }
990
+ if (["ethereum", "base"].includes(method.type) && address) {
991
+ return joinURL(explorerHost, "address", address);
992
+ }
993
+ return "";
994
+ }
@@ -1 +1,2 @@
1
1
  export declare function validatePostalCode(postalCode: string, country?: string): boolean;
2
+ export declare function getFieldValidation(fieldName: string, validations?: Record<string, any>, locale?: string): Record<string, any>;
@@ -1,4 +1,5 @@
1
1
  import isPostalCode from "validator/lib/isPostalCode";
2
+ import { t } from "../locales/index.js";
2
3
  const POSTAL_CODE_SUPPORTED_COUNTRIES = [
3
4
  "AD",
4
5
  "AT",
@@ -68,3 +69,16 @@ export function validatePostalCode(postalCode, country) {
68
69
  return false;
69
70
  }
70
71
  }
72
+ export function getFieldValidation(fieldName, validations, locale = "en") {
73
+ if (!validations || !validations[fieldName])
74
+ return {};
75
+ const fieldValidation = validations[fieldName];
76
+ const rules = {};
77
+ if (fieldValidation.pattern) {
78
+ rules.pattern = {
79
+ value: new RegExp(fieldValidation.pattern),
80
+ message: fieldValidation.pattern_message?.[locale] || t("payment.checkout.invalid", locale)
81
+ };
82
+ }
83
+ return rules;
84
+ }
package/es/locales/en.js CHANGED
@@ -232,7 +232,8 @@ export default flat({
232
232
  confirmPrompt: "Please confirm the details before proceeding.",
233
233
  payer: "Account",
234
234
  amount: "Amount",
235
- failed: "Account changed, please pay manually."
235
+ failed: "Account changed, please pay manually.",
236
+ balanceLink: "View Balance"
236
237
  }
237
238
  },
238
239
  customer: {
package/es/locales/zh.js CHANGED
@@ -232,7 +232,8 @@ export default flat({
232
232
  confirmPrompt: "\u8BF7\u786E\u8BA4\u652F\u4ED8\u4FE1\u606F\u65E0\u8BEF\u540E\u7EE7\u7EED\u3002",
233
233
  payer: "\u8D26\u6237\u5730\u5740",
234
234
  amount: "\u652F\u4ED8\u91D1\u989D",
235
- failed: "\u8D26\u6237\u53D1\u751F\u53D8\u5316\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5B8C\u6210\u652F\u4ED8\uFF0C\u8BF7\u624B\u52A8\u652F\u4ED8\u3002"
235
+ failed: "\u8D26\u6237\u53D1\u751F\u53D8\u5316\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5B8C\u6210\u652F\u4ED8\uFF0C\u8BF7\u624B\u52A8\u652F\u4ED8\u3002",
236
+ balanceLink: "\u67E5\u770B\u4F59\u989D"
236
237
  }
237
238
  },
238
239
  customer: {
@@ -3,11 +3,15 @@ type Props = {
3
3
  mode: string;
4
4
  stripe: boolean;
5
5
  sx?: SxProps;
6
+ fieldValidation?: Record<string, any>;
7
+ errorPosition?: 'right' | 'bottom';
6
8
  };
7
- declare function AddressForm({ mode, stripe, sx }: Props): import("react").JSX.Element | null;
9
+ declare function AddressForm({ mode, stripe, sx, fieldValidation, errorPosition }: Props): import("react").JSX.Element | null;
8
10
  declare namespace AddressForm {
9
11
  var defaultProps: {
10
12
  sx: {};
13
+ fieldValidation: {};
14
+ errorPosition: string;
11
15
  };
12
16
  }
13
17
  export default AddressForm;
@@ -4,11 +4,13 @@ import { Fade, FormLabel, InputAdornment, Stack } from "@mui/material";
4
4
  import { Controller, useFormContext, useWatch } from "react-hook-form";
5
5
  import FormInput from "../../components/input.js";
6
6
  import CountrySelect from "../../components/country-select.js";
7
- import { validatePostalCode } from "../../libs/validator.js";
7
+ import { getFieldValidation, validatePostalCode } from "../../libs/validator.js";
8
8
  AddressForm.defaultProps = {
9
- sx: {}
9
+ sx: {},
10
+ fieldValidation: {},
11
+ errorPosition: "right"
10
12
  };
11
- export default function AddressForm({ mode, stripe, sx = {} }) {
13
+ export default function AddressForm({ mode, stripe, sx = {}, fieldValidation, errorPosition }) {
12
14
  const { t } = useLocaleContext();
13
15
  const { control } = useFormContext();
14
16
  const country = useWatch({ control, name: "billing_address.country" });
@@ -19,8 +21,11 @@ export default function AddressForm({ mode, stripe, sx = {} }) {
19
21
  FormInput,
20
22
  {
21
23
  name: "billing_address.line1",
22
- rules: { required: t("payment.checkout.required") },
23
- errorPosition: "right",
24
+ rules: {
25
+ required: t("payment.checkout.required"),
26
+ ...getFieldValidation("billing_address.line1", fieldValidation)
27
+ },
28
+ errorPosition,
24
29
  variant: "outlined",
25
30
  placeholder: t("payment.checkout.billing.line1")
26
31
  }
@@ -30,8 +35,11 @@ export default function AddressForm({ mode, stripe, sx = {} }) {
30
35
  FormInput,
31
36
  {
32
37
  name: "billing_address.city",
33
- rules: { required: t("payment.checkout.required") },
34
- errorPosition: "right",
38
+ rules: {
39
+ required: t("payment.checkout.required"),
40
+ ...getFieldValidation("billing_address.city", fieldValidation)
41
+ },
42
+ errorPosition,
35
43
  variant: "outlined",
36
44
  placeholder: t("payment.checkout.billing.city")
37
45
  }
@@ -41,8 +49,11 @@ export default function AddressForm({ mode, stripe, sx = {} }) {
41
49
  FormInput,
42
50
  {
43
51
  name: "billing_address.state",
44
- rules: { required: t("payment.checkout.required") },
45
- errorPosition: "right",
52
+ rules: {
53
+ required: t("payment.checkout.required"),
54
+ ...getFieldValidation("billing_address.state", fieldValidation)
55
+ },
56
+ errorPosition,
46
57
  variant: "outlined",
47
58
  placeholder: t("payment.checkout.billing.state")
48
59
  }
@@ -57,9 +68,10 @@ export default function AddressForm({ mode, stripe, sx = {} }) {
57
68
  validate: (x) => {
58
69
  const isValid = validatePostalCode(x, country);
59
70
  return isValid ? true : t("payment.checkout.invalid");
60
- }
71
+ },
72
+ ...getFieldValidation("billing_address.postal_code", fieldValidation)
61
73
  },
62
- errorPosition: "right",
74
+ errorPosition,
63
75
  variant: "outlined",
64
76
  placeholder: t("payment.checkout.billing.postal_code"),
65
77
  InputProps: {
@@ -98,9 +110,10 @@ export default function AddressForm({ mode, stripe, sx = {} }) {
98
110
  validate: (x) => {
99
111
  const isValid = validatePostalCode(x, country);
100
112
  return isValid ? true : t("payment.checkout.invalid");
101
- }
113
+ },
114
+ ...getFieldValidation("billing_address.postal_code", fieldValidation)
102
115
  },
103
- errorPosition: "right",
116
+ errorPosition,
104
117
  variant: "outlined",
105
118
  placeholder: t("payment.checkout.billing.postal_code"),
106
119
  wrapperStyle: { height: "40px" },
@@ -2,7 +2,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import "react-international-phone/style.css";
3
3
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
4
4
  import Toast from "@arcblock/ux/lib/Toast";
5
- import { Box, Button, CircularProgress, Divider, Fade, FormLabel, Stack, Typography } from "@mui/material";
5
+ import { Box, Button, CircularProgress, Divider, Fade, FormLabel, Stack, Tooltip, Typography } from "@mui/material";
6
6
  import { useMemoizedFn, useSetState } from "ahooks";
7
7
  import pWaitFor from "p-wait-for";
8
8
  import { useEffect, useMemo, useRef } from "react";
@@ -13,7 +13,7 @@ import isEmail from "validator/es/lib/isEmail";
13
13
  import { fromUnitToToken } from "@ocap/util";
14
14
  import DID from "@arcblock/ux/lib/DID";
15
15
  import isEmpty from "lodash/isEmpty";
16
- import { HelpOutline } from "@mui/icons-material";
16
+ import { HelpOutline, OpenInNew } from "@mui/icons-material";
17
17
  import FormInput from "../../components/input.js";
18
18
  import { usePaymentContext } from "../../contexts/payment.js";
19
19
  import { useSubscription } from "../../hooks/subscription.js";
@@ -24,6 +24,7 @@ import {
24
24
  formatQuantityInventory,
25
25
  getPrefix,
26
26
  getStatementDescriptor,
27
+ getTokenBalanceLink,
27
28
  isCrossOrigin
28
29
  } from "../../libs/util.js";
29
30
  import AddressForm from "./address.js";
@@ -36,6 +37,7 @@ import LoadingButton from "../../components/loading-button.js";
36
37
  import OverdueInvoicePayment from "../../components/over-due-invoice-payment.js";
37
38
  import { saveCurrencyPreference } from "../../libs/currency.js";
38
39
  import ConfirmDialog from "../../components/confirm.js";
40
+ import { getFieldValidation } from "../../libs/validator.js";
39
41
  export const waitForCheckoutComplete = async (sessionId) => {
40
42
  let result;
41
43
  await pWaitFor(
@@ -104,6 +106,7 @@ export default function PaymentForm({
104
106
  const { isMobile } = useMobile();
105
107
  const { session, connect, payable } = usePaymentContext();
106
108
  const subscription = useSubscription("events");
109
+ const formErrorPosition = isMobile ? "bottom" : "right";
107
110
  const {
108
111
  control,
109
112
  getValues,
@@ -456,6 +459,7 @@ export default function PaymentForm({
456
459
  window.removeEventListener("keydown", handleKeyDown);
457
460
  };
458
461
  }, [state.submitting, state.paying, state.stripePaying, quantityInventoryStatus, payable]);
462
+ const balanceLink = getTokenBalanceLink(method, state.fastCheckoutInfo?.payer || "");
459
463
  const FastCheckoutConfirmDialog = state.fastCheckoutInfo && /* @__PURE__ */ jsx(
460
464
  ConfirmDialog,
461
465
  {
@@ -469,7 +473,23 @@ export default function PaymentForm({
469
473
  /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
470
474
  /* @__PURE__ */ jsxs(Stack, { flexDirection: "row", alignItems: "center", justifyContent: "space-between", children: [
471
475
  /* @__PURE__ */ jsx(Typography, { color: "text.primary", sx: { whiteSpace: "nowrap" }, children: t("payment.checkout.fastPay.payer") }),
472
- /* @__PURE__ */ jsx(Typography, { children: /* @__PURE__ */ jsx(DID, { did: state.fastCheckoutInfo.payer || "", compact: true, responsive: false }) })
476
+ /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.5 }, children: [
477
+ /* @__PURE__ */ jsx(DID, { did: state.fastCheckoutInfo.payer || "", compact: true, responsive: false }),
478
+ balanceLink && /* @__PURE__ */ jsx(Tooltip, { title: t("payment.checkout.fastPay.balanceLink"), placement: "top", children: /* @__PURE__ */ jsx(
479
+ OpenInNew,
480
+ {
481
+ sx: {
482
+ color: "text.lighter",
483
+ fontSize: "0.85rem",
484
+ cursor: "pointer",
485
+ "&:hover": { color: "text.primary" }
486
+ },
487
+ onClick: () => {
488
+ window.open(balanceLink, "_blank");
489
+ }
490
+ }
491
+ ) })
492
+ ] })
473
493
  ] }),
474
494
  /* @__PURE__ */ jsxs(Stack, { flexDirection: "row", alignItems: "center", justifyContent: "space-between", children: [
475
495
  /* @__PURE__ */ jsx(Typography, { color: "text.primary", children: t("payment.checkout.fastPay.amount") }),
@@ -609,9 +629,10 @@ export default function PaymentForm({
609
629
  {
610
630
  name: "customer_name",
611
631
  variant: "outlined",
612
- errorPosition: "right",
632
+ errorPosition: formErrorPosition,
613
633
  rules: {
614
- required: t("payment.checkout.required")
634
+ required: t("payment.checkout.required"),
635
+ ...getFieldValidation("customer_name", checkoutSession.metadata?.page_info?.field_validation, locale)
615
636
  }
616
637
  }
617
638
  ),
@@ -621,10 +642,15 @@ export default function PaymentForm({
621
642
  {
622
643
  name: "customer_email",
623
644
  variant: "outlined",
624
- errorPosition: "right",
645
+ errorPosition: formErrorPosition,
625
646
  rules: {
626
647
  required: t("payment.checkout.required"),
627
- validate: (x) => isEmail(x) ? true : t("payment.checkout.invalid")
648
+ validate: (x) => isEmail(x) ? true : t("payment.checkout.invalid"),
649
+ ...getFieldValidation(
650
+ "customer_email",
651
+ checkoutSession.metadata?.page_info?.field_validation,
652
+ locale
653
+ )
628
654
  }
629
655
  }
630
656
  ),
@@ -635,14 +661,19 @@ export default function PaymentForm({
635
661
  {
636
662
  name: "customer_phone",
637
663
  variant: "outlined",
638
- errorPosition: "right",
664
+ errorPosition: formErrorPosition,
639
665
  placeholder: "Phone number",
640
666
  rules: {
641
667
  required: t("payment.checkout.required"),
642
668
  validate: async (x) => {
643
669
  const isValid = await validatePhoneNumber(x);
644
670
  return isValid ? true : t("payment.checkout.invalid");
645
- }
671
+ },
672
+ ...getFieldValidation(
673
+ "customer_phone",
674
+ checkoutSession.metadata?.page_info?.field_validation,
675
+ locale
676
+ )
646
677
  }
647
678
  }
648
679
  )
@@ -652,7 +683,9 @@ export default function PaymentForm({
652
683
  {
653
684
  mode: checkoutSession.billing_address_collection,
654
685
  stripe: method?.type === "stripe",
655
- sx: { marginTop: "0 !important" }
686
+ sx: { marginTop: "0 !important" },
687
+ fieldValidation: checkoutSession.metadata?.page_info?.field_validation,
688
+ errorPosition: formErrorPosition
656
689
  }
657
690
  )
658
691
  ]
@@ -120,3 +120,4 @@ export declare function parseMarkedText(text: string): Array<{
120
120
  type: 'text' | 'marked';
121
121
  content: string;
122
122
  }>;
123
+ export declare function getTokenBalanceLink(method: TPaymentMethod, address: string): string;
package/lib/libs/util.js CHANGED
@@ -44,7 +44,9 @@ exports.getRefundStatusColor = getRefundStatusColor;
44
44
  exports.getStatementDescriptor = getStatementDescriptor;
45
45
  exports.getSubscriptionAction = void 0;
46
46
  exports.getSubscriptionStatusColor = getSubscriptionStatusColor;
47
- exports.getTxLink = exports.getSubscriptionTimeSummary = void 0;
47
+ exports.getSubscriptionTimeSummary = void 0;
48
+ exports.getTokenBalanceLink = getTokenBalanceLink;
49
+ exports.getTxLink = void 0;
48
50
  exports.getUserProfileLink = getUserProfileLink;
49
51
  exports.getWebhookStatusColor = getWebhookStatusColor;
50
52
  exports.getWordBreakStyle = getWordBreakStyle;
@@ -1169,4 +1171,17 @@ function parseMarkedText(text) {
1169
1171
  }
1170
1172
  }
1171
1173
  return result.filter(p => p.content !== "");
1174
+ }
1175
+ function getTokenBalanceLink(method, address) {
1176
+ if (!method || !address) {
1177
+ return "";
1178
+ }
1179
+ const explorerHost = method?.settings?.[method?.type]?.explorer_host || "";
1180
+ if (method.type === "arcblock" && address) {
1181
+ return (0, _ufo.joinURL)(explorerHost, "accounts", address, "tokens");
1182
+ }
1183
+ if (["ethereum", "base"].includes(method.type) && address) {
1184
+ return (0, _ufo.joinURL)(explorerHost, "address", address);
1185
+ }
1186
+ return "";
1172
1187
  }
@@ -1 +1,2 @@
1
1
  export declare function validatePostalCode(postalCode: string, country?: string): boolean;
2
+ export declare function getFieldValidation(fieldName: string, validations?: Record<string, any>, locale?: string): Record<string, any>;
@@ -3,8 +3,10 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ exports.getFieldValidation = getFieldValidation;
6
7
  exports.validatePostalCode = validatePostalCode;
7
8
  var _isPostalCode = _interopRequireDefault(require("validator/lib/isPostalCode"));
9
+ var _locales = require("../locales");
8
10
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
9
11
  const POSTAL_CODE_SUPPORTED_COUNTRIES = ["AD", "AT", "AU", "BE", "BG", "BR", "CA", "CH", "CN", "CZ", "DE", "DK", "DZ", "EE", "ES", "FI", "FR", "GB", "GR", "HR", "HU", "ID", "IE", "IL", "IN", "IR", "IS", "IT", "JP", "KE", "KR", "LI", "LT", "LU", "LV", "MX", "MT", "NL", "NO", "NZ", "PL", "PR", "PT", "RO", "RU", "SA", "SE", "SI", "SK", "TN", "TW", "UA", "US", "ZA", "ZM"];
10
12
  function validatePostalCode(postalCode, country) {
@@ -17,4 +19,16 @@ function validatePostalCode(postalCode, country) {
17
19
  console.error(error);
18
20
  return false;
19
21
  }
22
+ }
23
+ function getFieldValidation(fieldName, validations, locale = "en") {
24
+ if (!validations || !validations[fieldName]) return {};
25
+ const fieldValidation = validations[fieldName];
26
+ const rules = {};
27
+ if (fieldValidation.pattern) {
28
+ rules.pattern = {
29
+ value: new RegExp(fieldValidation.pattern),
30
+ message: fieldValidation.pattern_message?.[locale] || (0, _locales.t)("payment.checkout.invalid", locale)
31
+ };
32
+ }
33
+ return rules;
20
34
  }
package/lib/locales/en.js CHANGED
@@ -239,7 +239,8 @@ module.exports = (0, _flat.default)({
239
239
  confirmPrompt: "Please confirm the details before proceeding.",
240
240
  payer: "Account",
241
241
  amount: "Amount",
242
- failed: "Account changed, please pay manually."
242
+ failed: "Account changed, please pay manually.",
243
+ balanceLink: "View Balance"
243
244
  }
244
245
  },
245
246
  customer: {
package/lib/locales/zh.js CHANGED
@@ -239,7 +239,8 @@ module.exports = (0, _flat.default)({
239
239
  confirmPrompt: "\u8BF7\u786E\u8BA4\u652F\u4ED8\u4FE1\u606F\u65E0\u8BEF\u540E\u7EE7\u7EED\u3002",
240
240
  payer: "\u8D26\u6237\u5730\u5740",
241
241
  amount: "\u652F\u4ED8\u91D1\u989D",
242
- failed: "\u8D26\u6237\u53D1\u751F\u53D8\u5316\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5B8C\u6210\u652F\u4ED8\uFF0C\u8BF7\u624B\u52A8\u652F\u4ED8\u3002"
242
+ failed: "\u8D26\u6237\u53D1\u751F\u53D8\u5316\uFF0C\u65E0\u6CD5\u81EA\u52A8\u5B8C\u6210\u652F\u4ED8\uFF0C\u8BF7\u624B\u52A8\u652F\u4ED8\u3002",
243
+ balanceLink: "\u67E5\u770B\u4F59\u989D"
243
244
  }
244
245
  },
245
246
  customer: {
@@ -3,11 +3,15 @@ type Props = {
3
3
  mode: string;
4
4
  stripe: boolean;
5
5
  sx?: SxProps;
6
+ fieldValidation?: Record<string, any>;
7
+ errorPosition?: 'right' | 'bottom';
6
8
  };
7
- declare function AddressForm({ mode, stripe, sx }: Props): import("react").JSX.Element | null;
9
+ declare function AddressForm({ mode, stripe, sx, fieldValidation, errorPosition }: Props): import("react").JSX.Element | null;
8
10
  declare namespace AddressForm {
9
11
  var defaultProps: {
10
12
  sx: {};
13
+ fieldValidation: {};
14
+ errorPosition: string;
11
15
  };
12
16
  }
13
17
  export default AddressForm;
@@ -13,12 +13,16 @@ var _countrySelect = _interopRequireDefault(require("../../components/country-se
13
13
  var _validator = require("../../libs/validator");
14
14
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
15
  AddressForm.defaultProps = {
16
- sx: {}
16
+ sx: {},
17
+ fieldValidation: {},
18
+ errorPosition: "right"
17
19
  };
18
20
  function AddressForm({
19
21
  mode,
20
22
  stripe,
21
- sx = {}
23
+ sx = {},
24
+ fieldValidation,
25
+ errorPosition
22
26
  }) {
23
27
  const {
24
28
  t
@@ -46,9 +50,10 @@ function AddressForm({
46
50
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_input.default, {
47
51
  name: "billing_address.line1",
48
52
  rules: {
49
- required: t("payment.checkout.required")
53
+ required: t("payment.checkout.required"),
54
+ ...(0, _validator.getFieldValidation)("billing_address.line1", fieldValidation)
50
55
  },
51
- errorPosition: "right",
56
+ errorPosition,
52
57
  variant: "outlined",
53
58
  placeholder: t("payment.checkout.billing.line1")
54
59
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.FormLabel, {
@@ -57,9 +62,10 @@ function AddressForm({
57
62
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_input.default, {
58
63
  name: "billing_address.city",
59
64
  rules: {
60
- required: t("payment.checkout.required")
65
+ required: t("payment.checkout.required"),
66
+ ...(0, _validator.getFieldValidation)("billing_address.city", fieldValidation)
61
67
  },
62
- errorPosition: "right",
68
+ errorPosition,
63
69
  variant: "outlined",
64
70
  placeholder: t("payment.checkout.billing.city")
65
71
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.FormLabel, {
@@ -68,9 +74,10 @@ function AddressForm({
68
74
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_input.default, {
69
75
  name: "billing_address.state",
70
76
  rules: {
71
- required: t("payment.checkout.required")
77
+ required: t("payment.checkout.required"),
78
+ ...(0, _validator.getFieldValidation)("billing_address.state", fieldValidation)
72
79
  },
73
- errorPosition: "right",
80
+ errorPosition,
74
81
  variant: "outlined",
75
82
  placeholder: t("payment.checkout.billing.state")
76
83
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.FormLabel, {
@@ -83,9 +90,10 @@ function AddressForm({
83
90
  validate: x => {
84
91
  const isValid = (0, _validator.validatePostalCode)(x, country);
85
92
  return isValid ? true : t("payment.checkout.invalid");
86
- }
93
+ },
94
+ ...(0, _validator.getFieldValidation)("billing_address.postal_code", fieldValidation)
87
95
  },
88
- errorPosition: "right",
96
+ errorPosition,
89
97
  variant: "outlined",
90
98
  placeholder: t("payment.checkout.billing.postal_code"),
91
99
  InputProps: {
@@ -139,9 +147,10 @@ function AddressForm({
139
147
  validate: x => {
140
148
  const isValid = (0, _validator.validatePostalCode)(x, country);
141
149
  return isValid ? true : t("payment.checkout.invalid");
142
- }
150
+ },
151
+ ...(0, _validator.getFieldValidation)("billing_address.postal_code", fieldValidation)
143
152
  },
144
- errorPosition: "right",
153
+ errorPosition,
145
154
  variant: "outlined",
146
155
  placeholder: t("payment.checkout.billing.postal_code"),
147
156
  wrapperStyle: {
@@ -36,6 +36,7 @@ var _loadingButton = _interopRequireDefault(require("../../components/loading-bu
36
36
  var _overDueInvoicePayment = _interopRequireDefault(require("../../components/over-due-invoice-payment"));
37
37
  var _currency2 = require("../../libs/currency");
38
38
  var _confirm = _interopRequireDefault(require("../../components/confirm"));
39
+ var _validator = require("../../libs/validator");
39
40
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
40
41
  const waitForCheckoutComplete = async sessionId => {
41
42
  let result;
@@ -123,6 +124,7 @@ function PaymentForm({
123
124
  payable
124
125
  } = (0, _payment.usePaymentContext)();
125
126
  const subscription = (0, _subscription.useSubscription)("events");
127
+ const formErrorPosition = isMobile ? "bottom" : "right";
126
128
  const {
127
129
  control,
128
130
  getValues,
@@ -535,6 +537,7 @@ function PaymentForm({
535
537
  window.removeEventListener("keydown", handleKeyDown);
536
538
  };
537
539
  }, [state.submitting, state.paying, state.stripePaying, quantityInventoryStatus, payable]);
540
+ const balanceLink = (0, _util2.getTokenBalanceLink)(method, state.fastCheckoutInfo?.payer || "");
538
541
  const FastCheckoutConfirmDialog = state.fastCheckoutInfo && /* @__PURE__ */(0, _jsxRuntime.jsx)(_confirm.default, {
539
542
  onConfirm: handleFastCheckoutConfirm,
540
543
  onCancel: handleFastCheckoutCancel,
@@ -561,12 +564,33 @@ function PaymentForm({
561
564
  whiteSpace: "nowrap"
562
565
  },
563
566
  children: t("payment.checkout.fastPay.payer")
564
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
565
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_DID.default, {
567
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
568
+ sx: {
569
+ display: "flex",
570
+ alignItems: "center",
571
+ gap: 0.5
572
+ },
573
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_DID.default, {
566
574
  did: state.fastCheckoutInfo.payer || "",
567
575
  compact: true,
568
576
  responsive: false
569
- })
577
+ }), balanceLink && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Tooltip, {
578
+ title: t("payment.checkout.fastPay.balanceLink"),
579
+ placement: "top",
580
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.OpenInNew, {
581
+ sx: {
582
+ color: "text.lighter",
583
+ fontSize: "0.85rem",
584
+ cursor: "pointer",
585
+ "&:hover": {
586
+ color: "text.primary"
587
+ }
588
+ },
589
+ onClick: () => {
590
+ window.open(balanceLink, "_blank");
591
+ }
592
+ })
593
+ })]
570
594
  })]
571
595
  }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
572
596
  flexDirection: "row",
@@ -709,9 +733,10 @@ function PaymentForm({
709
733
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_input.default, {
710
734
  name: "customer_name",
711
735
  variant: "outlined",
712
- errorPosition: "right",
736
+ errorPosition: formErrorPosition,
713
737
  rules: {
714
- required: t("payment.checkout.required")
738
+ required: t("payment.checkout.required"),
739
+ ...(0, _validator.getFieldValidation)("customer_name", checkoutSession.metadata?.page_info?.field_validation, locale)
715
740
  }
716
741
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.FormLabel, {
717
742
  className: "base-label",
@@ -719,10 +744,11 @@ function PaymentForm({
719
744
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_input.default, {
720
745
  name: "customer_email",
721
746
  variant: "outlined",
722
- errorPosition: "right",
747
+ errorPosition: formErrorPosition,
723
748
  rules: {
724
749
  required: t("payment.checkout.required"),
725
- validate: x => (0, _isEmail.default)(x) ? true : t("payment.checkout.invalid")
750
+ validate: x => (0, _isEmail.default)(x) ? true : t("payment.checkout.invalid"),
751
+ ...(0, _validator.getFieldValidation)("customer_email", checkoutSession.metadata?.page_info?.field_validation, locale)
726
752
  }
727
753
  }), checkoutSession.phone_number_collection?.enabled && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
728
754
  children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.FormLabel, {
@@ -731,14 +757,15 @@ function PaymentForm({
731
757
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_phone.default, {
732
758
  name: "customer_phone",
733
759
  variant: "outlined",
734
- errorPosition: "right",
760
+ errorPosition: formErrorPosition,
735
761
  placeholder: "Phone number",
736
762
  rules: {
737
763
  required: t("payment.checkout.required"),
738
764
  validate: async x => {
739
765
  const isValid = await (0, _phoneValidator.validatePhoneNumber)(x);
740
766
  return isValid ? true : t("payment.checkout.invalid");
741
- }
767
+ },
768
+ ...(0, _validator.getFieldValidation)("customer_phone", checkoutSession.metadata?.page_info?.field_validation, locale)
742
769
  }
743
770
  })]
744
771
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_address.default, {
@@ -746,7 +773,9 @@ function PaymentForm({
746
773
  stripe: method?.type === "stripe",
747
774
  sx: {
748
775
  marginTop: "0 !important"
749
- }
776
+ },
777
+ fieldValidation: checkoutSession.metadata?.page_info?.field_validation,
778
+ errorPosition: formErrorPosition
750
779
  })]
751
780
  })]
752
781
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.18.34",
3
+ "version": "1.18.35",
4
4
  "description": "Reusable react components for payment kit v2",
5
5
  "keywords": [
6
6
  "react",
@@ -93,7 +93,7 @@
93
93
  "@babel/core": "^7.25.2",
94
94
  "@babel/preset-env": "^7.25.2",
95
95
  "@babel/preset-react": "^7.24.7",
96
- "@blocklet/payment-types": "1.18.34",
96
+ "@blocklet/payment-types": "1.18.35",
97
97
  "@storybook/addon-essentials": "^7.6.20",
98
98
  "@storybook/addon-interactions": "^7.6.20",
99
99
  "@storybook/addon-links": "^7.6.20",
@@ -124,5 +124,5 @@
124
124
  "vite-plugin-babel": "^1.2.0",
125
125
  "vite-plugin-node-polyfills": "^0.21.0"
126
126
  },
127
- "gitHead": "0ddb7c08956891409919c8027b56b77eba1726d4"
127
+ "gitHead": "802a98b5ca81475f8cd7b9dcbb77fce7240b9788"
128
128
  }
package/src/libs/util.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable no-nested-ternary */
2
2
  /* eslint-disable @typescript-eslint/indent */
3
3
  import type {
4
+ ChainType,
4
5
  PaymentDetails,
5
6
  PriceCurrency,
6
7
  PriceRecurring,
@@ -1259,3 +1260,17 @@ export function parseMarkedText(text: string): Array<{
1259
1260
 
1260
1261
  return result.filter((p) => p.content !== '');
1261
1262
  }
1263
+
1264
+ export function getTokenBalanceLink(method: TPaymentMethod, address: string) {
1265
+ if (!method || !address) {
1266
+ return '';
1267
+ }
1268
+ const explorerHost = (method?.settings?.[method?.type as ChainType] as any)?.explorer_host || '';
1269
+ if (method.type === 'arcblock' && address) {
1270
+ return joinURL(explorerHost, 'accounts', address, 'tokens');
1271
+ }
1272
+ if (['ethereum', 'base'].includes(method.type) && address) {
1273
+ return joinURL(explorerHost, 'address', address);
1274
+ }
1275
+ return '';
1276
+ }
@@ -1,4 +1,5 @@
1
1
  import isPostalCode, { PostalCodeLocale } from 'validator/lib/isPostalCode';
2
+ import { t } from '../locales';
2
3
 
3
4
  const POSTAL_CODE_SUPPORTED_COUNTRIES: PostalCodeLocale[] = [
4
5
  'AD',
@@ -68,3 +69,16 @@ export function validatePostalCode(postalCode: string, country?: string): boolea
68
69
  return false;
69
70
  }
70
71
  }
72
+
73
+ export function getFieldValidation(fieldName: string, validations?: Record<string, any>, locale: string = 'en') {
74
+ if (!validations || !validations[fieldName]) return {};
75
+ const fieldValidation = validations[fieldName];
76
+ const rules: Record<string, any> = {};
77
+ if (fieldValidation.pattern) {
78
+ rules.pattern = {
79
+ value: new RegExp(fieldValidation.pattern),
80
+ message: fieldValidation.pattern_message?.[locale] || t('payment.checkout.invalid', locale),
81
+ };
82
+ }
83
+ return rules;
84
+ }
@@ -241,6 +241,7 @@ export default flat({
241
241
  payer: 'Account',
242
242
  amount: 'Amount',
243
243
  failed: 'Account changed, please pay manually.',
244
+ balanceLink: 'View Balance',
244
245
  },
245
246
  },
246
247
  customer: {
@@ -236,6 +236,7 @@ export default flat({
236
236
  payer: '账户地址',
237
237
  amount: '支付金额',
238
238
  failed: '账户发生变化,无法自动完成支付,请手动支付。',
239
+ balanceLink: '查看余额',
239
240
  },
240
241
  },
241
242
  customer: {
@@ -4,19 +4,23 @@ import type { SxProps } from '@mui/material';
4
4
  import { Controller, useFormContext, useWatch } from 'react-hook-form';
5
5
  import FormInput from '../../components/input';
6
6
  import CountrySelect from '../../components/country-select';
7
- import { validatePostalCode } from '../../libs/validator';
7
+ import { getFieldValidation, validatePostalCode } from '../../libs/validator';
8
8
 
9
9
  type Props = {
10
10
  mode: string;
11
11
  stripe: boolean;
12
12
  sx?: SxProps;
13
+ fieldValidation?: Record<string, any>;
14
+ errorPosition?: 'right' | 'bottom';
13
15
  };
14
16
 
15
17
  AddressForm.defaultProps = {
16
18
  sx: {},
19
+ fieldValidation: {},
20
+ errorPosition: 'right',
17
21
  };
18
22
 
19
- export default function AddressForm({ mode, stripe, sx = {} }: Props) {
23
+ export default function AddressForm({ mode, stripe, sx = {}, fieldValidation, errorPosition }: Props) {
20
24
  const { t } = useLocaleContext();
21
25
  const { control } = useFormContext();
22
26
  const country = useWatch({ control, name: 'billing_address.country' });
@@ -28,24 +32,33 @@ export default function AddressForm({ mode, stripe, sx = {} }: Props) {
28
32
  <FormLabel className="base-label">{t('payment.checkout.billing.line1')}</FormLabel>
29
33
  <FormInput
30
34
  name="billing_address.line1"
31
- rules={{ required: t('payment.checkout.required') }}
32
- errorPosition="right"
35
+ rules={{
36
+ required: t('payment.checkout.required'),
37
+ ...getFieldValidation('billing_address.line1', fieldValidation),
38
+ }}
39
+ errorPosition={errorPosition}
33
40
  variant="outlined"
34
41
  placeholder={t('payment.checkout.billing.line1')}
35
42
  />
36
43
  <FormLabel className="base-label">{t('payment.checkout.billing.city')}</FormLabel>
37
44
  <FormInput
38
45
  name="billing_address.city"
39
- rules={{ required: t('payment.checkout.required') }}
40
- errorPosition="right"
46
+ rules={{
47
+ required: t('payment.checkout.required'),
48
+ ...getFieldValidation('billing_address.city', fieldValidation),
49
+ }}
50
+ errorPosition={errorPosition}
41
51
  variant="outlined"
42
52
  placeholder={t('payment.checkout.billing.city')}
43
53
  />
44
54
  <FormLabel className="base-label">{t('payment.checkout.billing.state')}</FormLabel>
45
55
  <FormInput
46
56
  name="billing_address.state"
47
- rules={{ required: t('payment.checkout.required') }}
48
- errorPosition="right"
57
+ rules={{
58
+ required: t('payment.checkout.required'),
59
+ ...getFieldValidation('billing_address.state', fieldValidation),
60
+ }}
61
+ errorPosition={errorPosition}
49
62
  variant="outlined"
50
63
  placeholder={t('payment.checkout.billing.state')}
51
64
  />
@@ -58,8 +71,9 @@ export default function AddressForm({ mode, stripe, sx = {} }: Props) {
58
71
  const isValid = validatePostalCode(x, country);
59
72
  return isValid ? true : t('payment.checkout.invalid');
60
73
  },
74
+ ...getFieldValidation('billing_address.postal_code', fieldValidation),
61
75
  }}
62
- errorPosition="right"
76
+ errorPosition={errorPosition}
63
77
  variant="outlined"
64
78
  placeholder={t('payment.checkout.billing.postal_code')}
65
79
  InputProps={{
@@ -104,8 +118,9 @@ export default function AddressForm({ mode, stripe, sx = {} }: Props) {
104
118
  const isValid = validatePostalCode(x, country);
105
119
  return isValid ? true : t('payment.checkout.invalid');
106
120
  },
121
+ ...getFieldValidation('billing_address.postal_code', fieldValidation),
107
122
  }}
108
- errorPosition="right"
123
+ errorPosition={errorPosition}
109
124
  variant="outlined"
110
125
  placeholder={t('payment.checkout.billing.postal_code')}
111
126
  wrapperStyle={{ height: '40px' }}
@@ -5,7 +5,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
5
5
  // import { useTheme } from '@arcblock/ux/lib/Theme';
6
6
  import Toast from '@arcblock/ux/lib/Toast';
7
7
  import type { TCheckoutSession, TCustomer, TPaymentIntent, TPaymentMethodExpanded } from '@blocklet/payment-types';
8
- import { Box, Button, CircularProgress, Divider, Fade, FormLabel, Stack, Typography } from '@mui/material';
8
+ import { Box, Button, CircularProgress, Divider, Fade, FormLabel, Stack, Tooltip, Typography } from '@mui/material';
9
9
  import { useMemoizedFn, useSetState } from 'ahooks';
10
10
  import pWaitFor from 'p-wait-for';
11
11
  import { useEffect, useMemo, useRef } from 'react';
@@ -17,7 +17,7 @@ import { fromUnitToToken } from '@ocap/util';
17
17
  import DID from '@arcblock/ux/lib/DID';
18
18
 
19
19
  import isEmpty from 'lodash/isEmpty';
20
- import { HelpOutline } from '@mui/icons-material';
20
+ import { HelpOutline, OpenInNew } from '@mui/icons-material';
21
21
  import FormInput from '../../components/input';
22
22
  import { usePaymentContext } from '../../contexts/payment';
23
23
  import { useSubscription } from '../../hooks/subscription';
@@ -28,6 +28,7 @@ import {
28
28
  formatQuantityInventory,
29
29
  getPrefix,
30
30
  getStatementDescriptor,
31
+ getTokenBalanceLink,
31
32
  isCrossOrigin,
32
33
  } from '../../libs/util';
33
34
  import type { CheckoutCallbacks, CheckoutContext } from '../../types';
@@ -41,6 +42,7 @@ import LoadingButton from '../../components/loading-button';
41
42
  import OverdueInvoicePayment from '../../components/over-due-invoice-payment';
42
43
  import { saveCurrencyPreference } from '../../libs/currency';
43
44
  import ConfirmDialog from '../../components/confirm';
45
+ import { getFieldValidation } from '../../libs/validator';
44
46
 
45
47
  export const waitForCheckoutComplete = async (sessionId: string) => {
46
48
  let result: CheckoutContext;
@@ -175,6 +177,7 @@ export default function PaymentForm({
175
177
  const { isMobile } = useMobile();
176
178
  const { session, connect, payable } = usePaymentContext();
177
179
  const subscription = useSubscription('events');
180
+ const formErrorPosition = isMobile ? 'bottom' : 'right';
178
181
  const {
179
182
  control,
180
183
  getValues,
@@ -596,6 +599,7 @@ export default function PaymentForm({
596
599
  };
597
600
  }, [state.submitting, state.paying, state.stripePaying, quantityInventoryStatus, payable]); // eslint-disable-line react-hooks/exhaustive-deps
598
601
 
602
+ const balanceLink = getTokenBalanceLink(method, state.fastCheckoutInfo?.payer || '');
599
603
  const FastCheckoutConfirmDialog = state.fastCheckoutInfo && (
600
604
  <ConfirmDialog
601
605
  onConfirm={handleFastCheckoutConfirm}
@@ -611,9 +615,24 @@ export default function PaymentForm({
611
615
  <Typography color="text.primary" sx={{ whiteSpace: 'nowrap' }}>
612
616
  {t('payment.checkout.fastPay.payer')}
613
617
  </Typography>
614
- <Typography>
618
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
615
619
  <DID did={state.fastCheckoutInfo.payer || ''} compact responsive={false} />
616
- </Typography>
620
+ {balanceLink && (
621
+ <Tooltip title={t('payment.checkout.fastPay.balanceLink')} placement="top">
622
+ <OpenInNew
623
+ sx={{
624
+ color: 'text.lighter',
625
+ fontSize: '0.85rem',
626
+ cursor: 'pointer',
627
+ '&:hover': { color: 'text.primary' },
628
+ }}
629
+ onClick={() => {
630
+ window.open(balanceLink, '_blank');
631
+ }}
632
+ />
633
+ </Tooltip>
634
+ )}
635
+ </Box>
617
636
  </Stack>
618
637
  <Stack flexDirection="row" alignItems="center" justifyContent="space-between">
619
638
  <Typography color="text.primary">{t('payment.checkout.fastPay.amount')}</Typography>
@@ -748,19 +767,25 @@ export default function PaymentForm({
748
767
  <FormInput
749
768
  name="customer_name"
750
769
  variant="outlined"
751
- errorPosition="right"
770
+ errorPosition={formErrorPosition}
752
771
  rules={{
753
772
  required: t('payment.checkout.required'),
773
+ ...getFieldValidation('customer_name', checkoutSession.metadata?.page_info?.field_validation, locale),
754
774
  }}
755
775
  />
756
776
  <FormLabel className="base-label">{t('payment.checkout.customer.email')}</FormLabel>
757
777
  <FormInput
758
778
  name="customer_email"
759
779
  variant="outlined"
760
- errorPosition="right"
780
+ errorPosition={formErrorPosition}
761
781
  rules={{
762
782
  required: t('payment.checkout.required'),
763
783
  validate: (x) => (isEmail(x) ? true : t('payment.checkout.invalid')),
784
+ ...getFieldValidation(
785
+ 'customer_email',
786
+ checkoutSession.metadata?.page_info?.field_validation,
787
+ locale
788
+ ),
764
789
  }}
765
790
  />
766
791
  {checkoutSession.phone_number_collection?.enabled && (
@@ -769,7 +794,7 @@ export default function PaymentForm({
769
794
  <PhoneInput
770
795
  name="customer_phone"
771
796
  variant="outlined"
772
- errorPosition="right"
797
+ errorPosition={formErrorPosition}
773
798
  placeholder="Phone number"
774
799
  rules={{
775
800
  required: t('payment.checkout.required'),
@@ -777,6 +802,11 @@ export default function PaymentForm({
777
802
  const isValid = await validatePhoneNumber(x);
778
803
  return isValid ? true : t('payment.checkout.invalid');
779
804
  },
805
+ ...getFieldValidation(
806
+ 'customer_phone',
807
+ checkoutSession.metadata?.page_info?.field_validation,
808
+ locale
809
+ ),
780
810
  }}
781
811
  />
782
812
  </>
@@ -785,6 +815,8 @@ export default function PaymentForm({
785
815
  mode={checkoutSession.billing_address_collection as string}
786
816
  stripe={method?.type === 'stripe'}
787
817
  sx={{ marginTop: '0 !important' }}
818
+ fieldValidation={checkoutSession.metadata?.page_info?.field_validation}
819
+ errorPosition={formErrorPosition}
788
820
  />
789
821
  </Stack>
790
822
  )}