payment-kit 1.14.29 → 1.14.31

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 (59) hide show
  1. package/api/src/index.ts +4 -0
  2. package/api/src/libs/api.ts +23 -0
  3. package/api/src/libs/subscription.ts +32 -0
  4. package/api/src/queues/refund.ts +38 -1
  5. package/api/src/queues/subscription.ts +218 -21
  6. package/api/src/routes/checkout-sessions.ts +5 -0
  7. package/api/src/routes/customers.ts +27 -1
  8. package/api/src/routes/invoices.ts +5 -1
  9. package/api/src/routes/payment-intents.ts +17 -2
  10. package/api/src/routes/payment-links.ts +105 -3
  11. package/api/src/routes/payouts.ts +5 -1
  12. package/api/src/routes/prices.ts +19 -3
  13. package/api/src/routes/pricing-table.ts +79 -2
  14. package/api/src/routes/products.ts +24 -8
  15. package/api/src/routes/refunds.ts +7 -4
  16. package/api/src/routes/subscription-items.ts +5 -1
  17. package/api/src/routes/subscriptions.ts +38 -6
  18. package/api/src/routes/webhook-endpoints.ts +5 -1
  19. package/api/src/store/models/subscription.ts +1 -0
  20. package/api/tests/libs/api.spec.ts +72 -1
  21. package/api/third.d.ts +2 -0
  22. package/blocklet.yml +1 -1
  23. package/package.json +19 -18
  24. package/src/components/customer/form.tsx +53 -0
  25. package/src/components/filter-toolbar.tsx +1 -1
  26. package/src/components/invoice/list.tsx +8 -8
  27. package/src/components/invoice/table.tsx +42 -36
  28. package/src/components/metadata/form.tsx +24 -3
  29. package/src/components/payment-intent/actions.tsx +17 -5
  30. package/src/components/payment-link/after-pay.tsx +46 -4
  31. package/src/components/payouts/list.tsx +1 -1
  32. package/src/components/price/form.tsx +14 -2
  33. package/src/components/pricing-table/payment-settings.tsx +45 -4
  34. package/src/components/product/features.tsx +16 -2
  35. package/src/components/product/form.tsx +28 -4
  36. package/src/components/subscription/actions/cancel.tsx +10 -0
  37. package/src/components/subscription/description.tsx +2 -2
  38. package/src/components/subscription/items/index.tsx +3 -2
  39. package/src/components/subscription/portal/cancel.tsx +12 -1
  40. package/src/components/subscription/portal/list.tsx +169 -145
  41. package/src/hooks/loading.ts +28 -0
  42. package/src/locales/en.tsx +6 -1
  43. package/src/locales/zh.tsx +6 -1
  44. package/src/pages/admin/billing/invoices/detail.tsx +17 -2
  45. package/src/pages/admin/billing/subscriptions/detail.tsx +4 -0
  46. package/src/pages/admin/customers/customers/detail.tsx +4 -0
  47. package/src/pages/admin/customers/customers/index.tsx +1 -1
  48. package/src/pages/admin/payments/intents/detail.tsx +4 -0
  49. package/src/pages/admin/payments/payouts/detail.tsx +4 -0
  50. package/src/pages/admin/payments/refunds/detail.tsx +4 -0
  51. package/src/pages/admin/products/links/detail.tsx +4 -0
  52. package/src/pages/admin/products/prices/detail.tsx +4 -0
  53. package/src/pages/admin/products/pricing-tables/detail.tsx +4 -0
  54. package/src/pages/admin/products/products/detail.tsx +4 -0
  55. package/src/pages/checkout/pricing-table.tsx +9 -3
  56. package/src/pages/customer/index.tsx +28 -17
  57. package/src/pages/customer/invoice/detail.tsx +27 -16
  58. package/src/pages/customer/invoice/past-due.tsx +3 -2
  59. package/src/pages/customer/subscription/detail.tsx +4 -0
package/api/third.d.ts CHANGED
@@ -16,6 +16,8 @@ declare module 'sql-where-parser';
16
16
 
17
17
  declare module 'cls-hooked';
18
18
 
