payment-kit 1.17.11 → 1.18.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.
- package/api/src/integrations/arcblock/stake.ts +0 -5
- package/api/src/routes/payment-methods.ts +84 -10
- package/blocklet.yml +1 -1
- package/package.json +4 -4
- package/src/components/collapse.tsx +2 -1
- package/src/components/payment-method/arcblock.tsx +7 -2
- package/src/components/payment-method/base.tsx +8 -2
- package/src/components/payment-method/bitcoin.tsx +7 -2
- package/src/components/payment-method/ethereum.tsx +8 -2
- package/src/components/payment-method/evm-rpc-input.tsx +7 -1
- package/src/components/payment-method/form.tsx +25 -6
- package/src/components/payment-method/stripe.tsx +5 -1
- package/src/components/uploader.tsx +19 -5
- package/src/locales/en.tsx +4 -1
- package/src/locales/zh.tsx +4 -1
- package/src/pages/admin/customers/customers/detail.tsx +1 -0
- package/src/pages/admin/customers/customers/index.tsx +1 -0
- package/src/pages/admin/overview.tsx +26 -5
- package/src/pages/admin/settings/payment-methods/edit.tsx +81 -0
- package/src/pages/admin/settings/payment-methods/index.tsx +150 -49
- package/src/pages/customer/index.tsx +11 -5
- package/src/pages/customer/subscription/detail.tsx +1 -1
- package/src/pages/home.tsx +6 -1
|
@@ -4,7 +4,6 @@ import assert from 'assert';
|
|
|
4
4
|
|
|
5
5
|
import { isEthereumDid } from '@arcblock/did';
|
|
6
6
|
import { toStakeAddress } from '@arcblock/did-util';
|
|
7
|
-
import env from '@blocklet/sdk/lib/env';
|
|
8
7
|
import { BN, fromUnitToToken, toBN } from '@ocap/util';
|
|
9
8
|
|
|
10
9
|
import { Op } from 'sequelize';
|
|
@@ -26,10 +25,6 @@ export async function ensureStakedForGas() {
|
|
|
26
25
|
|
|
27
26
|
try {
|
|
28
27
|
const { state: account } = await client.getAccountState({ address: wallet.address });
|
|
29
|
-
if (!account) {
|
|
30
|
-
const hash = await client.declare({ moniker: env.appNameSlug, wallet });
|
|
31
|
-
logger.info(`declared app on chain ${host}`, { hash });
|
|
32
|
-
}
|
|
33
28
|
|
|
34
29
|
const address = toStakeAddress(wallet.address, wallet.address);
|
|
35
30
|
const { state: stake } = await client.getStakeState({ address });
|
|
@@ -6,6 +6,7 @@ import pick from 'lodash/pick';
|
|
|
6
6
|
import { InferAttributes, Op, WhereOptions } from 'sequelize';
|
|
7
7
|
import cloneDeep from 'lodash/cloneDeep';
|
|
8
8
|
import merge from 'lodash/merge';
|
|
9
|
+
import { Joi } from '@arcblock/validator';
|
|
9
10
|
import { ensureWebhookRegistered } from '../integrations/stripe/setup';
|
|
10
11
|
import logger from '../libs/logger';
|
|
11
12
|
import { authenticate } from '../libs/security';
|
|
@@ -14,6 +15,7 @@ import { PaymentMethod, TPaymentMethod } from '../store/models/payment-method';
|
|
|
14
15
|
import type { EVMChainType, PaymentMethodSettings } from '../store/models/types';
|
|
15
16
|
import { ethWallet, wallet } from '../libs/auth';
|
|
16
17
|
import { EVM_CHAIN_TYPES } from '../libs/constants';
|
|
18
|
+
import { getTokenSummaryByDid } from '../integrations/arcblock/stake';
|
|
17
19
|
|
|
18
20
|
const router = Router();
|
|
19
21
|
|
|
@@ -114,7 +116,7 @@ router.post('/', auth, async (req, res) => {
|
|
|
114
116
|
blockNumber,
|
|
115
117
|
});
|
|
116
118
|
} catch (err) {
|
|
117
|
-
logger.error(
|
|
119
|
+
logger.error(`verify ${paymentType} api endpoint failed`, err);
|
|
118
120
|
return res.status(400).json({ error: err.message });
|
|
119
121
|
}
|
|
120
122
|
|
|
@@ -179,15 +181,28 @@ router.get('/', auth, async (req, res) => {
|
|
|
179
181
|
if (typeof query.livemode === 'string') {
|
|
180
182
|
where.livemode = JSON.parse(query.livemode);
|
|
181
183
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
184
|
+
try {
|
|
185
|
+
const list = await PaymentMethod.findAll({
|
|
186
|
+
where,
|
|
187
|
+
order: [['created_at', 'ASC']],
|
|
188
|
+
include: [{ model: PaymentCurrency, as: 'payment_currencies', order: [['created_at', 'ASC']] }],
|
|
189
|
+
});
|
|
190
|
+
if (query.addresses === 'true') {
|
|
191
|
+
const [arcblock, ethereum] = await Promise.all([
|
|
192
|
+
getTokenSummaryByDid(wallet.address, !!req.livemode, 'arcblock'),
|
|
193
|
+
getTokenSummaryByDid(ethWallet.address, !!req.livemode, EVM_CHAIN_TYPES),
|
|
194
|
+
]);
|
|
195
|
+
res.json({
|
|
196
|
+
list,
|
|
197
|
+
addresses: { arcblock: wallet.address, ethereum: ethWallet.address },
|
|
198
|
+
balances: { ...arcblock, ...ethereum },
|
|
199
|
+
});
|
|
200
|
+
} else {
|
|
201
|
+
res.json(list);
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
logger.error('get payment methods failed', err);
|
|
205
|
+
res.status(400).json({ error: err.message });
|
|
191
206
|
}
|
|
192
207
|
});
|
|
193
208
|
|
|
@@ -255,4 +270,63 @@ router.put('/:id/settings', auth, async (req, res) => {
|
|
|
255
270
|
}
|
|
256
271
|
});
|
|
257
272
|
|
|
273
|
+
const updateMethodSchema = Joi.object({
|
|
274
|
+
name: Joi.string().empty('').optional(),
|
|
275
|
+
description: Joi.string().empty('').optional(),
|
|
276
|
+
logo: Joi.string().empty('').optional(),
|
|
277
|
+
}).unknown(true);
|
|
278
|
+
router.put('/:id', auth, async (req, res) => {
|
|
279
|
+
const { id } = req.params;
|
|
280
|
+
const { error, value: raw } = updateMethodSchema.validate(pick(req.body, ['name', 'description', 'logo']));
|
|
281
|
+
if (error) {
|
|
282
|
+
return res.status(400).json({ error: error.message });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const method = await PaymentMethod.findByPk(id);
|
|
287
|
+
if (!method) {
|
|
288
|
+
return res.status(404).json({ error: 'Payment method not found' });
|
|
289
|
+
}
|
|
290
|
+
const updateData: Partial<TPaymentMethod> = {
|
|
291
|
+
name: raw.name ?? method.name,
|
|
292
|
+
description: raw.description ?? method.description,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
if ('logo' in method.dataValues || raw.logo !== undefined) {
|
|
296
|
+
updateData.logo = raw.logo ?? method.logo;
|
|
297
|
+
}
|
|
298
|
+
const updateSettings = 'settings' in req.body ? req.body.settings : null;
|
|
299
|
+
if (EVM_CHAIN_TYPES.includes(method.type as string) && updateSettings) {
|
|
300
|
+
const paymentType = method.type as EVMChainType;
|
|
301
|
+
if (!updateSettings[paymentType]?.api_host) {
|
|
302
|
+
return res.status(400).json({ error: `${paymentType} api_host is required` });
|
|
303
|
+
}
|
|
304
|
+
if (!updateSettings[paymentType]?.explorer_host) {
|
|
305
|
+
return res.status(400).json({ error: `${paymentType} explorer_host is required` });
|
|
306
|
+
}
|
|
307
|
+
if (!updateSettings[paymentType]?.native_symbol) {
|
|
308
|
+
return res.status(400).json({ error: `${paymentType} native_symbol is required` });
|
|
309
|
+
}
|
|
310
|
+
try {
|
|
311
|
+
const provider = new ethers.JsonRpcProvider(updateSettings[paymentType]?.api_host);
|
|
312
|
+
const [network, blockNumber] = await Promise.all([provider.getNetwork(), provider.getBlockNumber()]);
|
|
313
|
+
updateSettings[paymentType]!.chain_id = network.chainId.toString();
|
|
314
|
+
logger.info(`${paymentType} api endpoint verified`, {
|
|
315
|
+
settings: updateSettings[paymentType],
|
|
316
|
+
network,
|
|
317
|
+
blockNumber,
|
|
318
|
+
});
|
|
319
|
+
} catch (err) {
|
|
320
|
+
logger.error(`verify ${paymentType} api endpoint failed`, err);
|
|
321
|
+
return res.status(400).json({ error: err.message });
|
|
322
|
+
}
|
|
323
|
+
updateData.settings = pick(PaymentMethod.encryptSettings(updateSettings), [paymentType]) as PaymentMethodSettings;
|
|
324
|
+
}
|
|
325
|
+
const updatedMethod = await method.update(updateData);
|
|
326
|
+
return res.json(updatedMethod);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
return res.status(400).json({ error: err.message });
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
258
332
|
export default router;
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
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.
|
|
56
|
+
"@blocklet/payment-react": "1.18.0",
|
|
57
57
|
"@blocklet/sdk": "^1.16.37",
|
|
58
58
|
"@blocklet/ui-react": "^2.11.27",
|
|
59
59
|
"@blocklet/uploader": "^0.1.64",
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
"devDependencies": {
|
|
122
122
|
"@abtnode/types": "^1.16.37",
|
|
123
123
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
124
|
-
"@blocklet/payment-types": "1.
|
|
124
|
+
"@blocklet/payment-types": "1.18.0",
|
|
125
125
|
"@types/cookie-parser": "^1.4.7",
|
|
126
126
|
"@types/cors": "^2.8.17",
|
|
127
127
|
"@types/debug": "^4.1.12",
|
|
@@ -167,5 +167,5 @@
|
|
|
167
167
|
"parser": "typescript"
|
|
168
168
|
}
|
|
169
169
|
},
|
|
170
|
-
"gitHead": "
|
|
170
|
+
"gitHead": "052c301e1f565558766c9346ba355f2fd072108e"
|
|
171
171
|
}
|
|
@@ -40,7 +40,8 @@ export default function IconCollapse(props: Props) {
|
|
|
40
40
|
direction="row"
|
|
41
41
|
alignItems="center"
|
|
42
42
|
justifyContent="space-between"
|
|
43
|
-
onClick={() => {
|
|
43
|
+
onClick={(e) => {
|
|
44
|
+
e.stopPropagation();
|
|
44
45
|
props.onChange?.(props.value || '', !expanded);
|
|
45
46
|
toggleExpanded();
|
|
46
47
|
}}
|
|
@@ -6,7 +6,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
|
|
6
6
|
|
|
7
7
|
import Uploader from '../uploader';
|
|
8
8
|
|
|
9
|
-
export default function ArcBlockMethodForm() {
|
|
9
|
+
export default function ArcBlockMethodForm({ checkDisabled }: { checkDisabled: (key: string) => boolean }) {
|
|
10
10
|
const { t } = useLocaleContext();
|
|
11
11
|
const { control, setValue } = useFormContext();
|
|
12
12
|
const logo = useWatch({ control, name: 'logo' });
|
|
@@ -29,6 +29,7 @@ export default function ArcBlockMethodForm() {
|
|
|
29
29
|
rules={{ required: true }}
|
|
30
30
|
label={t('admin.paymentMethod.name.label')}
|
|
31
31
|
placeholder={t('admin.paymentMethod.name.tip')}
|
|
32
|
+
disabled={checkDisabled('name')}
|
|
32
33
|
/>
|
|
33
34
|
<FormInput
|
|
34
35
|
key="description"
|
|
@@ -37,6 +38,7 @@ export default function ArcBlockMethodForm() {
|
|
|
37
38
|
rules={{ required: true }}
|
|
38
39
|
label={t('admin.paymentMethod.description.label')}
|
|
39
40
|
placeholder={t('admin.paymentMethod.description.tip')}
|
|
41
|
+
disabled={checkDisabled('description')}
|
|
40
42
|
/>
|
|
41
43
|
<FormInput
|
|
42
44
|
key="secret_key"
|
|
@@ -45,6 +47,7 @@ export default function ArcBlockMethodForm() {
|
|
|
45
47
|
rules={{ required: true }}
|
|
46
48
|
label={t('admin.paymentMethod.arcblock.chain_id.label')}
|
|
47
49
|
placeholder={t('admin.paymentMethod.arcblock.chain_id.tip')}
|
|
50
|
+
disabled={checkDisabled('settings.arcblock.chain_id')}
|
|
48
51
|
/>
|
|
49
52
|
<FormInput
|
|
50
53
|
key="api_host"
|
|
@@ -53,6 +56,7 @@ export default function ArcBlockMethodForm() {
|
|
|
53
56
|
rules={{ required: true }}
|
|
54
57
|
label={t('admin.paymentMethod.arcblock.api_host.label')}
|
|
55
58
|
placeholder={t('admin.paymentMethod.arcblock.api_host.tip')}
|
|
59
|
+
disabled={checkDisabled('settings.arcblock.api_host')}
|
|
56
60
|
/>
|
|
57
61
|
<FormInput
|
|
58
62
|
key="explorer_host"
|
|
@@ -61,10 +65,11 @@ export default function ArcBlockMethodForm() {
|
|
|
61
65
|
rules={{ required: true }}
|
|
62
66
|
label={t('admin.paymentMethod.arcblock.explorer_host.label')}
|
|
63
67
|
placeholder={t('admin.paymentMethod.arcblock.explorer_host.tip')}
|
|
68
|
+
disabled={checkDisabled('settings.arcblock.explorer_host')}
|
|
64
69
|
/>
|
|
65
70
|
<Stack direction="column">
|
|
66
71
|
<Typography mb={1}>{t('admin.paymentCurrency.logo.label')}</Typography>
|
|
67
|
-
<Uploader onUploaded={onUploaded} preview={logo} />
|
|
72
|
+
<Uploader onUploaded={onUploaded} preview={logo} disabled={checkDisabled('logo')} />
|
|
68
73
|
</Stack>
|
|
69
74
|
</>
|
|
70
75
|
);
|
|
@@ -7,7 +7,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
|
|
7
7
|
import Uploader from '../uploader';
|
|
8
8
|
import EvmRpcInput from './evm-rpc-input';
|
|
9
9
|
|
|
10
|
-
export default function BaseMethodForm() {
|
|
10
|
+
export default function BaseMethodForm({ checkDisabled }: { checkDisabled: (key: string) => boolean }) {
|
|
11
11
|
const { t } = useLocaleContext();
|
|
12
12
|
const { control, setValue } = useFormContext();
|
|
13
13
|
const logo = useWatch({ control, name: 'logo' });
|
|
@@ -30,6 +30,7 @@ export default function BaseMethodForm() {
|
|
|
30
30
|
rules={{ required: true }}
|
|
31
31
|
label={t('admin.paymentMethod.name.label')}
|
|
32
32
|
placeholder={t('admin.paymentMethod.name.tip')}
|
|
33
|
+
disabled={checkDisabled('name')}
|
|
33
34
|
/>
|
|
34
35
|
<FormInput
|
|
35
36
|
key="description"
|
|
@@ -38,11 +39,13 @@ export default function BaseMethodForm() {
|
|
|
38
39
|
rules={{ required: true }}
|
|
39
40
|
label={t('admin.paymentMethod.description.label')}
|
|
40
41
|
placeholder={t('admin.paymentMethod.description.tip')}
|
|
42
|
+
disabled={checkDisabled('description')}
|
|
41
43
|
/>
|
|
42
44
|
<EvmRpcInput
|
|
43
45
|
name="settings.base.api_host"
|
|
44
46
|
label={t('admin.paymentMethod.base.api_host.label')}
|
|
45
47
|
placeholder={t('admin.paymentMethod.base.api_host.tip')}
|
|
48
|
+
disabled={checkDisabled('settings.base.api_host')}
|
|
46
49
|
/>
|
|
47
50
|
<FormInput
|
|
48
51
|
key="explorer_host"
|
|
@@ -51,6 +54,7 @@ export default function BaseMethodForm() {
|
|
|
51
54
|
rules={{ required: true }}
|
|
52
55
|
label={t('admin.paymentMethod.base.explorer_host.label')}
|
|
53
56
|
placeholder={t('admin.paymentMethod.base.explorer_host.tip')}
|
|
57
|
+
disabled={checkDisabled('settings.base.explorer_host')}
|
|
54
58
|
/>
|
|
55
59
|
<FormInput
|
|
56
60
|
key="native_symbol"
|
|
@@ -59,6 +63,7 @@ export default function BaseMethodForm() {
|
|
|
59
63
|
rules={{ required: true }}
|
|
60
64
|
label={t('admin.paymentMethod.base.native_symbol.label')}
|
|
61
65
|
placeholder={t('admin.paymentMethod.base.native_symbol.tip')}
|
|
66
|
+
disabled={checkDisabled('settings.base.native_symbol')}
|
|
62
67
|
/>
|
|
63
68
|
<FormInput
|
|
64
69
|
key="confirmation"
|
|
@@ -67,10 +72,11 @@ export default function BaseMethodForm() {
|
|
|
67
72
|
rules={{ required: true }}
|
|
68
73
|
label={t('admin.paymentMethod.base.confirmation.label')}
|
|
69
74
|
placeholder={t('admin.paymentMethod.base.confirmation.tip')}
|
|
75
|
+
disabled={checkDisabled('settings.base.confirmation')}
|
|
70
76
|
/>
|
|
71
77
|
<Stack direction="column">
|
|
72
78
|
<Typography mb={1}>{t('admin.paymentCurrency.logo.label')}</Typography>
|
|
73
|
-
<Uploader onUploaded={onUploaded} preview={logo} />
|
|
79
|
+
<Uploader onUploaded={onUploaded} preview={logo} disabled={checkDisabled('logo')} />
|
|
74
80
|
</Stack>
|
|
75
81
|
</>
|
|
76
82
|
);
|
|
@@ -6,7 +6,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
|
|
6
6
|
|
|
7
7
|
import Uploader from '../uploader';
|
|
8
8
|
|
|
9
|
-
export default function BitcoinMethodForm() {
|
|
9
|
+
export default function BitcoinMethodForm({ checkDisabled }: { checkDisabled: (key: string) => boolean }) {
|
|
10
10
|
const { t } = useLocaleContext();
|
|
11
11
|
const { control, setValue } = useFormContext();
|
|
12
12
|
const logo = useWatch({ control, name: 'logo' });
|
|
@@ -29,6 +29,7 @@ export default function BitcoinMethodForm() {
|
|
|
29
29
|
rules={{ required: true }}
|
|
30
30
|
label={t('admin.paymentMethod.name.label')}
|
|
31
31
|
placeholder={t('admin.paymentMethod.name.tip')}
|
|
32
|
+
disabled={checkDisabled('name')}
|
|
32
33
|
/>
|
|
33
34
|
<FormInput
|
|
34
35
|
key="description"
|
|
@@ -37,6 +38,7 @@ export default function BitcoinMethodForm() {
|
|
|
37
38
|
rules={{ required: true }}
|
|
38
39
|
label={t('admin.paymentMethod.description.label')}
|
|
39
40
|
placeholder={t('admin.paymentMethod.description.tip')}
|
|
41
|
+
disabled={checkDisabled('description')}
|
|
40
42
|
/>
|
|
41
43
|
<FormInput
|
|
42
44
|
key="chain_id"
|
|
@@ -45,6 +47,7 @@ export default function BitcoinMethodForm() {
|
|
|
45
47
|
rules={{ required: true }}
|
|
46
48
|
label={t('admin.paymentMethod.bitcoin.chain_id.label')}
|
|
47
49
|
placeholder={t('admin.paymentMethod.bitcoin.chain_id.tip')}
|
|
50
|
+
disabled={checkDisabled('settings.bitcoin.chain_id')}
|
|
48
51
|
/>
|
|
49
52
|
<FormInput
|
|
50
53
|
key="api_host"
|
|
@@ -53,6 +56,7 @@ export default function BitcoinMethodForm() {
|
|
|
53
56
|
rules={{ required: true }}
|
|
54
57
|
label={t('admin.paymentMethod.bitcoin.api_host.label')}
|
|
55
58
|
placeholder={t('admin.paymentMethod.bitcoin.api_host.tip')}
|
|
59
|
+
disabled={checkDisabled('settings.bitcoin.api_host')}
|
|
56
60
|
/>
|
|
57
61
|
<FormInput
|
|
58
62
|
key="explorer_host"
|
|
@@ -61,10 +65,11 @@ export default function BitcoinMethodForm() {
|
|
|
61
65
|
rules={{ required: true }}
|
|
62
66
|
label={t('admin.paymentMethod.bitcoin.explorer_host.label')}
|
|
63
67
|
placeholder={t('admin.paymentMethod.bitcoin.explorer_host.tip')}
|
|
68
|
+
disabled={checkDisabled('settings.bitcoin.explorer_host')}
|
|
64
69
|
/>
|
|
65
70
|
<Stack direction="column">
|
|
66
71
|
<Typography mb={1}>{t('admin.paymentCurrency.logo.label')}</Typography>
|
|
67
|
-
<Uploader onUploaded={onUploaded} preview={logo} />
|
|
72
|
+
<Uploader onUploaded={onUploaded} preview={logo} disabled={checkDisabled('logo')} />
|
|
68
73
|
</Stack>
|
|
69
74
|
</>
|
|
70
75
|
);
|
|
@@ -7,7 +7,7 @@ import { useFormContext, useWatch } from 'react-hook-form';
|
|
|
7
7
|
import Uploader from '../uploader';
|
|
8
8
|
import EvmRpcInput from './evm-rpc-input';
|
|
9
9
|
|
|
10
|
-
export default function EthereumMethodForm() {
|
|
10
|
+
export default function EthereumMethodForm({ checkDisabled }: { checkDisabled: (key: string) => boolean }) {
|
|
11
11
|
const { t } = useLocaleContext();
|
|
12
12
|
const { control, setValue } = useFormContext();
|
|
13
13
|
const logo = useWatch({ control, name: 'logo' });
|
|
@@ -30,6 +30,7 @@ export default function EthereumMethodForm() {
|
|
|
30
30
|
rules={{ required: true }}
|
|
31
31
|
label={t('admin.paymentMethod.name.label')}
|
|
32
32
|
placeholder={t('admin.paymentMethod.name.tip')}
|
|
33
|
+
disabled={checkDisabled('name')}
|
|
33
34
|
/>
|
|
34
35
|
<FormInput
|
|
35
36
|
key="description"
|
|
@@ -38,11 +39,13 @@ export default function EthereumMethodForm() {
|
|
|
38
39
|
rules={{ required: true }}
|
|
39
40
|
label={t('admin.paymentMethod.description.label')}
|
|
40
41
|
placeholder={t('admin.paymentMethod.description.tip')}
|
|
42
|
+
disabled={checkDisabled('description')}
|
|
41
43
|
/>
|
|
42
44
|
<EvmRpcInput
|
|
43
45
|
name="settings.ethereum.api_host"
|
|
44
46
|
label={t('admin.paymentMethod.ethereum.api_host.label')}
|
|
45
47
|
placeholder={t('admin.paymentMethod.ethereum.api_host.tip')}
|
|
48
|
+
disabled={checkDisabled('settings.ethereum.api_host')}
|
|
46
49
|
/>
|
|
47
50
|
<FormInput
|
|
48
51
|
key="explorer_host"
|
|
@@ -51,6 +54,7 @@ export default function EthereumMethodForm() {
|
|
|
51
54
|
rules={{ required: true }}
|
|
52
55
|
label={t('admin.paymentMethod.ethereum.explorer_host.label')}
|
|
53
56
|
placeholder={t('admin.paymentMethod.ethereum.explorer_host.tip')}
|
|
57
|
+
disabled={checkDisabled('settings.ethereum.explorer_host')}
|
|
54
58
|
/>
|
|
55
59
|
<FormInput
|
|
56
60
|
key="native_symbol"
|
|
@@ -59,6 +63,7 @@ export default function EthereumMethodForm() {
|
|
|
59
63
|
rules={{ required: true }}
|
|
60
64
|
label={t('admin.paymentMethod.ethereum.native_symbol.label')}
|
|
61
65
|
placeholder={t('admin.paymentMethod.ethereum.native_symbol.tip')}
|
|
66
|
+
disabled={checkDisabled('settings.ethereum.native_symbol')}
|
|
62
67
|
/>
|
|
63
68
|
<FormInput
|
|
64
69
|
key="confirmation"
|
|
@@ -67,10 +72,11 @@ export default function EthereumMethodForm() {
|
|
|
67
72
|
rules={{ required: true }}
|
|
68
73
|
label={t('admin.paymentMethod.ethereum.confirmation.label')}
|
|
69
74
|
placeholder={t('admin.paymentMethod.ethereum.confirmation.tip')}
|
|
75
|
+
disabled={checkDisabled('settings.ethereum.confirmation')}
|
|
70
76
|
/>
|
|
71
77
|
<Stack direction="column">
|
|
72
78
|
<Typography mb={1}>{t('admin.paymentCurrency.logo.label')}</Typography>
|
|
73
|
-
<Uploader onUploaded={onUploaded} preview={logo} />
|
|
79
|
+
<Uploader onUploaded={onUploaded} preview={logo} disabled={checkDisabled('logo')} />
|
|
74
80
|
</Stack>
|
|
75
81
|
</>
|
|
76
82
|
);
|
|
@@ -10,9 +10,14 @@ interface Props {
|
|
|
10
10
|
name: string; // 表单字段名
|
|
11
11
|
label: string;
|
|
12
12
|
placeholder: string;
|
|
13
|
+
disabled?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
EvmRpcInput.defaultProps = {
|
|
17
|
+
disabled: false,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function EvmRpcInput({ name, label, placeholder, disabled }: Props) {
|
|
16
21
|
const { t } = useLocaleContext();
|
|
17
22
|
const { control } = useFormContext();
|
|
18
23
|
const apiHost = useWatch({
|
|
@@ -28,6 +33,7 @@ export default function EvmRpcInput({ name, label, placeholder }: Props) {
|
|
|
28
33
|
name={name}
|
|
29
34
|
type="text"
|
|
30
35
|
rules={{ required: true }}
|
|
36
|
+
disabled={disabled}
|
|
31
37
|
label={
|
|
32
38
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
33
39
|
{label}
|
|
@@ -9,12 +9,30 @@ import EthereumMethodForm from './ethereum';
|
|
|
9
9
|
import StripeMethodForm from './stripe';
|
|
10
10
|
import BaseMethodForm from './base';
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
PaymentMethodForm.defaultProps = {
|
|
13
|
+
action: 'create',
|
|
14
|
+
editableKeys: [],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default function PaymentMethodForm({
|
|
18
|
+
action,
|
|
19
|
+
editableKeys,
|
|
20
|
+
}: {
|
|
21
|
+
action?: 'create' | 'edit';
|
|
22
|
+
editableKeys?: string[];
|
|
23
|
+
}) {
|
|
13
24
|
const { t } = useLocaleContext();
|
|
14
25
|
const { control, setValue } = useFormContext();
|
|
15
26
|
|
|
16
27
|
const type = useWatch({ control, name: 'type' });
|
|
17
28
|
|
|
29
|
+
const checkDisabled = (key: string) => {
|
|
30
|
+
if (action === 'edit') {
|
|
31
|
+
return !editableKeys?.includes(key);
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
};
|
|
35
|
+
|
|
18
36
|
return (
|
|
19
37
|
<Root direction="column" alignItems="flex-start" spacing={2}>
|
|
20
38
|
<Controller
|
|
@@ -23,6 +41,7 @@ export default function PaymentMethodForm() {
|
|
|
23
41
|
render={({ field }) => (
|
|
24
42
|
<ToggleButtonGroup
|
|
25
43
|
{...field}
|
|
44
|
+
disabled={checkDisabled(field.name)}
|
|
26
45
|
onChange={(_, value: string) => {
|
|
27
46
|
if (value !== null) {
|
|
28
47
|
setValue(field.name, value);
|
|
@@ -42,11 +61,11 @@ export default function PaymentMethodForm() {
|
|
|
42
61
|
<Typography variant="h6" sx={{ mb: 3, fontWeight: 600 }}>
|
|
43
62
|
{t('admin.paymentMethod.settings')}
|
|
44
63
|
</Typography>
|
|
45
|
-
{type === 'stripe' && <StripeMethodForm />}
|
|
46
|
-
{type === 'arcblock' && <ArcBlockMethodForm />}
|
|
47
|
-
{type === 'ethereum' && <EthereumMethodForm />}
|
|
48
|
-
{type === 'base' && <BaseMethodForm />}
|
|
49
|
-
{type === 'bitcoin' && <BitcoinMethodForm />}
|
|
64
|
+
{type === 'stripe' && <StripeMethodForm checkDisabled={checkDisabled} />}
|
|
65
|
+
{type === 'arcblock' && <ArcBlockMethodForm checkDisabled={checkDisabled} />}
|
|
66
|
+
{type === 'ethereum' && <EthereumMethodForm checkDisabled={checkDisabled} />}
|
|
67
|
+
{type === 'base' && <BaseMethodForm checkDisabled={checkDisabled} />}
|
|
68
|
+
{type === 'bitcoin' && <BitcoinMethodForm checkDisabled={checkDisabled} />}
|
|
50
69
|
</Root>
|
|
51
70
|
);
|
|
52
71
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
3
|
import { FormInput } from '@blocklet/payment-react';
|
|
4
4
|
|
|
5
|
-
export default function StripeMethodForm() {
|
|
5
|
+
export default function StripeMethodForm({ checkDisabled }: { checkDisabled: (key: string) => boolean }) {
|
|
6
6
|
const { t } = useLocaleContext();
|
|
7
7
|
|
|
8
8
|
return (
|
|
@@ -13,6 +13,7 @@ export default function StripeMethodForm() {
|
|
|
13
13
|
rules={{ required: true }}
|
|
14
14
|
label={t('admin.paymentMethod.name.label')}
|
|
15
15
|
placeholder={t('admin.paymentMethod.name.tip')}
|
|
16
|
+
disabled={checkDisabled('name')}
|
|
16
17
|
/>
|
|
17
18
|
<FormInput
|
|
18
19
|
name="description"
|
|
@@ -27,6 +28,7 @@ export default function StripeMethodForm() {
|
|
|
27
28
|
rules={{ required: true }}
|
|
28
29
|
label={t('admin.paymentMethod.stripe.dashboard.label')}
|
|
29
30
|
placeholder={t('admin.paymentMethod.stripe.dashboard.tip')}
|
|
31
|
+
disabled={checkDisabled('settings.stripe.dashboard')}
|
|
30
32
|
/>
|
|
31
33
|
<FormInput
|
|
32
34
|
name="settings.stripe.publishable_key"
|
|
@@ -34,6 +36,7 @@ export default function StripeMethodForm() {
|
|
|
34
36
|
rules={{ required: true }}
|
|
35
37
|
label={t('admin.paymentMethod.stripe.publishable_key.label')}
|
|
36
38
|
placeholder={t('admin.paymentMethod.stripe.publishable_key.tip')}
|
|
39
|
+
disabled={checkDisabled('settings.stripe.publishable_key')}
|
|
37
40
|
/>
|
|
38
41
|
<FormInput
|
|
39
42
|
name="settings.stripe.secret_key"
|
|
@@ -41,6 +44,7 @@ export default function StripeMethodForm() {
|
|
|
41
44
|
rules={{ required: true }}
|
|
42
45
|
label={t('admin.paymentMethod.stripe.secret_key.label')}
|
|
43
46
|
placeholder={t('admin.paymentMethod.stripe.secret_key.tip')}
|
|
47
|
+
disabled={checkDisabled('settings.stripe.secret_key')}
|
|
44
48
|
/>
|
|
45
49
|
</>
|
|
46
50
|
);
|
|
@@ -16,9 +16,17 @@ type Props = {
|
|
|
16
16
|
maxFileSize?: number;
|
|
17
17
|
maxNumberOfFiles?: number;
|
|
18
18
|
allowedFileExts?: string[];
|
|
19
|
+
disabled?: boolean;
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
export default function Uploader({
|
|
22
|
+
export default function Uploader({
|
|
23
|
+
onUploaded,
|
|
24
|
+
preview,
|
|
25
|
+
maxFileSize,
|
|
26
|
+
maxNumberOfFiles,
|
|
27
|
+
allowedFileExts,
|
|
28
|
+
disabled,
|
|
29
|
+
}: Props) {
|
|
22
30
|
const uploaderRef = useRef<any>(null);
|
|
23
31
|
const handleOpen = useCallback(() => {
|
|
24
32
|
if (!uploaderRef.current) return;
|
|
@@ -74,7 +82,7 @@ export default function Uploader({ onUploaded, preview, maxFileSize, maxNumberOf
|
|
|
74
82
|
display="flex"
|
|
75
83
|
alignItems="center"
|
|
76
84
|
justifyContent="center"
|
|
77
|
-
onClick={handleOpen}
|
|
85
|
+
onClick={disabled ? undefined : handleOpen}
|
|
78
86
|
sx={{
|
|
79
87
|
position: 'relative',
|
|
80
88
|
cursor: 'pointer',
|
|
@@ -117,10 +125,15 @@ export default function Uploader({ onUploaded, preview, maxFileSize, maxNumberOf
|
|
|
117
125
|
},
|
|
118
126
|
}}>
|
|
119
127
|
<Stack direction="row">
|
|
120
|
-
<Button variant="text" onClick={handleOpen} startIcon={<Edit />} sx={{ minWidth: 20, color: '#fff' }} />
|
|
121
128
|
<Button
|
|
122
129
|
variant="text"
|
|
123
|
-
onClick={
|
|
130
|
+
onClick={disabled ? undefined : handleOpen}
|
|
131
|
+
startIcon={<Edit />}
|
|
132
|
+
sx={{ minWidth: 20, color: '#fff' }}
|
|
133
|
+
/>
|
|
134
|
+
<Button
|
|
135
|
+
variant="text"
|
|
136
|
+
onClick={disabled ? undefined : handleRemove}
|
|
124
137
|
startIcon={<Delete />}
|
|
125
138
|
sx={{ minWidth: 20, color: '#fff' }}
|
|
126
139
|
/>
|
|
@@ -138,7 +151,8 @@ Uploader.defaultProps = {
|
|
|
138
151
|
preview: '',
|
|
139
152
|
maxFileSize: undefined,
|
|
140
153
|
maxNumberOfFiles: 1,
|
|
141
|
-
allowedFileExts: ['.png', '.jpeg', '.webp'],
|
|
154
|
+
allowedFileExts: ['.png', '.jpeg', '.webp', '.svg', '.jpg'],
|
|
155
|
+
disabled: false,
|
|
142
156
|
};
|
|
143
157
|
|
|
144
158
|
const Div = styled(Box)`
|
package/src/locales/en.tsx
CHANGED
|
@@ -322,12 +322,13 @@ export default flat({
|
|
|
322
322
|
_name: 'Payment Method',
|
|
323
323
|
type: 'Type',
|
|
324
324
|
add: 'Add payment method',
|
|
325
|
+
edit: 'Edit payment method',
|
|
325
326
|
save: 'Save payment method',
|
|
326
327
|
saved: 'Payment method successfully saved',
|
|
327
328
|
settings: 'Settings',
|
|
328
329
|
gasTip:
|
|
329
330
|
'Ensure your account on the {chain} network has sufficient balance to cover transaction fees when using {method}.',
|
|
330
|
-
|
|
331
|
+
recharge: 'Scan to add balance',
|
|
331
332
|
props: {
|
|
332
333
|
type: 'Type',
|
|
333
334
|
confirmation: 'Confirmation',
|
|
@@ -335,6 +336,8 @@ export default flat({
|
|
|
335
336
|
refund: 'Refund support',
|
|
336
337
|
dispute: 'Dispute support',
|
|
337
338
|
currencies: 'Currency support',
|
|
339
|
+
explorer_host: 'Explorer Host',
|
|
340
|
+
balance: 'Balance',
|
|
338
341
|
},
|
|
339
342
|
name: {
|
|
340
343
|
label: 'Name',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -312,11 +312,12 @@ export default flat({
|
|
|
312
312
|
_name: '支付方式',
|
|
313
313
|
type: '类型',
|
|
314
314
|
add: '添加支付方式',
|
|
315
|
+
edit: '编辑支付方式',
|
|
315
316
|
save: '保存支付方式',
|
|
316
317
|
saved: '支付方式已成功保存',
|
|
317
318
|
settings: '设置',
|
|
318
319
|
gasTip: '使用 {method} 支付需保证账户在 {chain} 链上有余额支付手续费',
|
|
319
|
-
|
|
320
|
+
recharge: '扫码充值',
|
|
320
321
|
props: {
|
|
321
322
|
type: '类型',
|
|
322
323
|
confirmation: '确认',
|
|
@@ -324,6 +325,8 @@ export default flat({
|
|
|
324
325
|
refund: '退款支持',
|
|
325
326
|
dispute: '纠纷支持',
|
|
326
327
|
currencies: '货币支持',
|
|
328
|
+
explorer_host: '区块浏览器',
|
|
329
|
+
balance: '余额',
|
|
327
330
|
},
|
|
328
331
|
name: {
|
|
329
332
|
label: '名称',
|
|
@@ -209,6 +209,7 @@ export default function CustomerDetail(props: { id: string }) {
|
|
|
209
209
|
data.customer?.updated_at ? new Date(data.customer.updated_at).toISOString() : '',
|
|
210
210
|
52
|
|
211
211
|
)}
|
|
212
|
+
alt={data.customer.name}
|
|
212
213
|
variant="square"
|
|
213
214
|
sx={{ width: 52, height: 52, borderRadius: 'var(--radius-s, 4px)' }}
|
|
214
215
|
/>
|
|
@@ -10,6 +10,7 @@ import omit from 'lodash/omit';
|
|
|
10
10
|
import { useEffect } from 'react';
|
|
11
11
|
import { Link } from 'react-router-dom';
|
|
12
12
|
|
|
13
|
+
import { ArrowForward } from '@mui/icons-material';
|
|
13
14
|
import Chart, { TCurrencyMap } from '../../components/chart';
|
|
14
15
|
import dayjs from '../../libs/dayjs';
|
|
15
16
|
import { stringToColor } from '../../libs/util';
|
|
@@ -277,14 +278,26 @@ export default function Overview() {
|
|
|
277
278
|
href={summary.data?.links[currencyId] as string}
|
|
278
279
|
target="_blank"
|
|
279
280
|
variant="outlined"
|
|
280
|
-
sx={{
|
|
281
|
-
|
|
281
|
+
sx={{
|
|
282
|
+
padding: 1,
|
|
283
|
+
transition: 'all 0.2s ease-in-out',
|
|
284
|
+
position: 'relative',
|
|
285
|
+
'&:hover': {
|
|
286
|
+
backgroundColor: 'action.hover',
|
|
287
|
+
boxShadow: 1,
|
|
288
|
+
'& .MuiSvgIcon-root': {
|
|
289
|
+
opacity: 1,
|
|
290
|
+
transform: 'translateX(0)',
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
}}>
|
|
294
|
+
<Stack direction="row" alignItems="center" spacing={1}>
|
|
282
295
|
<Avatar
|
|
283
296
|
src={currencies[currencyId]?.logo}
|
|
284
|
-
alt={currencies[currencyId]?.
|
|
285
|
-
sx={{ width: 36, height: 36
|
|
297
|
+
alt={currencies[currencyId]?.symbol}
|
|
298
|
+
sx={{ width: 36, height: 36 }}
|
|
286
299
|
/>
|
|
287
|
-
<Box>
|
|
300
|
+
<Box flex={1}>
|
|
288
301
|
<Typography variant="h5" component="div" sx={{ fontSize: '2rem' }}>
|
|
289
302
|
{formatBNStr(
|
|
290
303
|
summary.data?.balances?.[currencyId] as string,
|
|
@@ -295,6 +308,14 @@ export default function Overview() {
|
|
|
295
308
|
{currencies[currencyId]?.symbol} on {currencies[currencyId]?.method.name}
|
|
296
309
|
</Typography>
|
|
297
310
|
</Box>
|
|
311
|
+
<ArrowForward
|
|
312
|
+
sx={{
|
|
313
|
+
opacity: 0,
|
|
314
|
+
transform: 'translateX(-10px)',
|
|
315
|
+
transition: 'all 0.2s ease-in-out',
|
|
316
|
+
color: 'text.secondary',
|
|
317
|
+
}}
|
|
318
|
+
/>
|
|
298
319
|
</Stack>
|
|
299
320
|
</Card>
|
|
300
321
|
))}
|
|
@@ -0,0 +1,81 @@
|
|
|
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 { 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 '../../../../components/drawer-form';
|
|
13
|
+
import PaymentMethodForm from '../../../../components/payment-method/form';
|
|
14
|
+
|
|
15
|
+
export default function PaymentMethodEdit({ onClose, value }: { onClose: () => void; value: TPaymentMethod | null }) {
|
|
16
|
+
const { t } = useLocaleContext();
|
|
17
|
+
const [state, setState] = useSetState({ loading: false });
|
|
18
|
+
|
|
19
|
+
const methods = useForm<TPaymentMethod>({
|
|
20
|
+
defaultValues: {
|
|
21
|
+
type: value?.type,
|
|
22
|
+
name: value?.name,
|
|
23
|
+
description: value?.description,
|
|
24
|
+
logo: value?.logo,
|
|
25
|
+
settings: value?.settings,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
const { handleSubmit } = methods;
|
|
29
|
+
|
|
30
|
+
const onSubmit = (data: TPaymentMethod) => {
|
|
31
|
+
setState({ loading: true });
|
|
32
|
+
api
|
|
33
|
+
.put(`/api/payment-methods/${value?.id}`, data)
|
|
34
|
+
.then(() => {
|
|
35
|
+
setState({ loading: false });
|
|
36
|
+
Toast.success(t('admin.paymentMethod.saved'));
|
|
37
|
+
methods.reset();
|
|
38
|
+
dispatch('drawer.submitted');
|
|
39
|
+
dispatch('paymentMethod.updated');
|
|
40
|
+
})
|
|
41
|
+
.catch((err) => {
|
|
42
|
+
setState({ loading: false });
|
|
43
|
+
console.error(err);
|
|
44
|
+
Toast.error(formatError(err));
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<DrawerForm
|
|
50
|
+
open
|
|
51
|
+
icon={<AddOutlined />}
|
|
52
|
+
onClose={onClose}
|
|
53
|
+
text={t('admin.paymentMethod.edit')}
|
|
54
|
+
width={640}
|
|
55
|
+
footer={
|
|
56
|
+
<Button
|
|
57
|
+
variant="contained"
|
|
58
|
+
size="large"
|
|
59
|
+
onClick={handleSubmit(onSubmit)}
|
|
60
|
+
disabled={state.loading}
|
|
61
|
+
sx={{ width: '100%' }}>
|
|
62
|
+
{state.loading && <CircularProgress size={20} />} {t('common.save')}
|
|
63
|
+
</Button>
|
|
64
|
+
}>
|
|
65
|
+
<FormProvider {...methods}>
|
|
66
|
+
<PaymentMethodForm
|
|
67
|
+
action="edit"
|
|
68
|
+
editableKeys={[
|
|
69
|
+
'name',
|
|
70
|
+
'description',
|
|
71
|
+
'logo',
|
|
72
|
+
'settings.base.api_host',
|
|
73
|
+
'settings.base.explorer_host',
|
|
74
|
+
'settings.ethereum.api_host',
|
|
75
|
+
'settings.ethereum.explorer_host',
|
|
76
|
+
]}
|
|
77
|
+
/>
|
|
78
|
+
</FormProvider>
|
|
79
|
+
</DrawerForm>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
2
|
import { ConfirmDialog, Switch, api, formatError, usePaymentContext } from '@blocklet/payment-react';
|
|
3
|
-
import type { TPaymentCurrency, TPaymentMethodExpanded } from '@blocklet/payment-types';
|
|
3
|
+
import type { ChainType, TPaymentCurrency, TPaymentMethodExpanded } from '@blocklet/payment-types';
|
|
4
4
|
import {
|
|
5
5
|
AddOutlined,
|
|
6
6
|
Check,
|
|
@@ -11,12 +11,14 @@ import {
|
|
|
11
11
|
QrCodeOutlined,
|
|
12
12
|
CheckCircleOutline,
|
|
13
13
|
ErrorOutline,
|
|
14
|
+
OpenInNewOutlined,
|
|
14
15
|
} from '@mui/icons-material';
|
|
15
16
|
import {
|
|
16
17
|
Alert,
|
|
17
18
|
Avatar,
|
|
18
19
|
Box,
|
|
19
20
|
CircularProgress,
|
|
21
|
+
Divider,
|
|
20
22
|
Grid,
|
|
21
23
|
IconButton,
|
|
22
24
|
List,
|
|
@@ -34,16 +36,23 @@ import useBus from 'use-bus';
|
|
|
34
36
|
import { useState } from 'react';
|
|
35
37
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
36
38
|
import { DIDDialog } from '@arcblock/ux/lib/DID';
|
|
39
|
+
import { fromUnitToToken } from '@ocap/util';
|
|
40
|
+
import { joinURL } from 'ufo';
|
|
37
41
|
import IconCollapse from '../../../../components/collapse';
|
|
38
42
|
import InfoCard from '../../../../components/info-card';
|
|
39
43
|
import InfoRow from '../../../../components/info-row';
|
|
40
44
|
import PaymentCurrencyAdd from '../../../../components/payment-currency/add';
|
|
41
45
|
import PaymentCurrencyEdit from '../../../../components/payment-currency/edit';
|
|
42
46
|
import { useRpcStatus } from '../../../../hooks/rpc-status';
|
|
47
|
+
import PaymentMethodEdit from './edit';
|
|
43
48
|
|
|
44
49
|
const getMethods = (
|
|
45
50
|
params: Record<string, any> = {}
|
|
46
|
-
): Promise<{
|
|
51
|
+
): Promise<{
|
|
52
|
+
list: TPaymentMethodExpanded[];
|
|
53
|
+
addresses: { arcblock: string; ethereum: string };
|
|
54
|
+
balances: { [currencyId: string]: string };
|
|
55
|
+
}> => {
|
|
47
56
|
const search = new URLSearchParams();
|
|
48
57
|
Object.keys(params).forEach((key) => {
|
|
49
58
|
search.set(key, String(params[key]));
|
|
@@ -155,9 +164,10 @@ function RpcStatus({ method }: { method: TPaymentMethodExpanded }) {
|
|
|
155
164
|
const renderStatus = () => {
|
|
156
165
|
if (status.loading) {
|
|
157
166
|
return (
|
|
158
|
-
<
|
|
159
|
-
{
|
|
160
|
-
|
|
167
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
|
168
|
+
<CircularProgress size={14} />
|
|
169
|
+
<Typography color="text.secondary">{t('admin.paymentMethod.evm.checking')}</Typography>
|
|
170
|
+
</Box>
|
|
161
171
|
);
|
|
162
172
|
}
|
|
163
173
|
if (status.connected) {
|
|
@@ -184,6 +194,108 @@ function RpcStatus({ method }: { method: TPaymentMethodExpanded }) {
|
|
|
184
194
|
);
|
|
185
195
|
}
|
|
186
196
|
|
|
197
|
+
function Balance({
|
|
198
|
+
method,
|
|
199
|
+
balances,
|
|
200
|
+
addresses,
|
|
201
|
+
setDidDialog,
|
|
202
|
+
}: {
|
|
203
|
+
method: TPaymentMethodExpanded;
|
|
204
|
+
balances: { [currencyId: string]: string };
|
|
205
|
+
addresses: { arcblock: string; ethereum: string };
|
|
206
|
+
setDidDialog: (value: any) => void;
|
|
207
|
+
}) {
|
|
208
|
+
const { t } = useLocaleContext();
|
|
209
|
+
const defaultCurrency = (method.payment_currencies || [])?.find(
|
|
210
|
+
(x) => x.id === method.default_currency_id
|
|
211
|
+
) as TPaymentCurrency;
|
|
212
|
+
const balance = fromUnitToToken(balances?.[defaultCurrency?.id] || '0', defaultCurrency?.decimal);
|
|
213
|
+
const explorerHost = (method?.settings?.[method?.type as ChainType] as any)?.explorer_host || '';
|
|
214
|
+
const getLink = () => {
|
|
215
|
+
if (method.type === 'arcblock' && addresses?.arcblock) {
|
|
216
|
+
return joinURL(explorerHost, 'accounts', addresses?.arcblock, 'tokens');
|
|
217
|
+
}
|
|
218
|
+
if (['ethereum', 'base'].includes(method.type) && addresses?.ethereum) {
|
|
219
|
+
return joinURL(explorerHost, 'address', addresses?.ethereum);
|
|
220
|
+
}
|
|
221
|
+
return '';
|
|
222
|
+
};
|
|
223
|
+
const insufficientBalance =
|
|
224
|
+
['ethereum', 'base'].includes(method.type) &&
|
|
225
|
+
balances?.[defaultCurrency?.id] &&
|
|
226
|
+
balances?.[defaultCurrency?.id] === '0';
|
|
227
|
+
const getAddress = (type: string) => {
|
|
228
|
+
if (['ethereum', 'base'].includes(type)) {
|
|
229
|
+
return addresses?.ethereum || '';
|
|
230
|
+
}
|
|
231
|
+
return addresses?.arcblock || '';
|
|
232
|
+
};
|
|
233
|
+
return (
|
|
234
|
+
<>
|
|
235
|
+
<InfoRow label={t('admin.paymentMethod.props.explorer_host')} value={explorerHost} />
|
|
236
|
+
<InfoRow
|
|
237
|
+
label={
|
|
238
|
+
<Box display="flex" alignItems="center" gap={0.5}>
|
|
239
|
+
{t('admin.paymentMethod.props.balance')}
|
|
240
|
+
{['ethereum', 'base'].includes(method.type) && (
|
|
241
|
+
<Tooltip
|
|
242
|
+
title={t('admin.paymentMethod.gasTip', {
|
|
243
|
+
method: method.type,
|
|
244
|
+
chain: method.name,
|
|
245
|
+
})}>
|
|
246
|
+
<InfoOutlined
|
|
247
|
+
sx={{ fontSize: 16, cursor: 'pointer', color: insufficientBalance ? 'error.main' : 'text.secondary' }}
|
|
248
|
+
/>
|
|
249
|
+
</Tooltip>
|
|
250
|
+
)}
|
|
251
|
+
</Box>
|
|
252
|
+
}
|
|
253
|
+
value={
|
|
254
|
+
<Stack
|
|
255
|
+
direction="row"
|
|
256
|
+
alignItems="center"
|
|
257
|
+
sx={{
|
|
258
|
+
display: 'inline-flex',
|
|
259
|
+
borderRadius: 1,
|
|
260
|
+
}}>
|
|
261
|
+
<Typography
|
|
262
|
+
component="a"
|
|
263
|
+
href={getLink()}
|
|
264
|
+
target="_blank"
|
|
265
|
+
rel="noreferrer"
|
|
266
|
+
sx={{
|
|
267
|
+
display: 'flex',
|
|
268
|
+
alignItems: 'center',
|
|
269
|
+
gap: 0.5,
|
|
270
|
+
'&.MuiTypography-root': {
|
|
271
|
+
color: insufficientBalance ? 'error.main' : 'text.link',
|
|
272
|
+
},
|
|
273
|
+
}}>
|
|
274
|
+
{balance} {defaultCurrency?.symbol}
|
|
275
|
+
<OpenInNewOutlined sx={{ fontSize: 14 }} />
|
|
276
|
+
</Typography>
|
|
277
|
+
<Divider orientation="vertical" flexItem sx={{ mx: 1 }} />
|
|
278
|
+
<Tooltip title={t('admin.paymentMethod.recharge')}>
|
|
279
|
+
<IconButton
|
|
280
|
+
size="small"
|
|
281
|
+
onClick={() =>
|
|
282
|
+
setDidDialog({
|
|
283
|
+
open: true,
|
|
284
|
+
chainId: (method.settings?.[method.type as ChainType] as any)?.chain_id,
|
|
285
|
+
did: getAddress(method.type),
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
sx={{ p: 0.5 }}>
|
|
289
|
+
<QrCodeOutlined sx={{ fontSize: 16 }} />
|
|
290
|
+
</IconButton>
|
|
291
|
+
</Tooltip>
|
|
292
|
+
</Stack>
|
|
293
|
+
}
|
|
294
|
+
/>
|
|
295
|
+
</>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
187
299
|
export default function PaymentMethods() {
|
|
188
300
|
const { t } = useLocaleContext();
|
|
189
301
|
const [expandedId, setExpandedId] = useSessionStorageState('payment-method-expanded-id', {
|
|
@@ -192,7 +304,7 @@ export default function PaymentMethods() {
|
|
|
192
304
|
const {
|
|
193
305
|
loading,
|
|
194
306
|
error,
|
|
195
|
-
data = { list: [], addresses: {} } as any,
|
|
307
|
+
data = { list: [], addresses: {}, balances: {} } as any,
|
|
196
308
|
runAsync,
|
|
197
309
|
} = useRequest(() =>
|
|
198
310
|
getMethods({
|
|
@@ -203,10 +315,11 @@ export default function PaymentMethods() {
|
|
|
203
315
|
setExpandedId(expanded ? methodId : '');
|
|
204
316
|
};
|
|
205
317
|
const [didDialog, setDidDialog] = useSetState({ open: false, chainId: '', did: '' });
|
|
318
|
+
const [methodDialog, setMethodDialog] = useSetState({ open: false, value: null });
|
|
206
319
|
const { refresh } = usePaymentContext();
|
|
207
320
|
const [currencyDialog, setCurrencyDialog] = useSetState({ action: '', value: null, method: '' });
|
|
208
321
|
|
|
209
|
-
const { list: methods, addresses } = data;
|
|
322
|
+
const { list: methods, addresses, balances } = data;
|
|
210
323
|
useBus(
|
|
211
324
|
'paymentMethod.created',
|
|
212
325
|
() => {
|
|
@@ -215,6 +328,14 @@ export default function PaymentMethods() {
|
|
|
215
328
|
},
|
|
216
329
|
[]
|
|
217
330
|
);
|
|
331
|
+
useBus(
|
|
332
|
+
'paymentMethod.updated',
|
|
333
|
+
() => {
|
|
334
|
+
runAsync();
|
|
335
|
+
refresh(true);
|
|
336
|
+
},
|
|
337
|
+
[]
|
|
338
|
+
);
|
|
218
339
|
useBus(
|
|
219
340
|
'paymentCurrency.added',
|
|
220
341
|
() => {
|
|
@@ -246,13 +367,6 @@ export default function PaymentMethods() {
|
|
|
246
367
|
|
|
247
368
|
const groups = groupByType(methods);
|
|
248
369
|
|
|
249
|
-
const getAddress = (type: string) => {
|
|
250
|
-
if (['ethereum', 'base'].includes(type)) {
|
|
251
|
-
return addresses?.ethereum || '';
|
|
252
|
-
}
|
|
253
|
-
return addresses?.arcblock || '';
|
|
254
|
-
};
|
|
255
|
-
|
|
256
370
|
const handleDeleteCurrency = async (currency: TPaymentCurrency) => {
|
|
257
371
|
try {
|
|
258
372
|
await api.delete(`/api/payment-currencies/${currency.id}`);
|
|
@@ -275,45 +389,25 @@ export default function PaymentMethods() {
|
|
|
275
389
|
<Typography variant="h6" sx={{ textTransform: 'uppercase' }}>
|
|
276
390
|
{x}
|
|
277
391
|
</Typography>
|
|
278
|
-
{['ethereum', 'base'].includes(x) && (
|
|
279
|
-
<Stack
|
|
280
|
-
direction="row"
|
|
281
|
-
alignItems="center"
|
|
282
|
-
spacing={0.5}
|
|
283
|
-
sx={{
|
|
284
|
-
px: 0.5,
|
|
285
|
-
color: 'text.secondary',
|
|
286
|
-
}}>
|
|
287
|
-
<InfoOutlined sx={{ fontSize: 16 }} color="warning" />
|
|
288
|
-
<Typography variant="body2">
|
|
289
|
-
{t('admin.paymentMethod.gasTip', {
|
|
290
|
-
method: x,
|
|
291
|
-
address: '222',
|
|
292
|
-
chain: groups[x]?.[0]?.name || x,
|
|
293
|
-
})}
|
|
294
|
-
</Typography>
|
|
295
|
-
<Tooltip title={t('admin.paymentMethod.showQR')}>
|
|
296
|
-
<IconButton
|
|
297
|
-
size="small"
|
|
298
|
-
onClick={() =>
|
|
299
|
-
setDidDialog({
|
|
300
|
-
open: true,
|
|
301
|
-
chainId: groups[x]?.[0]?.id || '',
|
|
302
|
-
did: getAddress(x),
|
|
303
|
-
})
|
|
304
|
-
}
|
|
305
|
-
sx={{ p: 0.5 }}>
|
|
306
|
-
<QrCodeOutlined sx={{ fontSize: 16 }} />
|
|
307
|
-
</IconButton>
|
|
308
|
-
</Tooltip>
|
|
309
|
-
</Stack>
|
|
310
|
-
)}
|
|
311
392
|
</Stack>
|
|
312
393
|
{(groups[x] as TPaymentMethodExpanded[]).map((method) => (
|
|
313
394
|
<IconCollapse
|
|
314
395
|
key={method.id}
|
|
315
396
|
trigger={<InfoCard {...method} />}
|
|
316
|
-
addons={
|
|
397
|
+
addons={
|
|
398
|
+
<>
|
|
399
|
+
<Switch checked={method.active} disabled sx={{ cursor: 'default' }} />
|
|
400
|
+
{method.type !== 'arcblock' && (
|
|
401
|
+
<IconButton
|
|
402
|
+
onClick={(e) => {
|
|
403
|
+
e.stopPropagation();
|
|
404
|
+
setMethodDialog({ open: true, value: method as any });
|
|
405
|
+
}}>
|
|
406
|
+
<EditOutlined />
|
|
407
|
+
</IconButton>
|
|
408
|
+
)}
|
|
409
|
+
</>
|
|
410
|
+
}
|
|
317
411
|
style={{
|
|
318
412
|
py: 1,
|
|
319
413
|
borderTop: '1px solid #eee',
|
|
@@ -329,6 +423,10 @@ export default function PaymentMethods() {
|
|
|
329
423
|
<InfoRow label={t('admin.paymentMethod.props.type')} value={method.type} />
|
|
330
424
|
{method.type === 'arcblock' && <EditApiHost method={method} />}
|
|
331
425
|
{['ethereum', 'base'].includes(method.type) && <RpcStatus method={method} />}
|
|
426
|
+
{['arcblock', 'ethereum', 'base'].includes(method.type) && (
|
|
427
|
+
<Balance method={method} balances={balances} addresses={addresses} setDidDialog={setDidDialog} />
|
|
428
|
+
)}
|
|
429
|
+
|
|
332
430
|
<InfoRow label={t('admin.paymentMethod.props.confirmation')} value={method.confirmation.type} />
|
|
333
431
|
<InfoRow
|
|
334
432
|
label={t('admin.paymentMethod.props.recurring')}
|
|
@@ -382,7 +480,7 @@ export default function PaymentMethods() {
|
|
|
382
480
|
)
|
|
383
481
|
}>
|
|
384
482
|
<ListItemAvatar>
|
|
385
|
-
<Avatar src={currency.logo} alt={currency.
|
|
483
|
+
<Avatar src={currency.logo} alt={currency.symbol} />
|
|
386
484
|
</ListItemAvatar>
|
|
387
485
|
<ListItemText primary={currency.name} secondary={currency.description} />
|
|
388
486
|
</ListItem>
|
|
@@ -442,6 +540,9 @@ export default function PaymentMethods() {
|
|
|
442
540
|
chainId={didDialog.chainId}
|
|
443
541
|
/>
|
|
444
542
|
)}
|
|
543
|
+
{methodDialog.open && methodDialog.value && (
|
|
544
|
+
<PaymentMethodEdit value={methodDialog.value} onClose={() => setMethodDialog({ open: false, value: null })} />
|
|
545
|
+
)}
|
|
445
546
|
</>
|
|
446
547
|
);
|
|
447
548
|
}
|
|
@@ -94,7 +94,14 @@ export default function CustomerHome() {
|
|
|
94
94
|
const { settings } = usePaymentContext();
|
|
95
95
|
const [currency, setCurrency] = useState(settings?.baseCurrency);
|
|
96
96
|
const [subscriptionLoading, setSubscriptionLoading] = useState(false);
|
|
97
|
-
const currencies = flatten(
|
|
97
|
+
const currencies = flatten(
|
|
98
|
+
settings.paymentMethods.map((method) =>
|
|
99
|
+
(method.payment_currencies || []).map((c) => ({
|
|
100
|
+
...c,
|
|
101
|
+
methodName: method.name,
|
|
102
|
+
}))
|
|
103
|
+
)
|
|
104
|
+
);
|
|
98
105
|
|
|
99
106
|
const { livemode, setLivemode } = usePaymentContext();
|
|
100
107
|
const [state, setState] = useSetState({
|
|
@@ -249,18 +256,17 @@ export default function CustomerHome() {
|
|
|
249
256
|
{currencies.map((c) => (
|
|
250
257
|
<MenuItem key={c.id} value={c.id}>
|
|
251
258
|
<Box alignItems="center" display="flex" gap={1}>
|
|
252
|
-
<Avatar src={c?.logo} alt={c?.
|
|
259
|
+
<Avatar src={c?.logo} alt={c?.symbol} sx={{ width: 18, height: 18 }} />
|
|
253
260
|
<Typography
|
|
254
261
|
variant="h5"
|
|
255
262
|
component="div"
|
|
256
263
|
sx={{ fontSize: '16px', color: 'text.primary', fontWeight: '500' }}>
|
|
257
|
-
{c?.
|
|
264
|
+
{c?.symbol}
|
|
258
265
|
</Typography>
|
|
259
266
|
<Typography sx={{ fontSize: 12 }} color="text.lighter">
|
|
260
|
-
{c?.
|
|
267
|
+
{c?.methodName}
|
|
261
268
|
</Typography>
|
|
262
269
|
</Box>
|
|
263
|
-
{/* {c.symbol} */}
|
|
264
270
|
</MenuItem>
|
|
265
271
|
))}
|
|
266
272
|
</Select>
|
|
@@ -209,7 +209,7 @@ export default function CustomerSubscriptionDetail() {
|
|
|
209
209
|
gap: 0.5,
|
|
210
210
|
fontWeight: 500,
|
|
211
211
|
}}>
|
|
212
|
-
<Avatar src={data.paymentCurrency?.logo} sx={{ width: 16, height: 16 }} alt={data.paymentCurrency?.
|
|
212
|
+
<Avatar src={data.paymentCurrency?.logo} sx={{ width: 16, height: 16 }} alt={data.paymentCurrency?.symbol} />
|
|
213
213
|
<Box display="flex" alignItems="baseline">
|
|
214
214
|
{formatBNStr(overdraftProtection?.unused, data.paymentCurrency.decimal)}
|
|
215
215
|
<Typography
|
package/src/pages/home.tsx
CHANGED
|
@@ -17,7 +17,12 @@ function Home() {
|
|
|
17
17
|
/>
|
|
18
18
|
<Stack alignItems="center" justifyContent="center" sx={{ height: '60vh', width: '100vw' }}>
|
|
19
19
|
<Stack maxWidth="sm" direction="column" alignItems="center" spacing={3}>
|
|
20
|
-
<Avatar
|
|
20
|
+
<Avatar
|
|
21
|
+
src={window.blocklet.appLogo}
|
|
22
|
+
sx={{ width: 80, height: 80 }}
|
|
23
|
+
variant="square"
|
|
24
|
+
alt={window.blocklet.appName || 'Payment Kit'}
|
|
25
|
+
/>
|
|
21
26
|
<Stack direction="column" alignItems="center" spacing={1}>
|
|
22
27
|
<Typography variant="h4">Payment Kit</Typography>
|
|
23
28
|
<Typography variant="h5" color="text.secondary" fontWeight="normal">
|