payment-kit 1.17.5 → 1.17.6

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.
@@ -78,7 +78,7 @@ export async function checkCurrencySupportRecurring(currencyIds: string[] | stri
78
78
  include: [{ model: PaymentMethod, as: 'payment_method' }],
79
79
  })) as (PaymentCurrency & { payment_method: PaymentMethod })[];
80
80
  const notSupportCurrencies = currencies.filter(
81
- (c) => EVM_CHAIN_TYPES.includes(c.payment_method?.type) && c.symbol === 'ETH'
81
+ (c) => EVM_CHAIN_TYPES.includes(c.payment_method?.type) && c.payment_method?.default_currency_id === c.id
82
82
  );
83
83
  return {
84
84
  notSupportCurrencies,
@@ -2,6 +2,7 @@ import { fromTokenToUnit } from '@ocap/util';
2
2
  import { Router } from 'express';
3
3
  import { InferAttributes, Op, WhereOptions } from 'sequelize';
4
4
 
5
+ import Joi from 'joi';
5
6
  import { fetchErc20Meta } from '../integrations/ethereum/token';
6
7
  import logger from '../libs/logger';
7
8
  import { authenticate } from '../libs/security';
@@ -151,4 +152,63 @@ router.get('/:id', auth, async (req, res) => {
151
152
  }
152
153
  });
153
154
 
155
+ const updateCurrencySchema = Joi.object({
156
+ name: Joi.string().empty('').optional(),
157
+ description: Joi.string().empty('').optional(),
158
+ logo: Joi.string().empty('').optional(),
159
+ }).unknown(true);
160
+ router.put('/:id', auth, async (req, res) => {
161
+ const { id } = req.params;
162
+ const raw: Partial<TPaymentCurrency> = req.body;
163
+
164
+ const { error } = updateCurrencySchema.validate(raw);
165
+ if (error) {
166
+ return res.status(400).json({ error: error.message });
167
+ }
168
+
169
+ const currency = await PaymentCurrency.findByPk(id);
170
+ if (!currency) {
171
+ return res.status(404).json({ error: 'Payment currency not found' });
172
+ }
173
+ if (raw.contract && raw.contract !== currency.contract) {
174
+ return res.status(400).json({ error: 'contract cannot be updated' });
175
+ }
176
+
177
+ const method = await PaymentMethod.findByPk(currency.payment_method_id);
178
+ if (!method) {
179
+ return res.status(400).json({ error: 'Payment method not found' });
180
+ }
181
+
182
+ const updatedCurrency = await currency.update({
183
+ name: raw.name || currency.name,
184
+ description: raw.description || currency.description,
185
+ logo: raw.logo || method.logo,
186
+ });
187
+ return res.json(updatedCurrency);
188
+ });
189
+
190
+ router.delete('/:id', auth, async (req, res) => {
191
+ const { id } = req.params;
192
+
193
+ const currency = await PaymentCurrency.findByPk(id);
194
+ if (!currency) {
195
+ return res.status(404).json({ error: 'Payment currency not found' });
196
+ }
197
+ const isLocked = await currency.isLocked();
198
+ if (isLocked) {
199
+ return res.status(400).json({ error: 'Can not delete locked payment currency' });
200
+ }
201
+ const isUsed = await currency.isUsed();
202
+ if (isUsed) {
203
+ return res.status(400).json({ error: 'Can not delete payment currency used by other resources' });
204
+ }
205
+ try {
206
+ await currency.destroy();
207
+ return res.status(200).end();
208
+ } catch (err) {
209
+ logger.error('delete payment currency error', err);
210
+ return res.status(400).json({ error: 'Delete payment currency failed' });
211
+ }
212
+ });
213
+
154
214
  export default router;