19
+ declare module 'express-xss-sanitizer';
20
+
19
21
  namespace Express {
20
22
  interface Request {
21
23
  user?: {
package/blocklet.yml CHANGED
@@ -14,7 +14,7 @@ repository:
14
14
  type: git
15
15
  url: git+https://github.com/blocklet/payment-kit.git
16
16
  specVersion: 1.2.8
17
- version: 1.14.29
17
+ version: 1.14.31
18
18
  logo: logo.png
19
19
  files:
20
20
  - dist
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.14.29",
3
+ "version": "1.14.31",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -43,29 +43,29 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@abtnode/cron": "1.16.28",
46
- "@arcblock/did": "^1.18.128",
46
+ "@arcblock/did": "^1.18.132",
47
47
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
48
- "@arcblock/did-connect": "^2.10.16",
49
- "@arcblock/did-util": "^1.18.128",
50
- "@arcblock/jwt": "^1.18.128",
51
- "@arcblock/ux": "^2.10.16",
52
- "@arcblock/validator": "^1.18.128",
48
+ "@arcblock/did-connect": "^2.10.23",
49
+ "@arcblock/did-util": "^1.18.132",
50
+ "@arcblock/jwt": "^1.18.132",
51
+ "@arcblock/ux": "^2.10.23",
52
+ "@arcblock/validator": "^1.18.132",
53
53
  "@blocklet/js-sdk": "1.16.28",
54
54
  "@blocklet/logger": "1.16.28",
55
- "@blocklet/payment-react": "1.14.29",
55
+ "@blocklet/payment-react": "1.14.31",
56
56
  "@blocklet/sdk": "1.16.28",
57
- "@blocklet/ui-react": "^2.10.16",
58
- "@blocklet/uploader": "^0.1.20",
57
+ "@blocklet/ui-react": "^2.10.23",
58
+ "@blocklet/uploader": "^0.1.23",
59
59
  "@mui/icons-material": "^5.16.6",
60
60
  "@mui/lab": "^5.0.0-alpha.173",
61
61
  "@mui/material": "^5.16.6",
62
62
  "@mui/styles": "^5.16.6",
63
63
  "@mui/system": "^5.16.6",
64
- "@ocap/asset": "^1.18.128",
65
- "@ocap/client": "^1.18.128",
66
- "@ocap/mcrypto": "^1.18.128",
67
- "@ocap/util": "^1.18.128",
68
- "@ocap/wallet": "^1.18.128",
64
+ "@ocap/asset": "^1.18.132",
65
+ "@ocap/client": "^1.18.132",
66
+ "@ocap/mcrypto": "^1.18.132",
67
+ "@ocap/util": "^1.18.132",
68
+ "@ocap/wallet": "^1.18.132",
69
69
  "@react-pdf/renderer": "^3.4.4",
70
70
  "@stripe/react-stripe-js": "^2.7.3",
71
71
  "@stripe/stripe-js": "^2.4.0",
@@ -84,6 +84,7 @@
84
84
  "express": "^4.19.2",
85
85
  "express-async-errors": "^3.1.1",
86
86
  "express-history-api-fallback": "^2.2.1",
87
+ "express-xss-sanitizer": "^1.2.0",
87
88
  "fastq": "^1.17.1",
88
89
  "flat": "^5.0.2",
89
90
  "google-libphonenumber": "^3.2.38",
@@ -118,7 +119,7 @@
118
119
  "devDependencies": {
119
120
  "@abtnode/types": "1.16.28",
120
121
  "@arcblock/eslint-config-ts": "^0.3.2",
121
- "@blocklet/payment-types": "1.14.29",
122
+ "@blocklet/payment-types": "1.14.31",
122
123
  "@types/cookie-parser": "^1.4.7",
123
124
  "@types/cors": "^2.8.17",
124
125
  "@types/debug": "^4.1.12",
@@ -144,7 +145,7 @@
144
145
  "typescript": "^4.9.5",
145
146
  "vite": "^5.3.5",
146
147
  "vite-node": "^2.0.4",
147
- "vite-plugin-blocklet": "^0.8.16",
148
+ "vite-plugin-blocklet": "^0.8.17",
148
149
  "vite-plugin-node-polyfills": "^0.21.0",
149
150
  "vite-plugin-svgr": "^4.2.0",
150
151
  "vite-tsconfig-paths": "^4.3.2",
@@ -160,5 +161,5 @@
160
161
  "parser": "typescript"
161
162
  }
162
163
  },
163
- "gitHead": "104f4189d9ef96c1e332ffa90824ebd3063c989d"
164
+ "gitHead": "bd36f69fcd955be6b1353b520879bbb3c1e3266f"
164
165
  }
@@ -30,6 +30,17 @@ export default function CustomerForm() {
30
30
  errorPosition="right"
31
31
  rules={{
32
32
  required: t('payment.checkout.required'),
33
+ minLength: {
34
+ value: 2,
35
+ message: t('common.minLength', { len: 2 }),
36
+ },
37
+ maxLength: {
38
+ value: 30,
39
+ message: t('common.maxLength', { len: 30 }),
40
+ },
41
+ }}
42
+ inputProps={{
43
+ maxLength: 30,
33
44
  }}
34
45
  label={t('payment.checkout.customer.name')}
35
46
  />
@@ -72,6 +83,15 @@ export default function CustomerForm() {
72
83
  errorPosition="right"
73
84
  label={t('payment.checkout.billing.state')}
74
85
  placeholder={t('payment.checkout.billing.state')}
86
+ inputProps={{
87
+ maxLength: 50,
88
+ }}
89
+ rules={{
90
+ maxLength: {
91
+ value: 50,
92
+ message: t('common.maxLength', { len: 50 }),
93
+ },
94
+ }}
75
95
  />
76
96
  <FormInput
77
97
  name="address.city"
@@ -79,6 +99,15 @@ export default function CustomerForm() {
79
99
  errorPosition="right"
80
100
  label={t('payment.checkout.billing.city')}
81
101
  placeholder={t('payment.checkout.billing.city')}
102
+ inputProps={{
103
+ maxLength: 50,
104
+ }}
105
+ rules={{
106
+ maxLength: {
107
+ value: 50,
108
+ message: t('common.maxLength', { len: 50 }),
109
+ },
110
+ }}
82
111
  />
83
112
  <FormInput
84
113
  name="address.line1"
@@ -86,6 +115,15 @@ export default function CustomerForm() {
86
115
  errorPosition="right"
87
116
  label={t('payment.checkout.billing.line1')}
88
117
  placeholder={t('payment.checkout.billing.line1')}
118
+ inputProps={{
119
+ maxLength: 100,
120
+ }}
121
+ rules={{
122
+ maxLength: {
123
+ value: 100,
124
+ message: t('common.maxLength', { len: 100 }),
125
+ },
126
+ }}
89
127
  />
90
128
  <FormInput
91
129
  name="address.line2"
@@ -93,6 +131,15 @@ export default function CustomerForm() {
93
131
  errorPosition="right"
94
132
  label={t('payment.checkout.billing.line2')}
95
133
  placeholder={t('payment.checkout.billing.line2')}
134
+ inputProps={{
135
+ maxLength: 100,
136
+ }}
137
+ rules={{
138
+ maxLength: {
139
+ value: 100,
140
+ message: t('common.maxLength', { len: 100 }),
141
+ },
142
+ }}
96
143
  />
97
144
  <FormInput
98
145
  name="address.postal_code"
@@ -100,6 +147,12 @@ export default function CustomerForm() {
100
147
  errorPosition="right"
101
148
  label={t('payment.checkout.billing.postal_code')}
102
149
  placeholder={t('payment.checkout.billing.postal_code')}
150
+ rules={{
151
+ maxLength: {
152
+ value: 20,
153
+ message: t('common.maxLength', { len: 20 }),
154
+ },
155
+ }}
103
156
  />
104
157
  </Stack>
105
158
  );
@@ -158,7 +158,7 @@ function SearchStatus({
158
158
 
159
159
  function filterCurrency(methods: any) {
160
160
  const out: any = {};
161
- methods.forEach((m: any) => {
161
+ methods?.forEach((m: any) => {
162
162
  const currencies = m.payment_currencies;
163
163
  currencies.forEach((c: any) => {
164
164
  out[c.symbol] = c.id;
@@ -176,14 +176,14 @@ export default function InvoiceList({
176
176
  options: {
177
177
  customBodyRenderLite: (_: string, index: number) => {
178
178
  const item = data.list[index] as TInvoiceExpanded;
179
- const desc = item?.description || item?.id;
180
- return (
181
- <Link to={`/admin/billing/${item.id}`}>
182
- {desc.startsWith('Subscription ')
183
- ? t(`payment.invoice.reason.${desc.replace('Subscription ', '')}`)
184
- : desc}
185
- </Link>
186
- );
179
+ let desc = item?.description || item?.id;
180
+ if (desc.startsWith('Slash stake')) {
181
+ desc = t('payment.invoice.reason.slashStake');
182
+ }
183
+ if (desc.startsWith('Subscription ')) {
184
+ desc = t(`payment.invoice.reason.${desc.replace('Subscription ', '')}`);
185
+ }
186
+ return <Link to={`/admin/billing/${item.id}`}>{desc}</Link>;
187
187
  },
188
188
  },
189
189
  },
@@ -8,12 +8,14 @@ import { toBN } from '@ocap/util';
8
8
  import { useSetState } from 'ahooks';
9
9
 
10
10
  import { styled } from '@mui/system';
11
+ import { isEmpty } from 'lodash';
11
12
  import LineItemActions from '../subscription/items/actions';
12
13
  import { UsageRecordDialog } from '../subscription/items/usage-records';
13
14
 
14
15
  type Props = {
15
16
  invoice: TInvoiceExpanded;
16
17
  simple?: boolean;
18
+ emptyNodeText?: string;
17
19
  };
18
20
 
19
21
  type InvoiceDetailItem = {
@@ -112,7 +114,7 @@ export function getInvoiceRows(invoice: TInvoiceExpanded) {
112
114
  };
113
115
  }
114
116
 
115
- export default function InvoiceTable({ invoice, simple }: Props) {
117
+ export default function InvoiceTable({ invoice, simple, emptyNodeText }: Props) {
116
118
  const { t, locale } = useLocaleContext();
117
119
  const { detail, summary } = getInvoiceRows(invoice);
118
120
  const [state, setState] = useSetState({
@@ -194,7 +196,7 @@ export default function InvoiceTable({ invoice, simple }: Props) {
194
196
  {
195
197
  label: t('common.actions'),
196
198
  name: '',
197
- width: 40,
199
+ width: 80,
198
200
  options: {
199
201
  customBodyRenderLite: (_: string, index: number) => {
200
202
  const item = detail[index] as InvoiceDetailItem;
@@ -226,41 +228,44 @@ export default function InvoiceTable({ invoice, simple }: Props) {
226
228
  dire
227
229
  locale={locale}
228
230
  mobileTDFlexDirection="row"
229
- emptyNodeText={t('payment.customer.invoice.emptyList')}
231
+ emptyNodeText={emptyNodeText || t('payment.customer.invoice.emptyList')}
230
232
  />
231
- <Stack
232
- className="invoice-summary"
233
- sx={{
234
- display: 'flex',
235
- flexDirection: {
236
- xs: 'column',
237
- md: 'row',
238
- },
239
- justifyContent: 'flex-end',
240
- alignItems: {
241
- xs: 'flex-end',
242
- md: 'center',
243
- },
244
- gap: {
245
- xs: 1,
246
- md: 3,
247
- },
248
- mt: {
249
- xs: 1,
250
- md: 2,
251
- },
252
- }}>
253
- {summary.map((line) => (
254
- <Box key={line.key}>
255
- <Typography component="span" variant="body1" color="text.secondary">
256
- {t(line.key)}:&nbsp;
257
- </Typography>
258
- <Typography component="span" variant="body1" color="text.primary">
259
- {line.value}
260
- </Typography>
261
- </Box>
262
- ))}
263
- </Stack>
233
+ {!isEmpty(detail) && (
234
+ <Stack
235
+ className="invoice-summary"
236
+ sx={{
237
+ display: 'flex',
238
+ flexDirection: {
239
+ xs: 'column',
240
+ md: 'row',
241
+ },
242
+ justifyContent: 'flex-end',
243
+ alignItems: {
244
+ xs: 'flex-end',
245
+ md: 'center',
246
+ },
247
+ gap: {
248
+ xs: 1,
249
+ md: 3,
250
+ },
251
+ mt: {
252
+ xs: 1,
253
+ md: 2,
254
+ },
255
+ }}>
256
+ {summary.map((line) => (
257
+ <Box key={line.key}>
258
+ <Typography component="span" variant="body1" color="text.secondary">
259
+ {t(line.key)}:&nbsp;
260
+ </Typography>
261
+ <Typography component="span" variant="body1" color="text.primary">
262
+ {line.value}
263
+ </Typography>
264
+ </Box>
265
+ ))}
266
+ </Stack>
267
+ )}
268
+
264
269
  {state.subscriptionId && state.subscriptionItemId && (
265
270
  <UsageRecordDialog
266
271
  subscriptionId={state.subscriptionId}
@@ -276,6 +281,7 @@ export default function InvoiceTable({ invoice, simple }: Props) {
276
281
 
277
282
  InvoiceTable.defaultProps = {
278
283
  simple: false,
284
+ emptyNodeText: '',
279
285
  };
280
286
 
281
287
  const Root = styled(Box)`
@@ -42,7 +42,7 @@ export default function MetadataForm({
42
42
  }, [errors.metadata, errorRef]);
43
43
 
44
44
  return (
45
- <Box sx={{ width: 1 }}>
45
+ <Box sx={{ width: 1, height: '100%', display: 'flex', flexDirection: 'column' }}>
46
46
  {!!title && <Typography>{title}</Typography>}
47
47
  <Stack
48
48
  sx={{
@@ -55,6 +55,7 @@ export default function MetadataForm({
55
55
  pb: 1.5,
56
56
  mr: -1.5,
57
57
  pr: 1.5,
58
+ flex: 1,
58
59
  }}>
59
60
  {metadata.fields.map((meta, index) => (
60
61
  <Stack
@@ -67,23 +68,43 @@ export default function MetadataForm({
67
68
  <Stack direction="row" spacing={2} sx={{ flex: 1 }}>
68
69
  <FormInput
69
70
  sx={{ flex: 1 }}
71
+ errorPosition="right"
70
72
  size="small"
71
73
  name={`metadata.${index}.key`}
72
- rules={{ required: t('payment.checkout.required') }}
74
+ rules={{
75
+ required: t('payment.checkout.required'),
76
+ maxLength: {
77
+ value: 64,
78
+ message: t('common.maxLength', { len: 64 }),
79
+ },
80
+ }}
73
81
  placeholder="Key"
74
82
  label="Key"
75
83
  // @ts-ignore
76
84
  ref={errors?.metadata?.[index]?.key ? errorRef : null}
85
+ inputProps={{
86
+ maxLength: 64,
87
+ }}
77
88
  />
78
89
  <FormInput
79
90
  sx={{ flex: 2 }}
80
91
  size="small"
92
+ errorPosition="right"
81
93
  name={`metadata.${index}.value`}
82
94
  placeholder="Value"
83
- rules={{ required: t('payment.checkout.required') }}
95
+ rules={{
96
+ required: t('payment.checkout.required'),
97
+ maxLength: {
98
+ value: 256,
99
+ message: t('common.maxLength', { len: 256 }),
100
+ },
101
+ }}
84
102
  label="Value"
85
103
  // @ts-ignore
86
104
  ref={errors?.metadata?.[index]?.value ? errorRef : null}
105
+ inputProps={{
106
+ maxLength: 256,
107
+ }}
87
108
  />
88
109
  </Stack>
89
110
  <IconButton
@@ -155,6 +155,9 @@ function RefundForm({ data, refundMaxAmount }: { data: TPaymentIntentExpanded; r
155
155
  placeholder={t('admin.paymentIntent.refundForm.description')}
156
156
  error={!!(errors as any)?.refund?.description}
157
157
  helperText={(errors as any)?.refund?.description?.message}
158
+ inputProps={{
159
+ maxLength: 200,
160
+ }}
158
161
  />
159
162
  )}
160
163
  />
@@ -171,12 +174,21 @@ export function PaymentIntentActionsInner({ data, variant, onChange }: Props) {
171
174
  loading: false,
172
175
  });
173
176
  const [refundMaxAmount, setRefundMaxAmount] = useState('0');
174
- const { runAsync: runRefundAmountAsync } = useRequest(() => fetchRefundData(data.id), {
175
- onSuccess: (res) => {
176
- const amount = formatBNStr(res?.amount, data.paymentCurrency.decimal);
177
- setRefundMaxAmount(amount);
177
+ const isSlash = data.payment_details?.arcblock?.type === 'slash' && data.paymentMethod?.type === 'arcblock';
178
+ const { runAsync: runRefundAmountAsync } = useRequest(
179
+ () => {
180
+ if (isSlash) {
181
+ return Promise.resolve({ amount: '0' });
182
+ }
183
+ return fetchRefundData(data.id);
178
184
  },
179
- });
185
+ {
186
+ onSuccess: (res) => {
187
+ const amount = formatBNStr(res?.amount, data.paymentCurrency.decimal);
188
+ setRefundMaxAmount(amount);
189
+ },
190
+ }
191
+ );
180
192
 
181
193
  const onRefund = async () => {
182
194
  const { refund } = getValues();
@@ -1,11 +1,17 @@
1
1
  /* eslint-disable no-nested-ternary */
2
2
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
3
  import { Checkbox, FormControlLabel, Stack, TextField, Typography } from '@mui/material';
4
+ import { get } from 'lodash';
4
5
  import { Controller, useFormContext, useWatch } from 'react-hook-form';
5
6
 
6
7
  export default function AfterPay() {
7
8
  const { t } = useLocaleContext();
8
- const { control, setValue, getValues } = useFormContext();
9
+ const {
10
+ control,
11
+ setValue,
12
+ getValues,
13
+ formState: { errors },
14
+ } = useFormContext();
9
15
  const type = useWatch({ control, name: 'after_completion.type' });
10
16
  const nftMintEnabled = useWatch({ control, name: 'nft_mint_settings.enabled' });
11
17
 
@@ -34,13 +40,23 @@ export default function AfterPay() {
34
40
  <Controller
35
41
  name="after_completion.hosted_confirmation.custom_message"
36
42
  control={control}
43
+ rules={{
44
+ maxLength: {
45
+ value: 200,
46
+ message: t('common.maxLength', { len: 200 }),
47
+ },
48
+ }}
37
49
  render={({ field }) => (
38
50
  <TextField
39
51
  {...field}
40
52
  placeholder={t('admin.paymentLink.customMessage')}
41
- helperText={t('admin.paymentLink.customMessageTip')}
53
+ helperText={get(errors, field.name)?.message || t('admin.paymentLink.customMessageTip')}
54
+ error={!!get(errors, field.name)}
42
55
  fullWidth
43
56
  size="small"
57
+ inputProps={{
58
+ maxLength: 200,
59
+ }}
44
60
  />
45
61
  )}
46
62
  />
@@ -65,8 +81,21 @@ export default function AfterPay() {
65
81
  <Controller
66
82
  name="after_completion.redirect.url"
67
83
  control={control}
84
+ rules={{
85
+ maxLength: {
86
+ value: 2048,
87
+ message: t('common.maxLength', { len: 2048 }),
88
+ },
89
+ }}
68
90
  render={({ field }) => (
69
- <TextField placeholder="Redirect customers to your site" {...field} fullWidth size="small" />
91
+ <TextField
92
+ placeholder="Redirect customers to your site"
93
+ {...field}
94
+ fullWidth
95
+ size="small"
96
+ error={!!get(errors, field.name)}
97
+ helperText={get(errors, field.name)?.message as string}
98
+ />
70
99
  )}
71
100
  />
72
101
  )}
@@ -90,8 +119,21 @@ export default function AfterPay() {
90
119
  <Controller
91
120
  name="nft_mint_settings.factory"
92
121
  control={control}
122
+ rules={{
123
+ maxLength: {
124
+ value: 40,
125
+ message: t('common.maxLength', { len: 40 }),
126
+ },
127
+ }}
93
128
  render={({ field }) => (
94
- <TextField {...field} placeholder={t('admin.paymentLink.mintNftFrom')} fullWidth size="small" />
129
+ <TextField
130
+ {...field}
131
+ placeholder={t('admin.paymentLink.mintNftFrom')}
132
+ fullWidth
133
+ size="small"
134
+ error={!!get(errors, field.name)}
135
+ helperText={get(errors, field.name)?.message as string}
136
+ />
95
137
  )}
96
138
  />
97
139
  )}
@@ -159,7 +159,7 @@ export default function PayoutList({ customer_id, payment_intent_id, status, fea
159
159
  sort: true,
160
160
  customBodyRenderLite: (_: string, index: number) => {
161
161
  const item = data.list[index] as TPayoutExpanded;
162
- return <Link to={`/admin/payments/${item.id}`}>{formatTime(item.updated_at)}</Link>;
162
+ return <Link to={`/admin/payments/${item.id}`}>{formatTime(item.created_at)}</Link>;
163
163
  },
164
164
  },
165
165
  },
@@ -482,20 +482,32 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
482
482
  <Controller
483
483
  name={getFieldName('nickname')}
484
484
  control={control}
485
+ rules={{
486
+ maxLength: {
487
+ value: 64,
488
+ message: t('common.maxLength', { len: 64 }),
489
+ },
490
+ }}
485
491
  render={({ field }) => (
486
492
  <Box>
487
493
  <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.nickname.label')}</FormLabel>
488
- <TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} />
494
+ <TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} inputProps={{ maxLength: 64 }} />
489
495
  </Box>
490
496
  )}
491
497
  />
492
498
  <Controller
493
499
  name={getFieldName('lookup_key')}
494
500
  control={control}
501
+ rules={{
502
+ maxLength: {
503
+ value: 64,
504
+ message: t('common.maxLength', { len: 64 }),
505
+ },
506
+ }}
495
507
  render={({ field }) => (
496
508
  <Box>
497
509
  <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.lookup_key.label')}</FormLabel>
498
- <TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} />
510
+ <TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} inputProps={{ maxLength: 64 }} />
499
511
  </Box>
500
512
  )}
501
513
  />
@@ -15,7 +15,12 @@ export function PricePaymentSettings({ index }: { index: number }) {
15
15
  const getFieldName = (name: string) => `items.${index}.${name}`;
16
16
 
17
17
  const { t } = useLocaleContext();
18
- const { control, setValue, getValues } = useFormContext();
18
+ const {
19
+ control,
20
+ setValue,
21
+ getValues,
22
+ formState: { errors },
23
+ } = useFormContext();
19
24
  const type = useWatch({ control, name: getFieldName('after_completion.type') });
20
25
  const nftMintEnabled = useWatch({ control, name: 'nft_mint_settings.enabled' });
21
26
 
@@ -107,13 +112,23 @@ export function PricePaymentSettings({ index }: { index: number }) {
107
112
  <Controller
108
113
  name={getFieldName('after_completion.hosted_confirmation.custom_message')}
109
114
  control={control}
115
+ rules={{
116
+ maxLength: {
117
+ value: 200,
118
+ message: t('common.maxLength', { len: 200 }),
119
+ },
120
+ }}
110
121
  render={({ field }) => (
111
122
  <TextField
112
123
  {...field}
113
124
  placeholder={t('admin.paymentLink.customMessage')}
114
- helperText={t('admin.paymentLink.customMessageTip')}
115
125
  fullWidth
116
126
  size="small"
127
+ error={!!get(errors, field.name)}
128
+ helperText={get(errors, field.name)?.message || t('admin.paymentLink.customMessageTip')}
129
+ inputProps={{
130
+ maxLength: 200,
131
+ }}
117
132
  />
118
133
  )}
119
134
  />
@@ -138,8 +153,21 @@ export function PricePaymentSettings({ index }: { index: number }) {
138
153
  <Controller
139
154
  name={getFieldName('after_completion.redirect.url')}
140
155
  control={control}
156
+ rules={{
157
+ maxLength: {
158
+ value: 2048,
159
+ message: t('common.maxLength', { len: 2048 }),
160
+ },
161
+ }}
141
162
  render={({ field }) => (
142
- <TextField placeholder="Redirect customers to your site" {...field} fullWidth size="small" />
163
+ <TextField
164
+ placeholder="Redirect customers to your site"
165
+ {...field}
166
+ fullWidth
167
+ size="small"
168
+ error={!!get(errors, field.name)}
169
+ helperText={get(errors, field.name)?.message as string}
170
+ />
143
171
  )}
144
172
  />
145
173
  )}
@@ -163,8 +191,21 @@ export function PricePaymentSettings({ index }: { index: number }) {
163
191
  <Controller
164
192
  name={getFieldName('nft_mint_settings.factory')}
165
193
  control={control}
194
+ rules={{
195
+ maxLength: {
196
+ value: 40,
197
+ message: t('common.maxLength', { len: 40 }),
198
+ },
199
+ }}
166
200
  render={({ field }) => (
167
- <TextField {...field} placeholder={t('admin.paymentLink.mintNftFrom')} fullWidth size="small" />
201
+ <TextField
202
+ {...field}
203
+ placeholder={t('admin.paymentLink.mintNftFrom')}
204
+ fullWidth
205
+ size="small"
206
+ error={!!get(errors, field.name)}
207
+ helperText={get(errors, field.name)?.message as string}
208
+ />
168
209
  )}
169
210
  />
170
211
  )}