payment-kit 1.19.2 → 1.19.3
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/libs/security.ts +6 -3
- package/api/src/routes/checkout-sessions.ts +9 -4
- package/api/src/routes/credit-grants.ts +24 -2
- package/api/src/routes/customers.ts +2 -7
- package/api/src/routes/payment-currencies.ts +2 -0
- package/api/src/routes/payment-methods.ts +1 -0
- package/api/src/store/migrations/20250610-billing-credit.ts +0 -3
- package/api/src/store/migrations/20250708-currency-precision.ts +14 -0
- package/api/src/store/models/webhook-attempt.ts +1 -1
- package/blocklet.yml +1 -1
- package/package.json +25 -25
- package/src/components/edit-in-line.tsx +197 -0
- package/src/components/subscription/portal/list.tsx +0 -1
- package/src/pages/admin/settings/payment-methods/index.tsx +56 -85
- package/src/pages/customer/recharge/account.tsx +1 -1
package/api/src/libs/security.ts
CHANGED
|
@@ -17,6 +17,7 @@ type PermissionSpec<T extends Model> = {
|
|
|
17
17
|
// allow record owner
|
|
18
18
|
model: T;
|
|
19
19
|
field: string;
|
|
20
|
+
findById?: (id: string) => Promise<T | null>;
|
|
20
21
|
};
|
|
21
22
|
mine?: boolean;
|
|
22
23
|
embed?: boolean;
|
|
@@ -105,9 +106,11 @@ export function authenticate<T extends Model>({ component, roles, record, mine,
|
|
|
105
106
|
|
|
106
107
|
// authenticate by record owner
|
|
107
108
|
if (record) {
|
|
108
|
-
const { model, field = 'customer_id' } = record;
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const { model, field = 'customer_id', findById } = record;
|
|
110
|
+
const doc: T | null =
|
|
111
|
+
findById && typeof findById === 'function'
|
|
112
|
+
? await findById(req.params.id as string)
|
|
113
|
+
: await (model as any).findByPk(req.params.id);
|
|
111
114
|
if (doc && doc[field as keyof T]) {
|
|
112
115
|
const customer = await Customer.findOne({ where: { did: req.user.did } });
|
|
113
116
|
req.doc = doc;
|
|
@@ -1010,7 +1010,6 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
1012
1012
|
const checkoutSession = req.doc as CheckoutSession;
|
|
1013
|
-
logger.info('---checkoutSession---', checkoutSession.line_items);
|
|
1014
1013
|
if (checkoutSession.line_items) {
|
|
1015
1014
|
try {
|
|
1016
1015
|
await validateInventory(checkoutSession.line_items);
|
|
@@ -1091,9 +1090,15 @@ router.put('/:id/submit', user, ensureCheckoutSessionOpen, async (req, res) => {
|
|
|
1091
1090
|
} else {
|
|
1092
1091
|
const updates: Record<string, any> = {};
|
|
1093
1092
|
if (checkoutSession.customer_update?.name) {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1093
|
+
if (req.body.customer_name) {
|
|
1094
|
+
updates.name = req.body.customer_name;
|
|
1095
|
+
}
|
|
1096
|
+
if (req.body.customer_email) {
|
|
1097
|
+
updates.email = req.body.customer_email;
|
|
1098
|
+
}
|
|
1099
|
+
if (req.body.customer_phone) {
|
|
1100
|
+
updates.phone = req.body.customer_phone;
|
|
1101
|
+
}
|
|
1097
1102
|
}
|
|
1098
1103
|
if (checkoutSession.customer_update?.address) {
|
|
1099
1104
|
updates.address = Customer.formatUpdateAddress(req.body.billing_address, customer);
|
|
@@ -10,6 +10,7 @@ import { authenticate } from '../libs/security';
|
|
|
10
10
|
import { CreditGrant, Customer, PaymentCurrency, Price, Subscription } from '../store/models';
|
|
11
11
|
import { createCreditGrant } from '../libs/credit-grant';
|
|
12
12
|
import { getMeterPriceIdsFromSubscription } from '../libs/subscription';
|
|
13
|
+
import { blocklet } from '../libs/auth';
|
|
13
14
|
|
|
14
15
|
const router = Router();
|
|
15
16
|
const auth = authenticate<CreditGrant>({ component: true, roles: ['owner', 'admin'] });
|
|
@@ -179,9 +180,30 @@ router.post('/', auth, async (req, res) => {
|
|
|
179
180
|
return res.status(404).json({ error: `PaymentCurrency ${currencyId} not found` });
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
|
|
183
|
+
let customer = await Customer.findByPkOrDid(req.body.customer_id);
|
|
183
184
|
if (!customer) {
|
|
184
|
-
|
|
185
|
+
const { user: userInfo } = await blocklet.getUser(req.body.customer_id);
|
|
186
|
+
if (!userInfo) {
|
|
187
|
+
return res.status(404).json({ error: `User ${req.body.customer_id} not found` });
|
|
188
|
+
}
|
|
189
|
+
customer = await Customer.create({
|
|
190
|
+
livemode: true,
|
|
191
|
+
did: userInfo.did,
|
|
192
|
+
name: userInfo.fullName,
|
|
193
|
+
email: userInfo.email || '',
|
|
194
|
+
phone: userInfo.phone || '',
|
|
195
|
+
address: Customer.formatAddressFromUser(userInfo),
|
|
196
|
+
description: userInfo.remark || '',
|
|
197
|
+
metadata: {},
|
|
198
|
+
balance: '0',
|
|
199
|
+
next_invoice_sequence: 1,
|
|
200
|
+
delinquent: false,
|
|
201
|
+
invoice_prefix: Customer.getInvoicePrefix(),
|
|
202
|
+
});
|
|
203
|
+
logger.info('Customer created on credit grant', {
|
|
204
|
+
customerId: customer.id,
|
|
205
|
+
customer: customer.toJSON(),
|
|
206
|
+
});
|
|
185
207
|
}
|
|
186
208
|
|
|
187
209
|
const unitAmount = fromTokenToUnit(req.body.amount, paymentCurrency.decimal).toString();
|
|
@@ -37,6 +37,7 @@ const authPortal = authenticate<Customer>({
|
|
|
37
37
|
// @ts-ignore
|
|
38
38
|
model: Customer,
|
|
39
39
|
field: 'id',
|
|
40
|
+
findById: (id: string) => Customer.findByPkOrDid(id),
|
|
40
41
|
},
|
|
41
42
|
});
|
|
42
43
|
|
|
@@ -217,18 +218,12 @@ router.post('/sync-to-space', sessionMiddleware(), async (req, res) => {
|
|
|
217
218
|
});
|
|
218
219
|
|
|
219
220
|
// get overdue invoices
|
|
220
|
-
router.get('/:id/overdue/invoices',
|
|
221
|
-
if (!req.user) {
|
|
222
|
-
return res.status(403).json({ error: 'Unauthorized' });
|
|
223
|
-
}
|
|
221
|
+
router.get('/:id/overdue/invoices', authPortal, async (req, res) => {
|
|
224
222
|
try {
|
|
225
223
|
const doc = await Customer.findByPkOrDid(req.params.id as string);
|
|
226
224
|
if (!doc) {
|
|
227
225
|
return res.status(404).json({ error: 'Customer not found' });
|
|
228
226
|
}
|
|
229
|
-
if (doc.did !== req.user.did && !['admin', 'owner'].includes(req.user?.role)) {
|
|
230
|
-
return res.status(403).json({ error: 'You are not allowed to access this customer invoices' });
|
|
231
|
-
}
|
|
232
227
|
const { rows: invoices, count } = await Invoice.findAndCountAll({
|
|
233
228
|
where: {
|
|
234
229
|
customer_id: doc.id,
|
|
@@ -84,6 +84,7 @@ router.post('/', auth, async (req, res) => {
|
|
|
84
84
|
symbol: info.symbol,
|
|
85
85
|
decimal: info.decimal,
|
|
86
86
|
type: 'standard',
|
|
87
|
+
maximum_precision: 6,
|
|
87
88
|
|
|
88
89
|
// FIXME: make these configurable
|
|
89
90
|
minimum_payment_amount: fromTokenToUnit(0.000001, info.decimal).toString(),
|
|
@@ -121,6 +122,7 @@ router.post('/', auth, async (req, res) => {
|
|
|
121
122
|
symbol: state.symbol,
|
|
122
123
|
decimal: state.decimal,
|
|
123
124
|
type: 'standard',
|
|
125
|
+
maximum_precision: 6,
|
|
124
126
|
|
|
125
127
|
// FIXME: make these configurable
|
|
126
128
|
minimum_payment_amount: fromTokenToUnit(0.000001, state.decimal).toString(),
|
|
@@ -173,6 +173,7 @@ router.post('/', auth, async (req, res) => {
|
|
|
173
173
|
decimal: 18,
|
|
174
174
|
|
|
175
175
|
minimum_payment_amount: fromTokenToUnit(0.000001, 18).toString(),
|
|
176
|
+
maximum_precision: 6,
|
|
176
177
|
maximum_payment_amount: fromTokenToUnit(100000000, 18).toString(),
|
|
177
178
|
|
|
178
179
|
contract: '',
|
|
@@ -21,9 +21,6 @@ export const up: Migration = async ({ context }) => {
|
|
|
21
21
|
},
|
|
22
22
|
],
|
|
23
23
|
});
|
|
24
|
-
await context.sequelize.query(`
|
|
25
|
-
UPDATE payment_currencies SET maximum_precision = 2 WHERE type = 'standard';
|
|
26
|
-
`);
|
|
27
24
|
|
|
28
25
|
await context.sequelize.query(`
|
|
29
26
|
UPDATE payment_currencies
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Migration } from '../migrate';
|
|
2
|
+
|
|
3
|
+
export const up: Migration = async ({ context }) => {
|
|
4
|
+
await context.sequelize.query(`
|
|
5
|
+
UPDATE payment_currencies
|
|
6
|
+
SET maximum_precision = 6
|
|
7
|
+
WHERE payment_method_id IN (
|
|
8
|
+
SELECT id FROM payment_methods WHERE type IN ('arcblock', 'ethereum', 'base')
|
|
9
|
+
)
|
|
10
|
+
AND type != 'credit';
|
|
11
|
+
`);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const down = () => {};
|
|
@@ -42,7 +42,7 @@ export class WebhookAttempt extends Model<InferAttributes<WebhookAttempt>, Infer
|
|
|
42
42
|
allowNull: false,
|
|
43
43
|
},
|
|
44
44
|
status: {
|
|
45
|
-
type: DataTypes.ENUM('
|
|
45
|
+
type: DataTypes.ENUM('succeeded', 'failed'),
|
|
46
46
|
allowNull: false,
|
|
47
47
|
},
|
|
48
48
|
response_status: {
|
package/blocklet.yml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.3",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -43,31 +43,31 @@
|
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@abtnode/cron": "^1.16.
|
|
47
|
-
"@arcblock/did": "^1.20.
|
|
46
|
+
"@abtnode/cron": "^1.16.45",
|
|
47
|
+
"@arcblock/did": "^1.20.15",
|
|
48
48
|
"@arcblock/did-auth-storage-nedb": "^1.7.1",
|
|
49
|
-
"@arcblock/did-connect": "^3.0.
|
|
50
|
-
"@arcblock/did-util": "^1.20.
|
|
51
|
-
"@arcblock/jwt": "^1.20.
|
|
52
|
-
"@arcblock/ux": "^3.0.
|
|
53
|
-
"@arcblock/validator": "^1.20.
|
|
54
|
-
"@blocklet/did-space-js": "^1.
|
|
55
|
-
"@blocklet/js-sdk": "^1.16.
|
|
56
|
-
"@blocklet/logger": "^1.16.
|
|
57
|
-
"@blocklet/payment-react": "1.19.
|
|
58
|
-
"@blocklet/sdk": "^1.16.
|
|
59
|
-
"@blocklet/ui-react": "^3.0.
|
|
60
|
-
"@blocklet/uploader": "^0.
|
|
61
|
-
"@blocklet/xss": "^0.
|
|
49
|
+
"@arcblock/did-connect": "^3.0.22",
|
|
50
|
+
"@arcblock/did-util": "^1.20.15",
|
|
51
|
+
"@arcblock/jwt": "^1.20.15",
|
|
52
|
+
"@arcblock/ux": "^3.0.22",
|
|
53
|
+
"@arcblock/validator": "^1.20.15",
|
|
54
|
+
"@blocklet/did-space-js": "^1.1.5",
|
|
55
|
+
"@blocklet/js-sdk": "^1.16.45",
|
|
56
|
+
"@blocklet/logger": "^1.16.45",
|
|
57
|
+
"@blocklet/payment-react": "1.19.3",
|
|
58
|
+
"@blocklet/sdk": "^1.16.45",
|
|
59
|
+
"@blocklet/ui-react": "^3.0.22",
|
|
60
|
+
"@blocklet/uploader": "^0.2.4",
|
|
61
|
+
"@blocklet/xss": "^0.2.2",
|
|
62
62
|
"@mui/icons-material": "^7.1.2",
|
|
63
63
|
"@mui/lab": "7.0.0-beta.14",
|
|
64
64
|
"@mui/material": "^7.1.2",
|
|
65
65
|
"@mui/system": "^7.1.1",
|
|
66
|
-
"@ocap/asset": "^1.20.
|
|
67
|
-
"@ocap/client": "^1.20.
|
|
68
|
-
"@ocap/mcrypto": "^1.20.
|
|
69
|
-
"@ocap/util": "^1.20.
|
|
70
|
-
"@ocap/wallet": "^1.20.
|
|
66
|
+
"@ocap/asset": "^1.20.15",
|
|
67
|
+
"@ocap/client": "^1.20.15",
|
|
68
|
+
"@ocap/mcrypto": "^1.20.15",
|
|
69
|
+
"@ocap/util": "^1.20.15",
|
|
70
|
+
"@ocap/wallet": "^1.20.15",
|
|
71
71
|
"@stripe/react-stripe-js": "^2.9.0",
|
|
72
72
|
"@stripe/stripe-js": "^2.4.0",
|
|
73
73
|
"ahooks": "^3.8.5",
|
|
@@ -120,9 +120,9 @@
|
|
|
120
120
|
"web3": "^4.16.0"
|
|
121
121
|
},
|
|
122
122
|
"devDependencies": {
|
|
123
|
-
"@abtnode/types": "^1.16.
|
|
123
|
+
"@abtnode/types": "^1.16.45",
|
|
124
124
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
125
|
-
"@blocklet/payment-types": "1.19.
|
|
125
|
+
"@blocklet/payment-types": "1.19.3",
|
|
126
126
|
"@types/cookie-parser": "^1.4.9",
|
|
127
127
|
"@types/cors": "^2.8.19",
|
|
128
128
|
"@types/debug": "^4.1.12",
|
|
@@ -152,7 +152,7 @@
|
|
|
152
152
|
"vite": "^7.0.0",
|
|
153
153
|
"vite-node": "^3.2.4",
|
|
154
154
|
"vite-plugin-babel-import": "^2.0.5",
|
|
155
|
-
"vite-plugin-blocklet": "^0.
|
|
155
|
+
"vite-plugin-blocklet": "^0.10.1",
|
|
156
156
|
"vite-plugin-node-polyfills": "^0.23.0",
|
|
157
157
|
"vite-plugin-svgr": "^4.3.0",
|
|
158
158
|
"vite-tsconfig-paths": "^5.1.4",
|
|
@@ -168,5 +168,5 @@
|
|
|
168
168
|
"parser": "typescript"
|
|
169
169
|
}
|
|
170
170
|
},
|
|
171
|
-
"gitHead": "
|
|
171
|
+
"gitHead": "f274edb338c8f5a23ae46d9f7fea8458a6001cf7"
|
|
172
172
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
+
import Toast from '@arcblock/ux/lib/Toast';
|
|
3
|
+
import { formatError } from '@blocklet/payment-react';
|
|
4
|
+
import { Check, Close, EditOutlined } from '@mui/icons-material';
|
|
5
|
+
import { IconButton, Stack, TextField, Typography } from '@mui/material';
|
|
6
|
+
import { useState, useEffect } from 'react';
|
|
7
|
+
|
|
8
|
+
interface EditInLineProps {
|
|
9
|
+
value: string;
|
|
10
|
+
onSave: (newValue: string) => Promise<void>;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
multiline?: boolean;
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
validate?: (value: string) => string | null;
|
|
17
|
+
successMessage?: string;
|
|
18
|
+
autoFocus?: boolean;
|
|
19
|
+
onEdit?: () => void;
|
|
20
|
+
onCancel?: () => void;
|
|
21
|
+
hideSuccessToast?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default function EditInLine({
|
|
25
|
+
value,
|
|
26
|
+
onSave,
|
|
27
|
+
placeholder = '',
|
|
28
|
+
required = true,
|
|
29
|
+
disabled = false,
|
|
30
|
+
multiline = false,
|
|
31
|
+
maxLength = 255,
|
|
32
|
+
validate = () => null,
|
|
33
|
+
successMessage = '',
|
|
34
|
+
autoFocus = false,
|
|
35
|
+
onEdit = () => {},
|
|
36
|
+
onCancel = () => {},
|
|
37
|
+
hideSuccessToast = false,
|
|
38
|
+
}: EditInLineProps) {
|
|
39
|
+
const [edit, setEdit] = useState(false);
|
|
40
|
+
const [tempValue, setTempValue] = useState(value);
|
|
41
|
+
const [error, setError] = useState('');
|
|
42
|
+
const [loading, setLoading] = useState(false);
|
|
43
|
+
const { t } = useLocaleContext();
|
|
44
|
+
|
|
45
|
+
// 当外部 value 变化时同步更新
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (!edit) {
|
|
48
|
+
setTempValue(value);
|
|
49
|
+
}
|
|
50
|
+
}, [value, edit]);
|
|
51
|
+
|
|
52
|
+
const validateValue = (val: string): string => {
|
|
53
|
+
if (required && !val.trim()) {
|
|
54
|
+
return t('common.required');
|
|
55
|
+
}
|
|
56
|
+
if (maxLength && val.length > maxLength) {
|
|
57
|
+
return t('common.maxLength', { len: maxLength });
|
|
58
|
+
}
|
|
59
|
+
if (validate) {
|
|
60
|
+
const customError = validate(val);
|
|
61
|
+
if (customError) {
|
|
62
|
+
return customError;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return '';
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const handleSave = async () => {
|
|
69
|
+
const validationError = validateValue(tempValue);
|
|
70
|
+
if (validationError) {
|
|
71
|
+
setError(validationError);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
setLoading(true);
|
|
76
|
+
try {
|
|
77
|
+
await onSave(tempValue);
|
|
78
|
+
if (!hideSuccessToast) {
|
|
79
|
+
Toast.success(successMessage || t('common.saved'));
|
|
80
|
+
}
|
|
81
|
+
setEdit(false);
|
|
82
|
+
setError('');
|
|
83
|
+
} catch (err) {
|
|
84
|
+
Toast.error(formatError(err));
|
|
85
|
+
} finally {
|
|
86
|
+
setLoading(false);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const handleCancel = () => {
|
|
91
|
+
setTempValue(value);
|
|
92
|
+
setEdit(false);
|
|
93
|
+
setError('');
|
|
94
|
+
onCancel?.();
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleChange = (newValue: string) => {
|
|
98
|
+
setTempValue(newValue);
|
|
99
|
+
const validationError = validateValue(newValue);
|
|
100
|
+
setError(validationError);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handleEdit = () => {
|
|
104
|
+
setEdit(true);
|
|
105
|
+
setTempValue(value);
|
|
106
|
+
setError('');
|
|
107
|
+
onEdit?.();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
if (disabled) {
|
|
111
|
+
return (
|
|
112
|
+
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
113
|
+
{value || <span style={{ fontStyle: 'italic' }}>{placeholder}</span>}
|
|
114
|
+
</Typography>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<Stack
|
|
120
|
+
direction="row"
|
|
121
|
+
spacing={1}
|
|
122
|
+
sx={{
|
|
123
|
+
alignItems: multiline ? 'flex-start' : 'center',
|
|
124
|
+
flexWrap: 'wrap',
|
|
125
|
+
width: '100%',
|
|
126
|
+
}}>
|
|
127
|
+
{edit ? (
|
|
128
|
+
<>
|
|
129
|
+
<TextField
|
|
130
|
+
value={tempValue}
|
|
131
|
+
onChange={(e) => handleChange(e.target.value)}
|
|
132
|
+
variant="outlined"
|
|
133
|
+
size="small"
|
|
134
|
+
multiline={multiline}
|
|
135
|
+
rows={multiline ? 3 : 1}
|
|
136
|
+
sx={{ flex: 1, minWidth: '200px' }}
|
|
137
|
+
placeholder={placeholder}
|
|
138
|
+
error={!!error}
|
|
139
|
+
disabled={loading}
|
|
140
|
+
autoFocus={autoFocus}
|
|
141
|
+
slotProps={{
|
|
142
|
+
input: {
|
|
143
|
+
endAdornment: error ? (
|
|
144
|
+
<Typography color="error" sx={{ whiteSpace: 'nowrap' }}>
|
|
145
|
+
{error}
|
|
146
|
+
</Typography>
|
|
147
|
+
) : undefined,
|
|
148
|
+
},
|
|
149
|
+
htmlInput: {
|
|
150
|
+
maxLength,
|
|
151
|
+
},
|
|
152
|
+
}}
|
|
153
|
+
onKeyDown={(e) => {
|
|
154
|
+
if (e.key === 'Enter' && !multiline && !e.shiftKey) {
|
|
155
|
+
e.preventDefault();
|
|
156
|
+
handleSave();
|
|
157
|
+
}
|
|
158
|
+
if (e.key === 'Escape') {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
handleCancel();
|
|
161
|
+
}
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
<Stack direction="row" spacing={0.5}>
|
|
165
|
+
<IconButton
|
|
166
|
+
onClick={handleSave}
|
|
167
|
+
size="small"
|
|
168
|
+
disabled={!!error || loading}
|
|
169
|
+
color="primary"
|
|
170
|
+
title={t('common.save')}>
|
|
171
|
+
<Check />
|
|
172
|
+
</IconButton>
|
|
173
|
+
<IconButton onClick={handleCancel} size="small" disabled={loading} title={t('common.cancel')}>
|
|
174
|
+
<Close />
|
|
175
|
+
</IconButton>
|
|
176
|
+
</Stack>
|
|
177
|
+
</>
|
|
178
|
+
) : (
|
|
179
|
+
<>
|
|
180
|
+
<Typography
|
|
181
|
+
variant="body2"
|
|
182
|
+
sx={{
|
|
183
|
+
wordBreak: 'break-word',
|
|
184
|
+
whiteSpace: multiline ? 'pre-wrap' : 'nowrap',
|
|
185
|
+
overflow: multiline ? 'visible' : 'hidden',
|
|
186
|
+
textOverflow: multiline ? 'clip' : 'ellipsis',
|
|
187
|
+
}}>
|
|
188
|
+
{value || <span style={{ color: '#999', fontStyle: 'italic' }}>{placeholder}</span>}
|
|
189
|
+
</Typography>
|
|
190
|
+
<IconButton onClick={handleEdit} size="small" title={t('common.edit')}>
|
|
191
|
+
<EditOutlined fontSize="small" />
|
|
192
|
+
</IconButton>
|
|
193
|
+
</>
|
|
194
|
+
)}
|
|
195
|
+
</Stack>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
@@ -3,8 +3,6 @@ import { ConfirmDialog, Switch, api, formatError, usePaymentContext } from '@blo
|
|
|
3
3
|
import type { ChainType, TPaymentCurrency, TPaymentMethodExpanded } from '@blocklet/payment-types';
|
|
4
4
|
import {
|
|
5
5
|
AddOutlined,
|
|
6
|
-
Check,
|
|
7
|
-
Close,
|
|
8
6
|
DeleteOutlined,
|
|
9
7
|
EditOutlined,
|
|
10
8
|
InfoOutlined,
|
|
@@ -27,7 +25,6 @@ import {
|
|
|
27
25
|
ListItemText,
|
|
28
26
|
Skeleton,
|
|
29
27
|
Stack,
|
|
30
|
-
TextField,
|
|
31
28
|
Tooltip,
|
|
32
29
|
Typography,
|
|
33
30
|
useTheme,
|
|
@@ -35,7 +32,6 @@ import {
|
|
|
35
32
|
import { useRequest, useSessionStorageState, useSetState } from 'ahooks';
|
|
36
33
|
import useBus from 'use-bus';
|
|
37
34
|
|
|
38
|
-
import { useState } from 'react';
|
|
39
35
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
40
36
|
import { DIDDialog } from '@arcblock/ux/lib/DID';
|
|
41
37
|
import { fromUnitToToken } from '@ocap/util';
|
|
@@ -45,6 +41,7 @@ import InfoCard from '../../../../components/info-card';
|
|
|
45
41
|
import InfoRow from '../../../../components/info-row';
|
|
46
42
|
import PaymentCurrencyAdd from '../../../../components/payment-currency/add';
|
|
47
43
|
import PaymentCurrencyEdit from '../../../../components/payment-currency/edit';
|
|
44
|
+
import EditInLine from '../../../../components/edit-in-line';
|
|
48
45
|
import { useRpcStatus } from '../../../../hooks/rpc-status';
|
|
49
46
|
import PaymentMethodEdit from './edit';
|
|
50
47
|
|
|
@@ -62,94 +59,51 @@ const getMethods = (
|
|
|
62
59
|
return api.get(`/api/payment-methods?${search.toString()}`).then((res) => res.data);
|
|
63
60
|
};
|
|
64
61
|
|
|
65
|
-
const updateApiHost = (id: string,
|
|
66
|
-
return api.put(`/api/payment-methods/${id}/settings`, { arcblock:
|
|
62
|
+
const updateApiHost = (id: string, params: Record<string, any>) => {
|
|
63
|
+
return api.put(`/api/payment-methods/${id}/settings`, { arcblock: params }).then((res) => res.data);
|
|
67
64
|
};
|
|
68
65
|
|
|
69
|
-
function EditApiHost({ method }: { method: TPaymentMethodExpanded }) {
|
|
70
|
-
const [edit, setEdit] = useState(false);
|
|
66
|
+
function EditApiHost({ method, refresh }: { method: TPaymentMethodExpanded; refresh: () => void }) {
|
|
71
67
|
const { t } = useLocaleContext();
|
|
72
|
-
const [value, setValue] = useState(method.settings?.arcblock?.api_host || '');
|
|
73
|
-
const [tempValue, setTempValue] = useState(value);
|
|
74
|
-
const [error, setError] = useState('');
|
|
75
68
|
|
|
76
|
-
const handleSave = async () => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
await updateApiHost(method.id, tempValue);
|
|
83
|
-
Toast.success(t('common.saved'));
|
|
84
|
-
setValue(tempValue);
|
|
85
|
-
setEdit(false);
|
|
86
|
-
setError('');
|
|
87
|
-
} catch (err) {
|
|
88
|
-
Toast.error(formatError(err));
|
|
89
|
-
}
|
|
69
|
+
const handleSave = async (newValue: string) => {
|
|
70
|
+
await updateApiHost(method.id, { api_host: newValue });
|
|
71
|
+
refresh();
|
|
90
72
|
};
|
|
91
73
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
74
|
+
return (
|
|
75
|
+
<InfoRow
|
|
76
|
+
label={t('admin.paymentMethod.arcblock.api_host.label')}
|
|
77
|
+
value={
|
|
78
|
+
<EditInLine
|
|
79
|
+
value={method.settings?.arcblock?.api_host || ''}
|
|
80
|
+
onSave={handleSave}
|
|
81
|
+
placeholder={t('admin.paymentMethod.arcblock.api_host.tip')}
|
|
82
|
+
required
|
|
83
|
+
/>
|
|
84
|
+
}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function EditApiExplorerHost({ method, refresh }: { method: TPaymentMethodExpanded; refresh: () => void }) {
|
|
90
|
+
const { t } = useLocaleContext();
|
|
91
|
+
|
|
92
|
+
const handleSave = async (newValue: string) => {
|
|
93
|
+
await updateApiHost(method.id, { explorer_host: newValue });
|
|
94
|
+
refresh();
|
|
96
95
|
};
|
|
97
96
|
|
|
98
97
|
return (
|
|
99
98
|
<InfoRow
|
|
100
|
-
label={t('admin.paymentMethod.arcblock.
|
|
99
|
+
label={t('admin.paymentMethod.arcblock.explorer_host.label')}
|
|
101
100
|
value={
|
|
102
|
-
<
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}}>
|
|
109
|
-
{edit ? (
|
|
110
|
-
<>
|
|
111
|
-
<TextField
|
|
112
|
-
value={tempValue}
|
|
113
|
-
onChange={(e) => {
|
|
114
|
-
const val = e.target.value;
|
|
115
|
-
if (!val) {
|
|
116
|
-
setError(t('common.required'));
|
|
117
|
-
} else {
|
|
118
|
-
setError('');
|
|
119
|
-
}
|
|
120
|
-
setTempValue(val);
|
|
121
|
-
}}
|
|
122
|
-
variant="outlined"
|
|
123
|
-
size="small"
|
|
124
|
-
sx={{ flex: 1 }}
|
|
125
|
-
placeholder={t('admin.paymentMethod.arcblock.api_host.tip')}
|
|
126
|
-
error={!!error}
|
|
127
|
-
slotProps={{
|
|
128
|
-
input: {
|
|
129
|
-
endAdornment: error ? (
|
|
130
|
-
<Typography color="error" sx={{ whiteSpace: 'nowrap' }}>
|
|
131
|
-
{error}
|
|
132
|
-
</Typography>
|
|
133
|
-
) : undefined,
|
|
134
|
-
},
|
|
135
|
-
}}
|
|
136
|
-
/>
|
|
137
|
-
<IconButton onClick={handleSave} size="small">
|
|
138
|
-
<Check />
|
|
139
|
-
</IconButton>
|
|
140
|
-
<IconButton onClick={handleCancel} size="small">
|
|
141
|
-
<Close />
|
|
142
|
-
</IconButton>
|
|
143
|
-
</>
|
|
144
|
-
) : (
|
|
145
|
-
<>
|
|
146
|
-
<Typography variant="body2">{value}</Typography>
|
|
147
|
-
<IconButton onClick={() => setEdit(true)} size="small">
|
|
148
|
-
<EditOutlined fontSize="small" />
|
|
149
|
-
</IconButton>
|
|
150
|
-
</>
|
|
151
|
-
)}
|
|
152
|
-
</Stack>
|
|
101
|
+
<EditInLine
|
|
102
|
+
value={method.settings?.arcblock?.explorer_host || ''}
|
|
103
|
+
onSave={handleSave}
|
|
104
|
+
placeholder={t('admin.paymentMethod.arcblock.explorer_host.tip')}
|
|
105
|
+
required
|
|
106
|
+
/>
|
|
153
107
|
}
|
|
154
108
|
/>
|
|
155
109
|
);
|
|
@@ -219,11 +173,13 @@ function Balance({
|
|
|
219
173
|
balances,
|
|
220
174
|
addresses,
|
|
221
175
|
setDidDialog,
|
|
176
|
+
refresh,
|
|
222
177
|
}: {
|
|
223
178
|
method: TPaymentMethodExpanded;
|
|
224
179
|
balances: { [currencyId: string]: string };
|
|
225
180
|
addresses: { arcblock: string; ethereum: string };
|
|
226
181
|
setDidDialog: (value: any) => void;
|
|
182
|
+
refresh: () => void;
|
|
227
183
|
}) {
|
|
228
184
|
const { t } = useLocaleContext();
|
|
229
185
|
const defaultCurrency = (method.payment_currencies || [])?.find(
|
|
@@ -252,7 +208,11 @@ function Balance({
|
|
|
252
208
|
};
|
|
253
209
|
return (
|
|
254
210
|
<>
|
|
255
|
-
|
|
211
|
+
{method.type === 'arcblock' ? (
|
|
212
|
+
<EditApiExplorerHost method={method} refresh={refresh} />
|
|
213
|
+
) : (
|
|
214
|
+
<InfoRow label={t('admin.paymentMethod.props.explorer_host')} value={explorerHost} />
|
|
215
|
+
)}
|
|
256
216
|
<InfoRow
|
|
257
217
|
label={
|
|
258
218
|
<Box
|
|
@@ -461,6 +421,11 @@ export default function PaymentMethods() {
|
|
|
461
421
|
}
|
|
462
422
|
};
|
|
463
423
|
|
|
424
|
+
const handleRefresh = () => {
|
|
425
|
+
runAsync();
|
|
426
|
+
refresh(true);
|
|
427
|
+
};
|
|
428
|
+
|
|
464
429
|
return (
|
|
465
430
|
<>
|
|
466
431
|
{Object.keys(groups).map((x) => (
|
|
@@ -513,7 +478,7 @@ export default function PaymentMethods() {
|
|
|
513
478
|
container
|
|
514
479
|
spacing={2}
|
|
515
480
|
sx={{
|
|
516
|
-
mt:
|
|
481
|
+
mt: 1,
|
|
517
482
|
}}>
|
|
518
483
|
<Grid
|
|
519
484
|
size={{
|
|
@@ -521,10 +486,16 @@ export default function PaymentMethods() {
|
|
|
521
486
|
md: 6,
|
|
522
487
|
}}>
|
|
523
488
|
<InfoRow label={t('admin.paymentMethod.props.type')} value={method.type} />
|
|
524
|
-
{method.type === 'arcblock' && <EditApiHost method={method} />}
|
|
489
|
+
{method.type === 'arcblock' && <EditApiHost method={method} refresh={handleRefresh} />}
|
|
525
490
|
{['ethereum', 'base'].includes(method.type) && <RpcStatus method={method} />}
|
|
526
491
|
{['arcblock', 'ethereum', 'base'].includes(method.type) && (
|
|
527
|
-
<Balance
|
|
492
|
+
<Balance
|
|
493
|
+
method={method}
|
|
494
|
+
balances={balances}
|
|
495
|
+
addresses={addresses}
|
|
496
|
+
setDidDialog={setDidDialog}
|
|
497
|
+
refresh={handleRefresh}
|
|
498
|
+
/>
|
|
528
499
|
)}
|
|
529
500
|
|
|
530
501
|
<InfoRow label={t('admin.paymentMethod.props.confirmation')} value={method.confirmation.type} />
|
|
@@ -260,7 +260,7 @@ export default function BalanceRechargePage() {
|
|
|
260
260
|
if (Number(value) > MAX_SAFE_AMOUNT) {
|
|
261
261
|
return;
|
|
262
262
|
}
|
|
263
|
-
const precision = currency.
|
|
263
|
+
const precision = currency.maximum_precision;
|
|
264
264
|
const errorMessage = formatAmountPrecisionLimit(value, locale, precision || 6);
|
|
265
265
|
setAmountError(errorMessage || '');
|
|
266
266
|
setAmount(value);
|