@@ -68,7 +68,7 @@ router.post('/', auth, async (req, res) => {
68
68
  const currency = await PaymentCurrency.create({
69
69
  livemode: method.livemode,
70
70
  active: method.active,
71
- locked: false,
71
+ locked: true,
72
72
  is_base_currency: false,
73
73
  payment_method_id: method.id,
74
74
 
@@ -144,7 +144,7 @@ router.post('/', auth, async (req, res) => {
144
144
  const currency = await PaymentCurrency.create({
145
145
  livemode: method.livemode,
146
146
  active: method.active,
147
- locked: false,
147
+ locked: true,
148
148
  is_base_currency: false,
149
149
  payment_method_id: method.id,
150
150
 
@@ -7,6 +7,7 @@ import {
7
7
  InferCreationAttributes,
8
8
  Model,
9
9
  Op,
10
+ QueryTypes,
10
11
  } from 'sequelize';
11
12
 
12
13
  import { createIdGenerator } from '../../libs/util';
@@ -145,6 +146,36 @@ export class PaymentCurrency extends Model<InferAttributes<PaymentCurrency>, Inf
145
146
  ...options,
146
147
  });
147
148
  }
149
+
150
+ public async isLocked(): Promise<boolean> {
151
+ const { PaymentMethod } = this.sequelize.models;
152
+ const method = (await PaymentMethod!.findByPk(this.payment_method_id)) as any;
153
+ return this.locked || method?.default_currency_id === this.id;
154
+ }
155
+
156
+ public async isUsed(): Promise<boolean> {
157
+ const { Price } = this.sequelize.models;
158
+ const price = await Price!.findOne({
159
+ where: {
160
+ currency_id: this.id,
161
+ },
162
+ });
163
+ if (price) {
164
+ return true;
165
+ }
166
+ // @ts-ignore
167
+ const [{ count }] = await this.sequelize.query(
168
+ `SELECT count(p.id) AS count
169
+ FROM prices AS p
170
+ JOIN json_each(p.currency_options) AS option
171
+ ON json_extract(option.value, '$.currency_id') = ?`,
172
+ {
173
+ replacements: [this.id],
174
+ type: QueryTypes.SELECT,
175
+ }
176
+ );
177
+ return count > 0;
178
+ }
148
179
  }
149
180
 
150
181
  export type TPaymentCurrency = InferAttributes<PaymentCurrency>;
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.17.5
17
+ version: 1.17.6
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.17.5",
3
+ "version": "1.17.6",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "eject": "vite eject",
@@ -53,7 +53,7 @@
53
53
  "@arcblock/validator": "^1.19.3",
54
54
  "@blocklet/js-sdk": "^1.16.37",
55
55
  "@blocklet/logger": "^1.16.37",
56
- "@blocklet/payment-react": "1.17.5",
56
+ "@blocklet/payment-react": "1.17.6",
57
57
  "@blocklet/sdk": "^1.16.37",
58
58
  "@blocklet/ui-react": "^2.11.27",
59
59
  "@blocklet/uploader": "^0.1.64",
@@ -120,7 +120,7 @@
120
120
  "devDependencies": {
121
121
  "@abtnode/types": "^1.16.37",
122
122
  "@arcblock/eslint-config-ts": "^0.3.3",
123
- "@blocklet/payment-types": "1.17.5",
123
+ "@blocklet/payment-types": "1.17.6",
124
124
  "@types/cookie-parser": "^1.4.7",
125
125
  "@types/cors": "^2.8.17",
126
126
  "@types/debug": "^4.1.12",
@@ -166,5 +166,5 @@
166
166
  "parser": "typescript"
167
167
  }
168
168
  },
169
- "gitHead": "7713be8272f1056796820a9d52f742ed63899900"
169
+ "gitHead": "50e93ab4340be53c9d9bd18718c6db26048578c5"
170
170
  }
@@ -12,7 +12,7 @@ import { dispatch } from 'use-bus';
12
12
  import DrawerForm from '../drawer-form';
13
13
  import PaymentCurrencyForm from './form';
14
14
 
15
- export default function PaymentCurrencyAdd({ method, onClose }: { method: TPaymentMethod; onClose: Function }) {
15
+ export default function PaymentCurrencyAdd({ method, onClose }: { method: TPaymentMethod; onClose: () => void }) {
16
16
  const { t } = useLocaleContext();
17
17
  const [state, setState] = useSetState({ loading: false });
18
18
 
@@ -0,0 +1,73 @@
1
+ /* eslint-disable no-nested-ternary */
2
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
3
+ import Toast from '@arcblock/ux/lib/Toast';
4
+ import { api, formatError } from '@blocklet/payment-react';
5
+ import type { TPaymentCurrency, TPaymentMethod } from '@blocklet/payment-types';
6
+ import { AddOutlined } from '@mui/icons-material';
7
+ import { Button, CircularProgress } from '@mui/material';
8
+ import { useSetState } from 'ahooks';
9
+ import { FormProvider, useForm } from 'react-hook-form';
10
+ import { dispatch } from 'use-bus';
11
+
12
+ import DrawerForm from '../drawer-form';
13
+ import PaymentCurrencyForm from './form';
14
+
15
+ export default function PaymentCurrencyEdit({
16
+ method,
17
+ onClose,
18
+ value,
19
+ }: {
20
+ method: TPaymentMethod;
21
+ onClose: () => void;
22
+ value: TPaymentCurrency;
23
+ }) {
24
+ const { t } = useLocaleContext();
25
+ const [state, setState] = useSetState({ loading: false });
26
+
27
+ const methods = useForm<TPaymentCurrency>({
28
+ defaultValues: {
29
+ payment_method_id: method.id,
30
+ name: value?.name,
31
+ description: value?.description,
32
+ logo: value?.logo,
33
+ contract: value?.contract,
34
+ },
35
+ });
36
+ const { handleSubmit } = methods;
37
+
38
+ const onSubmit = (data: TPaymentCurrency) => {
39
+ setState({ loading: true });
40
+ api
41
+ .put(`/api/payment-currencies/${value.id}`, data)
42
+ .then(() => {
43
+ setState({ loading: false });
44
+ Toast.success(t('admin.paymentCurrency.saved'));
45
+ methods.reset();
46
+ dispatch('drawer.submitted');
47
+ dispatch('paymentCurrency.updated');
48
+ })
49
+ .catch((err) => {
50
+ setState({ loading: false });
51
+ console.error(err);
52
+ Toast.error(formatError(err));
53
+ });
54
+ };
55
+
56
+ return (
57
+ <DrawerForm
58
+ open
59
+ icon={<AddOutlined />}
60
+ onClose={onClose}
61
+ text={t('admin.paymentCurrency.edit')}
62
+ width={640}
63
+ addons={
64
+ <Button variant="contained" size="small" onClick={handleSubmit(onSubmit)} disabled={state.loading}>
65
+ {state.loading ? <CircularProgress size="small" /> : t('admin.paymentCurrency.save')}
66
+ </Button>
67
+ }>
68
+ <FormProvider {...methods}>
69
+ <PaymentCurrencyForm disableKeys={['contract']} />
70
+ </FormProvider>
71
+ </DrawerForm>
72
+ );
73
+ }
@@ -6,7 +6,15 @@ import { useFormContext, useWatch } from 'react-hook-form';
6
6
 
7
7
  import Uploader from '../uploader';
8
8
 
9
- export default function PaymentCurrencyForm() {
9
+ type TPaymentCurrencyFormProps = {
10
+ disableKeys?: string[];
11
+ };
12
+
13
+ PaymentCurrencyForm.defaultProps = {
14
+ disableKeys: [],
15
+ };
16
+
17
+ export default function PaymentCurrencyForm({ disableKeys = [] }: TPaymentCurrencyFormProps) {
10
18
  const { t } = useLocaleContext();
11
19
  const { control, setValue } = useFormContext();
12
20
  const logo = useWatch({ control, name: 'logo' });
@@ -29,6 +37,7 @@ export default function PaymentCurrencyForm() {
29
37
  rules={{ required: true }}
30
38
  label={t('admin.paymentMethod.name.label')}
31
39
  placeholder={t('admin.paymentMethod.name.tip')}
40
+ disabled={disableKeys.includes('name')}
32
41
  />
33
42
  <FormInput
34
43
  key="description"
@@ -37,6 +46,7 @@ export default function PaymentCurrencyForm() {
37
46
  rules={{ required: true }}
38
47
  label={t('admin.paymentMethod.description.label')}
39
48
  placeholder={t('admin.paymentMethod.description.tip')}
49
+ disabled={disableKeys.includes('description')}
40
50
  />
41
51
  <FormInput
42
52
  key="contract"
@@ -45,6 +55,7 @@ export default function PaymentCurrencyForm() {
45
55
  rules={{ required: true }}
46
56
  label={t('admin.paymentCurrency.contract.label')}
47
57
  placeholder={t('admin.paymentCurrency.contract.tip')}
58
+ disabled={disableKeys.includes('contract')}
48
59
  />
49
60
  <Stack direction="column">
50
61
  <Typography mb={1}>{t('admin.paymentCurrency.logo.label')}</Typography>
@@ -423,9 +423,13 @@ export default flat({
423
423
  },
424
424
  paymentCurrency: {
425
425
  name: 'Payment Currency',
426
- add: 'Add payment currency',
426
+ add: 'Add Currency',
427
+ edit: 'Edit Currency',
427
428
  save: 'Save payment currency',
428
429
  saved: 'Payment currency successfully saved',
430
+ delete: 'Delete payment currency',
431
+ deleteConfirm: 'Are you sure you want to delete this payment currency? Once deleted, it cannot be recovered',
432
+ deleted: 'Payment currency successfully deleted',
429
433
  logo: {
430
434
  label: 'Logo',
431
435
  tip: 'Displayed on payment page',
@@ -413,8 +413,12 @@ export default flat({
413
413
  paymentCurrency: {
414
414
  name: '支付货币',
415
415
  add: '添加货币',
416
+ edit: '编辑货币',
416
417
  save: '保存货币',
417
418
  saved: '货币已成功保存',
419
+ delete: '删除货币',
420
+ deleteConfirm: '确定要删除此货币吗?一旦删除,将无法恢复',
421
+ deleted: '货币已成功删除',
418
422
  logo: {
419
423
  label: 'Logo',
420
424
  tip: '在支付页面显示',
@@ -1,6 +1,6 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
- import { Switch, api, formatError, usePaymentContext } from '@blocklet/payment-react';
3
- import type { TPaymentMethodExpanded } from '@blocklet/payment-types';
2
+ import { ConfirmDialog, Switch, api, formatError, usePaymentContext } from '@blocklet/payment-react';
3
+ import type { TPaymentCurrency, TPaymentMethodExpanded } from '@blocklet/payment-types';
4
4
  import {
5
5
  AddOutlined,
6
6
  Check,
@@ -36,6 +36,7 @@ import IconCollapse from '../../../../components/collapse';
36
36
  import InfoCard from '../../../../components/info-card';
37
37
  import InfoRow from '../../../../components/info-row';
38
38
  import PaymentCurrencyAdd from '../../../../components/payment-currency/add';
39
+ import PaymentCurrencyEdit from '../../../../components/payment-currency/edit';
39
40
 
40
41
  const getMethods = (
41
42
  params: Record<string, any> = {}
@@ -154,9 +155,9 @@ export default function PaymentMethods() {
154
155
  addresses: true,
155
156
  })
156
157
  );
157
- const [state, setState] = useSetState({ method: '' });
158
158
  const [didDialog, setDidDialog] = useSetState({ open: false, chainId: '', did: '' });
159
159
  const { refresh } = usePaymentContext();
160
+ const [currencyDialog, setCurrencyDialog] = useSetState({ action: '', value: null, method: '' });
160
161
 
161
162
  const { list: methods, addresses } = data;
162
163
  useBus(
@@ -170,7 +171,19 @@ export default function PaymentMethods() {
170
171
  useBus(
171
172
  'paymentCurrency.added',
172
173
  () => {
173
- runAsync().then(() => setState({ method: '' }));
174
+ runAsync().then(() => {
175
+ setCurrencyDialog({ action: '', method: '' });
176
+ });
177
+ refresh(true);
178
+ },
179
+ []
180
+ );
181
+ useBus(
182
+ 'paymentCurrency.updated',
183
+ () => {
184
+ runAsync().then(() => {
185
+ setCurrencyDialog({ action: '', method: '' });
186
+ });
174
187
  refresh(true);
175
188
  },
176
189
  []
@@ -192,6 +205,21 @@ export default function PaymentMethods() {
192
205
  }
193
206
  return addresses?.arcblock || '';
194
207
  };
208
+
209
+ const handleDeleteCurrency = async (currency: TPaymentCurrency) => {
210
+ try {
211
+ await api.delete(`/api/payment-currencies/${currency.id}`);
212
+ runAsync();
213
+ refresh(true);
214
+ Toast.success(t('admin.paymentCurrency.deleted'));
215
+ } catch (err) {
216
+ Toast.error(formatError(err));
217
+ } finally {
218
+ // @ts-ignore
219
+ setCurrencyDialog({ action: '', method: '' });
220
+ }
221
+ };
222
+
195
223
  return (
196
224
  <>
197
225
  {Object.keys(groups).map((x) => (
@@ -269,14 +297,38 @@ export default function PaymentMethods() {
269
297
  value={
270
298
  <List>
271
299
  {method.payment_currencies.map((currency) => {
300
+ if (!currency) {
301
+ return null;
302
+ }
272
303
  return (
273
304
  <ListItem
274
305
  key={currency.id}
275
306
  disablePadding
276
307
  secondaryAction={
277
- <IconButton edge="end" disabled>
278
- <DeleteOutlined />
279
- </IconButton>
308
+ currency.locked || method.default_currency_id === currency.id ? null : (
309
+ <>
310
+ <IconButton
311
+ edge="end"
312
+ onClick={() => {
313
+ setCurrencyDialog({
314
+ action: 'edit',
315
+ // @ts-ignore
316
+ value: currency,
317
+ method: method.id,
318
+ });
319
+ }}>
320
+ <EditOutlined />
321
+ </IconButton>
322
+ <IconButton
323
+ edge="end"
324
+ onClick={() => {
325
+ // @ts-ignore
326
+ setCurrencyDialog({ action: 'delete', value: currency, method: method.id });
327
+ }}>
328
+ <DeleteOutlined />
329
+ </IconButton>
330
+ </>
331
+ )
280
332
  }>
281
333
  <ListItemAvatar>
282
334
  <Avatar src={currency.logo} alt={currency.name} />
@@ -290,17 +342,37 @@ export default function PaymentMethods() {
290
342
  key="add-currency"
291
343
  disablePadding
292
344
  sx={{ cursor: 'pointer' }}
293
- onClick={() => setState({ method: method.id })}>
345
+ onClick={() => {
346
+ setCurrencyDialog({ action: 'create', method: method.id });
347
+ }}>
294
348
  <ListItemAvatar>
295
349
  <Avatar>
296
350
  <AddOutlined fontSize="small" />
297
351
  </Avatar>
298
352
  </ListItemAvatar>
299
- <ListItemText primary="Add Currency" />
353
+ <ListItemText primary={t('admin.paymentCurrency.add')} />
300
354
  </ListItem>
301
355
  )}
302
- {state.method === method.id && (
303
- <PaymentCurrencyAdd method={method} onClose={() => setState({ method: '' })} />
356
+ {currencyDialog.method === method.id && currencyDialog.action === 'create' && (
357
+ <PaymentCurrencyAdd
358
+ method={method}
359
+ onClose={() => setCurrencyDialog({ action: '', method: '' })}
360
+ />
361
+ )}
362
+ {currencyDialog.method === method.id && currencyDialog.action === 'edit' && (
363
+ <PaymentCurrencyEdit
364
+ method={method}
365
+ onClose={() => setCurrencyDialog({ action: '', method: '' })}
366
+ value={currencyDialog.value!}
367
+ />
368
+ )}
369
+ {currencyDialog.method === method.id && currencyDialog.action === 'delete' && (
370
+ <ConfirmDialog
371
+ onConfirm={() => handleDeleteCurrency(currencyDialog.value!)}
372
+ onCancel={() => setCurrencyDialog({ action: '', method: '' })}
373
+ title={t('admin.paymentCurrency.delete')}
374
+ message={t('admin.paymentCurrency.deleteConfirm')}
375
+ />
304
376
  )}
305
377
  </List>
306
378
  }