payment-kit 1.14.38 → 1.15.0

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.
@@ -44,7 +44,7 @@ export async function ensureStripeProduct(internal: Product, method: PaymentMeth
44
44
  attrs.unit_label = internal.unit_label;
45
45
  }
46
46
  if (internal.statement_descriptor) {
47
- attrs.statement_descriptor_suffix = internal.statement_descriptor;
47
+ attrs.statement_descriptor_suffix = '';
48
48
  }
49
49
 
50
50
  const product = await client.products.create(attrs);
@@ -176,7 +176,7 @@ export async function ensureStripePaymentIntent(
176
176
  enabled: true,
177
177
  allow_redirects: 'never',
178
178
  },
179
- statement_descriptor_suffix: internal.statement_descriptor,
179
+ statement_descriptor_suffix: '',
180
180
  metadata: {
181
181
  appPid: env.appPid,
182
182
  id: internal.id,
@@ -163,13 +163,13 @@ function validateMetadataValue(value: any, helpers: any) {
163
163
  export const MetadataSchema = Joi.alternatives()
164
164
  .try(
165
165
  Joi.object()
166
- .pattern(Joi.string().max(64), Joi.any().custom(validateMetadataValue, 'Custom Validation'))
166
+ .pattern(Joi.string().max(40), Joi.any().custom(validateMetadataValue, 'Custom Validation'))
167
167
  .min(0)
168
168
  .allow(null),
169
169
  Joi.array()
170
170
  .items(
171
171
  Joi.object({
172
- key: Joi.string().max(64).required(),
172
+ key: Joi.string().max(40).required(),
173
173
  value: Joi.any().custom(validateMetadataValue, 'Custom Validation').required(),
174
174
  })
175
175
  )
@@ -21,11 +21,19 @@ const auth = authenticate<Product>({ component: true, roles: ['owner', 'admin']
21
21
  const ProductAndPriceSchema = Joi.object({
22
22
  name: Joi.string().max(64).empty('').optional(),
23
23
  type: Joi.string().valid('service', 'good').empty('').optional(),
24
- description: Joi.string().max(256).empty('').optional(),
24
+ description: Joi.string().max(250).empty('').optional(),
25
25
  images: Joi.any().optional(),
26
26
  metadata: MetadataSchema,
27
- statement_descriptor: Joi.string().max(32).empty('').optional(),
28
- unit_label: Joi.string().max(32).empty('').optional(),
27
+ statement_descriptor: Joi.string()
28
+ .max(22)
29
+ .pattern(/^(?=.*[A-Za-z])[^\u4e00-\u9fa5<>"’\\]*$/)
30
+ .messages({
31
+ 'string.pattern.base':
32
+ 'statement_descriptor should be at least one letter and cannot include Chinese characters and special characters such as <, >、"、’ or \\',
33
+ })
34
+ .empty('')
35
+ .optional(),
36
+ unit_label: Joi.string().max(12).empty('').optional(),
29
37
  nft_factory: Joi.string().max(40).allow(null).empty('').optional(),
30
38
  features: Joi.array()
31
39
  .items(Joi.object({ name: Joi.string().max(64).empty('').optional() }).unknown(true))
@@ -177,13 +177,11 @@ describe('MetadataSchema', () => {
177
177
 
178
178
  it('should invalidate an object with a key longer than 64 characters', () => {
179
179
  const data = {
180
- ['a'.repeat(65)]: 'value1',
180
+ ['a'.repeat(41)]: 'value1',
181
181
  };
182
182
  const { error } = MetadataSchema.validate(data);
183
183
  expect(error).toBeDefined();
184
- expect(error?.details?.[0]?.message).toMatch(
185
- /"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" is not allowed/
186
- );
184
+ expect(error?.details?.[0]?.message).toMatch(/"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" is not allowed/);
187
185
  });
188
186
 
189
187
  it('should invalidate an array with an object missing the key field', () => {
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.38
17
+ version: 1.15.0
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.38",
3
+ "version": "1.15.0",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -43,34 +43,34 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@abtnode/cron": "1.16.30",
46
- "@arcblock/did": "^1.18.132",
46
+ "@arcblock/did": "^1.18.135",
47
47
  "@arcblock/did-auth-storage-nedb": "^1.7.1",
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",
48
+ "@arcblock/did-connect": "^2.10.25",
49
+ "@arcblock/did-util": "^1.18.135",
50
+ "@arcblock/jwt": "^1.18.135",
51
+ "@arcblock/ux": "2.10.24",
52
+ "@arcblock/validator": "^1.18.135",
53
53
  "@blocklet/js-sdk": "1.16.30",
54
54
  "@blocklet/logger": "1.16.30",
55
- "@blocklet/payment-react": "1.14.38",
55
+ "@blocklet/payment-react": "1.15.0",
56
56
  "@blocklet/sdk": "1.16.30",
57
- "@blocklet/ui-react": "^2.10.23",
57
+ "@blocklet/ui-react": "^2.10.25",
58
58
  "@blocklet/uploader": "^0.1.27",
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.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",
64
+ "@ocap/asset": "^1.18.135",
65
+ "@ocap/client": "^1.18.135",
66
+ "@ocap/mcrypto": "^1.18.135",
67
+ "@ocap/util": "^1.18.135",
68
+ "@ocap/wallet": "^1.18.135",
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",
72
72
  "ahooks": "^3.8.0",
73
- "axios": "^1.7.2",
73
+ "axios": "^1.7.5",
74
74
  "body-parser": "^1.20.2",
75
75
  "cls-hooked": "^4.2.2",
76
76
  "cookie-parser": "^1.4.6",
@@ -119,7 +119,7 @@
119
119
  "devDependencies": {
120
120
  "@abtnode/types": "1.16.30",
121
121
  "@arcblock/eslint-config-ts": "^0.3.2",
122
- "@blocklet/payment-types": "1.14.38",
122
+ "@blocklet/payment-types": "1.15.0",
123
123
  "@types/cookie-parser": "^1.4.7",
124
124
  "@types/cors": "^2.8.17",
125
125
  "@types/debug": "^4.1.12",
@@ -139,13 +139,13 @@
139
139
  "npm-run-all": "^4.1.5",
140
140
  "prettier": "^3.3.3",
141
141
  "prettier-plugin-import-sort": "^0.0.7",
142
- "ts-jest": "^29.2.3",
142
+ "ts-jest": "^29.2.5",
143
143
  "ts-node": "^10.9.2",
144
144
  "type-fest": "^4.23.0",
145
145
  "typescript": "^4.9.5",
146
146
  "vite": "^5.3.5",
147
147
  "vite-node": "^2.0.4",
148
- "vite-plugin-blocklet": "^0.9.1",
148
+ "vite-plugin-blocklet": "^0.9.3",
149
149
  "vite-plugin-node-polyfills": "^0.21.0",
150
150
  "vite-plugin-svgr": "^4.2.0",
151
151
  "vite-tsconfig-paths": "^4.3.2",
@@ -161,5 +161,5 @@
161
161
  "parser": "typescript"
162
162
  }
163
163
  },
164
- "gitHead": "f572122a40f30619bd736d8df20e362b6cdab420"
164
+ "gitHead": "a53bb8a9b781e36974899d3fc8a4aad544621a9c"
165
165
  }
@@ -1,6 +1,7 @@
1
1
  import type { TCustomer } from '@blocklet/payment-types';
2
2
  import { Link } from 'react-router-dom';
3
3
 
4
+ import { getCustomerAvatar } from '@blocklet/payment-react';
4
5
  import InfoCard from '../info-card';
5
6
 
6
7
  export default function CustomerLink({ customer, linked }: { customer: TCustomer; linked?: boolean }) {
@@ -11,7 +12,11 @@ export default function CustomerLink({ customer, linked }: { customer: TCustomer
11
12
  return (
12
13
  <Link to={`/admin/customers/${customer.id}`}>
13
14
  <InfoCard
14
- logo={`/.well-known/service/user/avatar/${customer.did}?imageFilter=resize&w=48&h=48`}
15
+ logo={getCustomerAvatar(
16
+ customer?.did,
17
+ customer?.updated_at ? new Date(customer.updated_at).toISOString() : '',
18
+ 48
19
+ )}
15
20
  name={customer.email}
16
21
  description={`${customer.did.slice(0, 6)}...${customer.did.slice(-6)}`}
17
22
  />
@@ -21,7 +26,7 @@ export default function CustomerLink({ customer, linked }: { customer: TCustomer
21
26
 
22
27
  return (
23
28
  <InfoCard
24
- logo={`/.well-known/service/user/avatar/${customer.did}?imageFilter=resize&w=48&h=48`}
29
+ logo={getCustomerAvatar(customer.did, customer.updated_at ? new Date(customer.updated_at).toISOString() : '', 48)}
25
30
  name={customer.email}
26
31
  description={<span style={{ wordBreak: 'break-all' }}>{customer?.did}</span>}
27
32
  />
@@ -1,5 +1,5 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
- import { api, useMobile, usePaymentContext } from '@blocklet/payment-react';
2
+ import { api, getCustomerAvatar, useMobile, usePaymentContext } from '@blocklet/payment-react';
3
3
  import type { TCustomer } from '@blocklet/payment-types';
4
4
  import { Add, Close } from '@mui/icons-material';
5
5
  import { Button, Menu, MenuItem } from '@mui/material';
@@ -300,7 +300,7 @@ function SearchCustomers({ setSearch, search }: Pick<Props, 'setSearch' | 'searc
300
300
  setShow(null);
301
301
  }}>
302
302
  <InfoCard
303
- logo={`/.well-known/service/user/avatar/${x.did}?imageFilter=resize&w=48&h=48`}
303
+ logo={getCustomerAvatar(x?.did, x?.updated_at, 48)}
304
304
  name={x.email}
305
305
  key={x.id}
306
306
  description={`${x.did.slice(0, 6)}...${x.did.slice(-6)}`}
@@ -75,7 +75,7 @@ export default function MetadataForm({
75
75
  required: t('payment.checkout.required'),
76
76
  maxLength: {
77
77
  value: 64,
78
- message: t('common.maxLength', { len: 64 }),
78
+ message: t('common.maxLength', { len: 40 }),
79
79
  },
80
80
  }}
81
81
  placeholder="Key"
@@ -83,7 +83,7 @@ export default function MetadataForm({
83
83
  // @ts-ignore
84
84
  ref={errors?.metadata?.[index]?.key ? errorRef : null}
85
85
  inputProps={{
86
- maxLength: 64,
86
+ maxLength: 40,
87
87
  }}
88
88
  />
89
89
  <FormInput
@@ -104,6 +104,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
104
104
  control,
105
105
  setValue,
106
106
  formState: { errors },
107
+ trigger,
107
108
  } = useFormContext();
108
109
  const getFieldError = (name: string) => {
109
110
  const names = name?.split('.');
@@ -111,7 +112,6 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
111
112
  };
112
113
  const { settings, livemode } = usePaymentContext();
113
114
  const currencies = useFieldArray({ control, name: getFieldName('currency_options') });
114
-
115
115
  const priceLocked = useWatch({ control, name: getFieldName('locked') });
116
116
  const isRecurring = useWatch({ control, name: getFieldName('type') }) === 'recurring';
117
117
  const isMetered = useWatch({ control, name: getFieldName('recurring.usage_type') }) === 'metered';
@@ -138,6 +138,11 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
138
138
  return true;
139
139
  };
140
140
 
141
+ const handleRemoveCurrency = async (index: number) => {
142
+ await currencies.remove(index);
143
+ trigger(getFieldName('recurring.interval_config'));
144
+ };
145
+
141
146
  return (
142
147
  <Root direction="column" alignItems="flex-start" spacing={2}>
143
148
  {isLocked && <Alert severity="info">{t('admin.price.locked')}</Alert>}
@@ -280,7 +285,7 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
280
285
  );
281
286
  }}
282
287
  />
283
- <IconButton size="small" disabled={isLocked} onClick={() => currencies.remove(index)}>
288
+ <IconButton size="small" disabled={isLocked} onClick={() => handleRemoveCurrency(index)}>
284
289
  <DeleteOutlineOutlined color="error" sx={{ opacity: 0.75 }} />
285
290
  </IconButton>
286
291
  </Stack>
@@ -322,6 +327,19 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
322
327
  name={getFieldName('recurring.interval_config')}
323
328
  control={control}
324
329
  disabled={isLocked}
330
+ rules={{
331
+ validate: (val) => {
332
+ const hasStripe = currencies.fields?.some((x: any) => {
333
+ return !!settings.paymentMethods.find(
334
+ (y) => y?.type === 'stripe' && x?.currency_id === y?.default_currency_id
335
+ );
336
+ });
337
+ if (val === 'hour_1' && hasStripe) {
338
+ return t('admin.price.recurring.stripeTip');
339
+ }
340
+ return true;
341
+ },
342
+ }}
325
343
  render={({ field }) => (
326
344
  <Box>
327
345
  <FormLabel sx={{ color: 'text.primary' }}>{t('admin.price.recurring.interval')}</FormLabel>
@@ -333,8 +351,10 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
333
351
  setValue(getFieldName('recurring.interval'), interval);
334
352
  setValue(getFieldName('recurring.interval_count'), +count);
335
353
  setValue(getFieldName('recurring.interval_config'), e.target.value);
354
+ trigger(getFieldName('recurring.interval_config'));
336
355
  }}
337
356
  sx={{ width: INPUT_WIDTH }}
357
+ error={!!get(errors, getFieldName('recurring.interval_config'))}
338
358
  size="small">
339
359
  {!livemode && <MenuItem value="hour_1">{t('common.hourly')}</MenuItem>}
340
360
  <MenuItem value="day_1">{t('common.daily')}</MenuItem>
@@ -345,6 +365,12 @@ export default function PriceForm({ prefix, simple }: PriceFormProps) {
345
365
  <MenuItem value="year_1">{t('common.yearly')}</MenuItem>
346
366
  <MenuItem value="month_2">{t('common.custom')}</MenuItem>
347
367
  </Select>
368
+ {get(errors, getFieldName('recurring.interval_config'))?.message && (
369
+ <Typography color="error" sx={{ fontSize: '0.75rem', mt: 0.5, ml: 1.75 }}>
370
+ {/* @ts-ignore */}
371
+ {get(errors, getFieldName('recurring.interval_config')).message}
372
+ </Typography>
373
+ )}
348
374
  </Box>
349
375
  )}
350
376
  />
@@ -57,8 +57,8 @@ export default function ProductForm(props: Props) {
57
57
  rules={{
58
58
  required: t('admin.product.description.required'),
59
59
  maxLength: {
60
- value: 256,
61
- message: t('common.maxLength', { len: 256 }),
60
+ value: 250,
61
+ message: t('common.maxLength', { len: 250 }),
62
62
  },
63
63
  }}
64
64
  label={t('admin.product.description.label')}
@@ -68,21 +68,27 @@ export default function ProductForm(props: Props) {
68
68
  multiline
69
69
  minRows={2}
70
70
  maxRows={4}
71
- inputProps={{ maxLength: 256 }}
71
+ inputProps={{ maxLength: 250 }}
72
72
  />
73
73
  <Collapse trigger={t('admin.product.additional')}>
74
74
  <Stack spacing={2} alignItems="flex-start">
75
75
  <FormInput
76
76
  name="statement_descriptor"
77
77
  label={t('admin.product.statement_descriptor.label')}
78
- rules={{ maxLength: { value: 32, message: t('common.maxLength', { len: 32 }) } }}
79
- inputProps={{ maxLength: 32 }}
78
+ rules={{
79
+ maxLength: { value: 22, message: t('common.maxLength', { len: 22 }) },
80
+ pattern: {
81
+ value: /^(?=.*[A-Za-z])[^\u4e00-\u9fa5<>"’\\]*$/,
82
+ message: t('common.latinOnly'),
83
+ },
84
+ }}
80
85
  />
81
86
  <FormInput
82
87
  name="unit_label"
83
88
  label={t('admin.product.unit_label.label')}
84
- rules={{ maxLength: { value: 32, message: t('common.maxLength', { len: 32 }) } }}
85
- inputProps={{ maxLength: 32 }}
89
+ rules={{
90
+ maxLength: { value: 12, message: t('common.maxLength', { len: 12 }) },
91
+ }}
86
92
  />
87
93
  {!props.simple && <ProductFeatures />}
88
94
  {!props.simple && <MetadataForm title={t('common.metadata.label')} />}
@@ -18,6 +18,9 @@ export default flat({
18
18
  exit: 'Exit',
19
19
  maxLength: 'Max {len} characters',
20
20
  minLength: 'Min {len} characters',
21
+ invalidCharacters: 'Invalid characters',
22
+ latinOnly:
23
+ 'At least one letter and cannot include Chinese characters and special characters such as <, >、"、’ or \\',
21
24
  loading: 'Loading...',
22
25
  },
23
26
  admin: {
@@ -148,6 +151,7 @@ export default flat({
148
151
  'Metered billing lets you charge customers based on reported usage at the end of each billing period.',
149
152
  aggregate: 'Charge for metered usage by',
150
153
  intervalCountTip: 'Billing interval must be a positive integer',
154
+ stripeTip: 'Stripe requires the billing period to be greater than or equal to 1 day',
151
155
  },
152
156
  currency: {
153
157
  add: 'Add more currencies',
@@ -18,6 +18,8 @@ export default flat({
18
18
  exit: '退出',
19
19
  maxLength: '最多输入{len}个字符',
20
20
  minLength: '最少输入{len}个字符',
21
+ invalidCharacters: '无效字符',
22
+ latinOnly: '至少包含一个字母,并且不能包含中文字符和特殊字符如 <, >、"、’ 或 \\',
21
23
  loading: '加载中...',
22
24
  },
23
25
  admin: {
@@ -144,6 +146,7 @@ export default flat({
144
146
  meteredTip: '计量计费允许您根据每个计费周期结束时的报告的使用情况向客户收费。',
145
147
  aggregate: '按何种方式收费计量使用',
146
148
  intervalCountTip: '计费周期必须是正整数',
149
+ stripeTip: 'Stripe要求计费周期不能为小时',
147
150
  },
148
151
  currency: {
149
152
  add: '添加更多货币',
@@ -2,7 +2,15 @@
2
2
  import DidAddress from '@arcblock/ux/lib/DID';
3
3
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
4
  import Toast from '@arcblock/ux/lib/Toast';
5
- import { api, formatBNStr, formatError, formatTime, useMobile, usePaymentContext } from '@blocklet/payment-react';
5
+ import {
6
+ api,
7
+ formatBNStr,
8
+ formatError,
9
+ formatTime,
10
+ getCustomerAvatar,
11
+ useMobile,
12
+ usePaymentContext,
13
+ } from '@blocklet/payment-react';
6
14
  import type { GroupedBN, TCustomerExpanded, TPaymentMethodExpanded } from '@blocklet/payment-types';
7
15
  import { ArrowBackOutlined } from '@mui/icons-material';
8
16
  import { Alert, Avatar, Box, Button, CircularProgress, Divider, Stack, Typography } from '@mui/material';
@@ -196,7 +204,11 @@ export default function CustomerDetail(props: { id: string }) {
196
204
  <Stack direction="row" alignItems="center" spacing={1}>
197
205
  <Avatar
198
206
  title={data.customer.name}
199
- src={`/.well-known/service/user/avatar/${data?.customer?.did}`}
207
+ src={getCustomerAvatar(
208
+ data.customer?.did,
209
+ data.customer?.updated_at ? new Date(data.customer.updated_at).toISOString() : '',
210
+ 52
211
+ )}
200
212
  variant="square"
201
213
  sx={{ width: 52, height: 52, borderRadius: 'var(--radius-s, 4px)' }}
202
214
  />
@@ -2,7 +2,7 @@
2
2
  import { getDurableData } from '@arcblock/ux/lib/Datatable';
3
3
  import DidAddress from '@arcblock/ux/lib/DID';
4
4
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
5
- import { api, formatTime, Table } from '@blocklet/payment-react';
5
+ import { api, formatTime, getCustomerAvatar, Table } from '@blocklet/payment-react';
6
6
  import type { TCustomer } from '@blocklet/payment-types';
7
7
  import { Avatar, CircularProgress, Stack, Typography } from '@mui/material';
8
8
  import { useEffect, useState } from 'react';
@@ -60,7 +60,11 @@ export default function CustomersList() {
60
60
  <Link to={`/admin/customers/${item.id}`}>
61
61
  <Stack direction="row" alignItems="center" spacing={1}>
62
62
  <Avatar
63
- src={`/.well-known/service/user/avatar/${item.did}?imageFilter=resize&w=48&h=48`}
63
+ src={getCustomerAvatar(
64
+ item?.did,
65
+ item?.updated_at ? new Date(item.updated_at).toISOString() : '',
66
+ 48
67
+ )}
64
68
  variant="square"
65
69
  sx={{ borderRadius: 'var(--radius-m, 8px)' }}
66
70
  />
@@ -5,6 +5,7 @@ import {
5
5
  CustomerInvoiceList,
6
6
  formatBNStr,
7
7
  formatError,
8
+ getCustomerAvatar,
8
9
  getPrefix,
9
10
  TruncatedText,
10
11
  useMobile,
@@ -337,7 +338,7 @@ export default function CustomerHome() {
337
338
  <Box display="flex" alignItems="center" gap={1} flexWrap="wrap" sx={{ mb: 3 }}>
338
339
  <Avatar
339
340
  title={data?.name}
340
- src={`/.well-known/service/user/avatar/${data?.did}`}
341
+ src={getCustomerAvatar(data?.did, data?.updated_at ? new Date(data.updated_at).toISOString() : '', 48)}
341
342
  variant="circular"
342
343
  sx={{ width: 48, height: 48 }}
343
344
  />