payment-kit 1.19.17 → 1.19.19
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/index.ts +3 -1
- package/api/src/integrations/ethereum/tx.ts +11 -0
- package/api/src/integrations/stripe/handlers/invoice.ts +26 -6
- package/api/src/integrations/stripe/handlers/setup-intent.ts +34 -2
- package/api/src/integrations/stripe/resource.ts +185 -1
- package/api/src/libs/invoice.ts +2 -1
- package/api/src/libs/notification/template/customer-credit-low-balance.ts +155 -0
- package/api/src/libs/session.ts +6 -1
- package/api/src/libs/ws.ts +3 -2
- package/api/src/locales/en.ts +6 -6
- package/api/src/locales/zh.ts +4 -4
- package/api/src/queues/auto-recharge.ts +343 -0
- package/api/src/queues/credit-consume.ts +51 -1
- package/api/src/queues/credit-grant.ts +15 -0
- package/api/src/queues/notification.ts +16 -13
- package/api/src/queues/payment.ts +14 -1
- package/api/src/queues/space.ts +1 -0
- package/api/src/routes/auto-recharge-configs.ts +454 -0
- package/api/src/routes/connect/auto-recharge-auth.ts +182 -0
- package/api/src/routes/connect/recharge-account.ts +72 -10
- package/api/src/routes/connect/setup.ts +5 -3
- package/api/src/routes/connect/shared.ts +45 -4
- package/api/src/routes/customers.ts +10 -6
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/invoices.ts +10 -1
- package/api/src/routes/meter-events.ts +1 -1
- package/api/src/routes/meters.ts +1 -1
- package/api/src/routes/payment-currencies.ts +129 -0
- package/api/src/store/migrate.ts +20 -0
- package/api/src/store/migrations/20250821-auto-recharge-config.ts +38 -0
- package/api/src/store/models/auto-recharge-config.ts +225 -0
- package/api/src/store/models/credit-grant.ts +2 -11
- package/api/src/store/models/customer.ts +1 -0
- package/api/src/store/models/index.ts +3 -0
- package/api/src/store/models/invoice.ts +2 -1
- package/api/src/store/models/payment-currency.ts +10 -2
- package/api/src/store/models/types.ts +12 -1
- package/blocklet.yml +3 -3
- package/package.json +18 -18
- package/src/components/currency.tsx +3 -1
- package/src/components/customer/credit-overview.tsx +103 -18
- package/src/components/customer/overdraft-protection.tsx +5 -5
- package/src/components/info-metric.tsx +11 -2
- package/src/components/invoice/recharge.tsx +8 -2
- package/src/components/metadata/form.tsx +29 -27
- package/src/components/meter/form.tsx +1 -2
- package/src/components/price/form.tsx +39 -26
- package/src/components/product/form.tsx +1 -2
- package/src/components/subscription/items/index.tsx +8 -2
- package/src/components/subscription/metrics.tsx +5 -1
- package/src/locales/en.tsx +15 -0
- package/src/locales/zh.tsx +14 -0
- package/src/pages/admin/billing/meters/detail.tsx +18 -0
- package/src/pages/admin/customers/customers/credit-grant/detail.tsx +10 -0
- package/src/pages/admin/products/prices/actions.tsx +42 -2
- package/src/pages/admin/products/products/create.tsx +1 -2
- package/src/pages/admin/settings/vault-config/edit-form.tsx +8 -8
- package/src/pages/customer/credit-grant/detail.tsx +9 -1
- package/src/pages/customer/recharge/account.tsx +14 -7
- package/src/pages/customer/recharge/subscription.tsx +4 -4
- package/src/pages/customer/subscription/detail.tsx +6 -1
- package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +0 -151
|
@@ -598,11 +598,11 @@ export default function OverdraftProtectionDialog({
|
|
|
598
598
|
<Currency logo={currency.logo} name={currency.symbol} />
|
|
599
599
|
</Box>
|
|
600
600
|
),
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
601
|
+
},
|
|
602
|
+
htmlInput: {
|
|
603
|
+
min: 0,
|
|
604
|
+
max: MAX_SAFE_AMOUNT,
|
|
605
|
+
step: Number(estimateAmount),
|
|
606
606
|
},
|
|
607
607
|
}}
|
|
608
608
|
/>
|
|
@@ -31,7 +31,16 @@ export default function InfoMetric(rawProps: Props) {
|
|
|
31
31
|
return (
|
|
32
32
|
<>
|
|
33
33
|
<Stack sx={stackSx}>
|
|
34
|
-
<Typography
|
|
34
|
+
<Typography
|
|
35
|
+
className="info-metric-label"
|
|
36
|
+
component="div"
|
|
37
|
+
variant="subtitle2"
|
|
38
|
+
mb={1}
|
|
39
|
+
color="text.primary"
|
|
40
|
+
sx={{
|
|
41
|
+
display: 'flex',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
}}>
|
|
35
44
|
{/* eslint-disable-next-line react/prop-types */}
|
|
36
45
|
{props.label}
|
|
37
46
|
{/* eslint-disable-next-line react/prop-types */}
|
|
@@ -39,7 +48,7 @@ export default function InfoMetric(rawProps: Props) {
|
|
|
39
48
|
{!!props.tip && (
|
|
40
49
|
// eslint-disable-next-line react/prop-types
|
|
41
50
|
<Tooltip title={props.tip}>
|
|
42
|
-
<InfoOutlined
|
|
51
|
+
<InfoOutlined sx={{ color: 'text.secondary', cursor: 'pointer', fontSize: 'medium', ml: 0.5 }} />
|
|
43
52
|
</Tooltip>
|
|
44
53
|
)}
|
|
45
54
|
</Typography>
|
|
@@ -65,6 +65,8 @@ type ListProps = {
|
|
|
65
65
|
};
|
|
66
66
|
currency_id?: string;
|
|
67
67
|
subscription_id?: string;
|
|
68
|
+
recharge_address?: string;
|
|
69
|
+
customer_id?: string;
|
|
68
70
|
};
|
|
69
71
|
|
|
70
72
|
const getListKey = (props: ListProps) => {
|
|
@@ -80,12 +82,14 @@ const getListKey = (props: ListProps) => {
|
|
|
80
82
|
export default function RechargeList({
|
|
81
83
|
currency_id = '',
|
|
82
84
|
subscription_id = '',
|
|
85
|
+
recharge_address = '',
|
|
86
|
+
customer_id = '',
|
|
83
87
|
features = {
|
|
84
88
|
customer: true,
|
|
85
89
|
filter: true,
|
|
86
90
|
},
|
|
87
91
|
}: ListProps) {
|
|
88
|
-
const listKey = getListKey({ currency_id, subscription_id });
|
|
92
|
+
const listKey = getListKey({ currency_id, subscription_id, recharge_address });
|
|
89
93
|
|
|
90
94
|
const { t, locale } = useLocaleContext();
|
|
91
95
|
const defaultPageSize = useDefaultPageSize(20);
|
|
@@ -106,6 +110,8 @@ export default function RechargeList({
|
|
|
106
110
|
try {
|
|
107
111
|
const res = await fetchData(subscription_id || '', currency_id || '', {
|
|
108
112
|
...search,
|
|
113
|
+
recharge_address,
|
|
114
|
+
customer_id,
|
|
109
115
|
});
|
|
110
116
|
setData(res);
|
|
111
117
|
} catch (error) {
|
|
@@ -117,7 +123,7 @@ export default function RechargeList({
|
|
|
117
123
|
|
|
118
124
|
useEffect(() => {
|
|
119
125
|
refresh();
|
|
120
|
-
}, [search, currency_id, subscription_id]);
|
|
126
|
+
}, [search, currency_id, subscription_id, recharge_address]);
|
|
121
127
|
|
|
122
128
|
const getInvoiceLink = (invoice: TInvoiceExpanded) => {
|
|
123
129
|
return {
|
|
@@ -280,34 +280,36 @@ export default function MetadataForm({
|
|
|
280
280
|
value={jsonValue}
|
|
281
281
|
onChange={(e) => handleJsonChange(e.target.value)}
|
|
282
282
|
placeholder={t('common.metadata.jsonPlaceholder')}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
<
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
<IconButton
|
|
301
|
-
onClick={handleFormatJson}
|
|
302
|
-
size="small"
|
|
303
|
-
sx={{
|
|
304
|
-
color: formatError ? 'error.main' : 'text.secondary',
|
|
283
|
+
slotProps={{
|
|
284
|
+
input: {
|
|
285
|
+
endAdornment: (
|
|
286
|
+
<InputAdornment position="end" sx={{ alignSelf: 'flex-start', mt: 1 }}>
|
|
287
|
+
<Tooltip
|
|
288
|
+
title={formatError || t('common.metadata.formatJson')}
|
|
289
|
+
placement="top"
|
|
290
|
+
componentsProps={{
|
|
291
|
+
tooltip: {
|
|
292
|
+
sx: formatError
|
|
293
|
+
? {
|
|
294
|
+
maxWidth: 300,
|
|
295
|
+
backgroundColor: 'error.main',
|
|
296
|
+
opacity: 0.8,
|
|
297
|
+
}
|
|
298
|
+
: {},
|
|
299
|
+
},
|
|
305
300
|
}}>
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
301
|
+
<IconButton
|
|
302
|
+
onClick={handleFormatJson}
|
|
303
|
+
size="small"
|
|
304
|
+
sx={{
|
|
305
|
+
color: formatError ? 'error.main' : 'text.secondary',
|
|
306
|
+
}}>
|
|
307
|
+
<FormatAlignLeft fontSize="small" />
|
|
308
|
+
</IconButton>
|
|
309
|
+
</Tooltip>
|
|
310
|
+
</InputAdornment>
|
|
311
|
+
),
|
|
312
|
+
},
|
|
311
313
|
}}
|
|
312
314
|
sx={{
|
|
313
315
|
mb: 2,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import { FormInput, FormLabel } from '@blocklet/payment-react';
|
|
2
|
+
import { FormInput, FormLabel, Collapse } from '@blocklet/payment-react';
|
|
3
3
|
import { FormControl, Select, MenuItem, Typography, Box, FormHelperText, Stack } from '@mui/material';
|
|
4
4
|
import { useFormContext, useWatch } from 'react-hook-form';
|
|
5
5
|
import { InfoOutlined } from '@mui/icons-material';
|
|
6
6
|
|
|
7
7
|
import type { TMeter } from '@blocklet/payment-types';
|
|
8
|
-
import Collapse from '../collapse';
|
|
9
8
|
import MetadataForm from '../metadata/form';
|
|
10
9
|
|
|
11
10
|
type Props = {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
FormLabel,
|
|
9
9
|
isCreditMetered,
|
|
10
10
|
formatPrice,
|
|
11
|
+
Collapse,
|
|
11
12
|
} from '@blocklet/payment-react';
|
|
12
13
|
import type {
|
|
13
14
|
InferFormType,
|
|
@@ -46,7 +47,6 @@ import { useMemo, useState } from 'react';
|
|
|
46
47
|
import { get } from 'lodash';
|
|
47
48
|
import { useRequest } from 'ahooks';
|
|
48
49
|
import ProductSelect from '../payment-link/product-select';
|
|
49
|
-
import Collapse from '../collapse';
|
|
50
50
|
import { useProductsContext } from '../../contexts/products';
|
|
51
51
|
import CurrencySelect from './currency-select';
|
|
52
52
|
import { getProductByPriceId } from '../../libs/util';
|
|
@@ -782,29 +782,33 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
782
782
|
metadata.credit_config = creditConfig;
|
|
783
783
|
field.onChange(metadata);
|
|
784
784
|
}}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
785
|
+
slotProps={{
|
|
786
|
+
input: {
|
|
787
|
+
endAdornment: (
|
|
788
|
+
<InputAdornment position="end">
|
|
789
|
+
<CurrencySelect
|
|
790
|
+
mode="selected"
|
|
791
|
+
hasSelected={(c) => currencies.fields.some((x: any) => x.currency_id === c.id)}
|
|
792
|
+
currencyFilter={(c) => c.type === 'credit'}
|
|
793
|
+
onSelect={(currencyId) => {
|
|
794
|
+
const metadata = field.value || {};
|
|
795
|
+
const creditConfig = metadata.credit_config || {};
|
|
796
|
+
creditConfig.currency_id = currencyId;
|
|
797
|
+
metadata.credit_config = creditConfig;
|
|
798
|
+
field.onChange(metadata);
|
|
799
|
+
}}
|
|
800
|
+
value={field.value?.credit_config?.currency_id || creditCurrencies?.[0]?.id}
|
|
801
|
+
disabled={isLocked}
|
|
802
|
+
hideMethod
|
|
803
|
+
selectSX={{ '.MuiOutlinedInput-notchedOutline': { border: 'none' } }}
|
|
804
|
+
/>
|
|
805
|
+
</InputAdornment>
|
|
806
|
+
),
|
|
807
|
+
},
|
|
808
|
+
htmlInput: {
|
|
809
|
+
min: 0,
|
|
810
|
+
step: 'any',
|
|
811
|
+
},
|
|
808
812
|
}}
|
|
809
813
|
/>
|
|
810
814
|
<Typography
|
|
@@ -843,7 +847,11 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
843
847
|
value={field.value ?? '0'}
|
|
844
848
|
placeholder="0"
|
|
845
849
|
disabled={isLocked}
|
|
846
|
-
|
|
850
|
+
slotProps={{
|
|
851
|
+
htmlInput: {
|
|
852
|
+
min: 0,
|
|
853
|
+
},
|
|
854
|
+
}}
|
|
847
855
|
/>
|
|
848
856
|
)}
|
|
849
857
|
/>
|
|
@@ -1000,7 +1008,12 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
1000
1008
|
metadata.credit_config = creditConfig;
|
|
1001
1009
|
field.onChange(metadata);
|
|
1002
1010
|
}}
|
|
1003
|
-
|
|
1011
|
+
slotProps={{
|
|
1012
|
+
htmlInput: {
|
|
1013
|
+
min: 0,
|
|
1014
|
+
max: 100,
|
|
1015
|
+
},
|
|
1016
|
+
}}
|
|
1004
1017
|
/>
|
|
1005
1018
|
<Typography
|
|
1006
1019
|
variant="caption"
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/* eslint-disable no-nested-ternary */
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import { FormInput, FormLabel } from '@blocklet/payment-react';
|
|
3
|
+
import { FormInput, FormLabel, Collapse } from '@blocklet/payment-react';
|
|
4
4
|
import type { InferFormType, TProduct } from '@blocklet/payment-types';
|
|
5
5
|
import { Box, Stack, Typography, Select, MenuItem } from '@mui/material';
|
|
6
6
|
import { useFormContext, useWatch, Controller } from 'react-hook-form';
|
|
7
7
|
|
|
8
|
-
import Collapse from '../collapse';
|
|
9
8
|
import MetadataForm from '../metadata/form';
|
|
10
9
|
import type { Price } from '../price/form';
|
|
11
10
|
import Uploader from '../uploader';
|
|
@@ -42,7 +42,10 @@ export default function SubscriptionItemList({ data, currency, mode = 'customer'
|
|
|
42
42
|
sm: 2,
|
|
43
43
|
},
|
|
44
44
|
}}>
|
|
45
|
-
<Box
|
|
45
|
+
<Box
|
|
46
|
+
sx={{
|
|
47
|
+
order: isMobile ? 2 : 1,
|
|
48
|
+
}}>
|
|
46
49
|
{item.price.product.images.length > 0 ? (
|
|
47
50
|
// @ts-ignore
|
|
48
51
|
<Avatar
|
|
@@ -58,7 +61,10 @@ export default function SubscriptionItemList({ data, currency, mode = 'customer'
|
|
|
58
61
|
</Avatar>
|
|
59
62
|
)}
|
|
60
63
|
</Box>
|
|
61
|
-
<Box
|
|
64
|
+
<Box
|
|
65
|
+
sx={{
|
|
66
|
+
order: isMobile ? 1 : 2,
|
|
67
|
+
}}>
|
|
62
68
|
{isAdmin ? (
|
|
63
69
|
<>
|
|
64
70
|
<Typography
|
|
@@ -169,7 +169,11 @@ export default function SubscriptionMetrics({ subscription, showBalance = true }
|
|
|
169
169
|
alignItems: 'center',
|
|
170
170
|
fontWeight: 500,
|
|
171
171
|
}}>
|
|
172
|
-
<Typography
|
|
172
|
+
<Typography
|
|
173
|
+
sx={{
|
|
174
|
+
fontWeight: 500,
|
|
175
|
+
fontSize: 14,
|
|
176
|
+
}}>
|
|
173
177
|
{t('admin.subscription.currentBalance')}
|
|
174
178
|
</Typography>
|
|
175
179
|
<Tooltip
|
package/src/locales/en.tsx
CHANGED
|
@@ -222,6 +222,11 @@ export default flat({
|
|
|
222
222
|
creditProducts: {
|
|
223
223
|
emptyTip: 'No credit products are associated with this meter yet.',
|
|
224
224
|
},
|
|
225
|
+
rechargePackage: {
|
|
226
|
+
priceId: 'Top-up Package',
|
|
227
|
+
tooltip:
|
|
228
|
+
'The top-up package is used to recharge {name}, and users can get the corresponding {name} after purchase.',
|
|
229
|
+
},
|
|
225
230
|
},
|
|
226
231
|
creditProduct: {
|
|
227
232
|
create: 'Create Credit Product',
|
|
@@ -489,6 +494,10 @@ export default flat({
|
|
|
489
494
|
description:
|
|
490
495
|
'Credit metered billing requires a corresponding Credit consumption. If the Credit is consumed, the corresponding service will stop.',
|
|
491
496
|
},
|
|
497
|
+
credit: {
|
|
498
|
+
saveAsBasePrice: 'Set as Top-up Package',
|
|
499
|
+
saveSuccess: 'Successfully set as top-up package',
|
|
500
|
+
},
|
|
492
501
|
types: {
|
|
493
502
|
onetime: 'One time',
|
|
494
503
|
onetimeDesc: 'Charge a one-time fee',
|
|
@@ -970,6 +979,7 @@ export default flat({
|
|
|
970
979
|
},
|
|
971
980
|
originalAmount: 'Original Amount',
|
|
972
981
|
usage: 'Usage',
|
|
982
|
+
viewInvoice: 'View Invoice',
|
|
973
983
|
grantId: 'Grant ID',
|
|
974
984
|
currency: 'Currency',
|
|
975
985
|
created: 'Created',
|
|
@@ -1221,6 +1231,11 @@ export default flat({
|
|
|
1221
1231
|
product: {
|
|
1222
1232
|
empty: 'No Products',
|
|
1223
1233
|
},
|
|
1234
|
+
credit: {
|
|
1235
|
+
recharge: 'Buy Credits',
|
|
1236
|
+
autoRecharge: 'Auto Top-up',
|
|
1237
|
+
unsupported: 'Not supported yet',
|
|
1238
|
+
},
|
|
1224
1239
|
recharge: {
|
|
1225
1240
|
title: 'Add Funds',
|
|
1226
1241
|
amount: 'Amount',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -221,6 +221,10 @@ export default flat({
|
|
|
221
221
|
creditProducts: {
|
|
222
222
|
emptyTip: '此计量器尚未关联任何 Credit 产品。',
|
|
223
223
|
},
|
|
224
|
+
rechargePackage: {
|
|
225
|
+
priceId: '充值套餐',
|
|
226
|
+
tooltip: '充值套餐是用于充值 {name} 的套餐,用户购买后可以获得对应的 {name}。',
|
|
227
|
+
},
|
|
224
228
|
},
|
|
225
229
|
creditProduct: {
|
|
226
230
|
create: '创建 Credit 产品',
|
|
@@ -459,6 +463,10 @@ export default flat({
|
|
|
459
463
|
pricingNote: '对于计量产品,定价会自动基于所选计量器跟踪的使用情况。',
|
|
460
464
|
description: 'Credit 计量计费的订阅需要有对应的 Credit 消费,如果 Credit 消耗完,对应的服务会停止。',
|
|
461
465
|
},
|
|
466
|
+
credit: {
|
|
467
|
+
saveAsBasePrice: '设为充值套餐',
|
|
468
|
+
saveSuccess: '设置成功',
|
|
469
|
+
},
|
|
462
470
|
types: {
|
|
463
471
|
onetime: '一次性',
|
|
464
472
|
onetimeDesc: '收取一次性费用',
|
|
@@ -937,6 +945,7 @@ export default flat({
|
|
|
937
945
|
overviewDescription: '监控所有货币的信用余额、使用情况和未偿债务。',
|
|
938
946
|
availableBalance: '可用余额',
|
|
939
947
|
usage: '使用情况',
|
|
948
|
+
viewInvoice: '查看账单',
|
|
940
949
|
viewGrants: '查看额度',
|
|
941
950
|
viewUsage: '查看使用情况',
|
|
942
951
|
recentActivity: '最近活动',
|
|
@@ -1181,6 +1190,11 @@ export default flat({
|
|
|
1181
1190
|
product: {
|
|
1182
1191
|
empty: '没有订阅产品',
|
|
1183
1192
|
},
|
|
1193
|
+
credit: {
|
|
1194
|
+
recharge: '购买额度',
|
|
1195
|
+
autoRecharge: '自动充值',
|
|
1196
|
+
unsupported: '暂不支持购买额度',
|
|
1197
|
+
},
|
|
1184
1198
|
recharge: {
|
|
1185
1199
|
title: '充值',
|
|
1186
1200
|
amount: '金额',
|
|
@@ -253,6 +253,24 @@ export default function MeterDetail(props: { id: string }) {
|
|
|
253
253
|
}}>
|
|
254
254
|
<InfoMetric label={t('common.id')} value={<Copyable text={props.id} style={{ marginLeft: 4 }} />} divider />
|
|
255
255
|
<InfoMetric label={t('common.createdAt')} value={formatTime(data.created_at)} divider />
|
|
256
|
+
{data.paymentCurrency?.recharge_config?.base_price_id && (
|
|
257
|
+
<InfoMetric
|
|
258
|
+
label={t('admin.meter.rechargePackage.priceId')}
|
|
259
|
+
tip={t('admin.meter.rechargePackage.tooltip', {
|
|
260
|
+
name: data.paymentCurrency.name,
|
|
261
|
+
})}
|
|
262
|
+
value={
|
|
263
|
+
<Button
|
|
264
|
+
variant="text"
|
|
265
|
+
size="small"
|
|
266
|
+
onClick={() => navigate(`/admin/products/${data.paymentCurrency.recharge_config!.base_price_id}`)}
|
|
267
|
+
sx={{ p: 0, minWidth: 'auto', textAlign: 'left', ml: 0.5, color: 'text.link' }}>
|
|
268
|
+
{data.paymentCurrency.recharge_config!.base_price_id}
|
|
269
|
+
</Button>
|
|
270
|
+
}
|
|
271
|
+
divider
|
|
272
|
+
/>
|
|
273
|
+
)}
|
|
256
274
|
</Stack>
|
|
257
275
|
</Box>
|
|
258
276
|
<Divider />
|
|
@@ -16,6 +16,7 @@ import { Alert, Avatar, Box, Button, CircularProgress, Divider, Stack, Typograph
|
|
|
16
16
|
import { useRequest, useSetState } from 'ahooks';
|
|
17
17
|
import { styled } from '@mui/system';
|
|
18
18
|
import { useCallback } from 'react';
|
|
19
|
+
import { useNavigate } from 'react-router-dom';
|
|
19
20
|
|
|
20
21
|
import InfoMetric from '../../../../../components/info-metric';
|
|
21
22
|
import InfoRow from '../../../../../components/info-row';
|
|
@@ -34,6 +35,7 @@ const fetchData = (id: string | undefined): Promise<TCreditGrantExpanded> => {
|
|
|
34
35
|
|
|
35
36
|
export default function AdminCreditGrantDetail({ id }: { id: string }) {
|
|
36
37
|
const { t } = useLocaleContext();
|
|
38
|
+
const navigate = useNavigate();
|
|
37
39
|
const [state, setState] = useSetState({
|
|
38
40
|
editing: {
|
|
39
41
|
metadata: false,
|
|
@@ -117,6 +119,14 @@ export default function AdminCreditGrantDetail({ id }: { id: string }) {
|
|
|
117
119
|
{t('admin.customer.creditGrants.title')}
|
|
118
120
|
</Typography>
|
|
119
121
|
</Stack>
|
|
122
|
+
{data.metadata!.invoice_id && (
|
|
123
|
+
<Button
|
|
124
|
+
variant="outlined"
|
|
125
|
+
size="small"
|
|
126
|
+
onClick={() => navigate(`/admin/billing/${data.metadata!.invoice_id}`)}>
|
|
127
|
+
{t('admin.customer.creditGrants.viewInvoice')}
|
|
128
|
+
</Button>
|
|
129
|
+
)}
|
|
120
130
|
</Stack>
|
|
121
131
|
|
|
122
132
|
<Box
|
|
@@ -2,7 +2,7 @@ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
|
2
2
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
3
3
|
import { ConfirmDialog, api, formatError } from '@blocklet/payment-react';
|
|
4
4
|
import type { TPrice, TProduct } from '@blocklet/payment-types';
|
|
5
|
-
import { useSetState } from 'ahooks';
|
|
5
|
+
import { useRequest, useSetState } from 'ahooks';
|
|
6
6
|
import noop from 'lodash/noop';
|
|
7
7
|
import { useNavigate } from 'react-router-dom';
|
|
8
8
|
|
|
@@ -18,6 +18,11 @@ type Props = {
|
|
|
18
18
|
product?: TProduct | null;
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
const getCreditCurrencyConfig = async (currencyId: string) => {
|
|
22
|
+
const res = await api.get(`/api/payment-currencies/${currencyId}/recharge-config`);
|
|
23
|
+
return res.data;
|
|
24
|
+
};
|
|
25
|
+
|
|
21
26
|
export default function PriceActions({
|
|
22
27
|
data,
|
|
23
28
|
onChange,
|
|
@@ -106,6 +111,30 @@ export default function PriceActions({
|
|
|
106
111
|
navigate(`/admin/products/pricing-tables?price_id=${data.id}`);
|
|
107
112
|
};
|
|
108
113
|
|
|
114
|
+
const creditCurrencyId = data.metadata?.credit_config?.currency_id;
|
|
115
|
+
|
|
116
|
+
const { data: creditCurrencyConfig } = useRequest(() => getCreditCurrencyConfig(creditCurrencyId), {
|
|
117
|
+
refreshDeps: [creditCurrencyId],
|
|
118
|
+
ready: !!creditCurrencyId,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const onSaveAsTopupPrice = async () => {
|
|
122
|
+
try {
|
|
123
|
+
setState({ loading: true });
|
|
124
|
+
await api.put(`/api/payment-currencies/${creditCurrencyId}/recharge-config`, {
|
|
125
|
+
base_price_id: data.id,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
Toast.success(t('admin.price.credit.saveSuccess'));
|
|
129
|
+
onChange(state.action);
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error(err);
|
|
132
|
+
Toast.error(formatError(err));
|
|
133
|
+
} finally {
|
|
134
|
+
setState({ loading: false, action: '' });
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
109
138
|
const actions = [
|
|
110
139
|
{
|
|
111
140
|
label: t('admin.pricing'),
|
|
@@ -140,7 +169,7 @@ export default function PriceActions({
|
|
|
140
169
|
},
|
|
141
170
|
{ label: t('admin.paymentLink.add'), handler: onCreatePaymentLink, color: 'primary' },
|
|
142
171
|
{ label: t('admin.pricingTable.add'), handler: onCreatePricingTable, color: 'primary', disabled: !data.recurring },
|
|
143
|
-
];
|
|
172
|
+
].filter(Boolean);
|
|
144
173
|
|
|
145
174
|
if (!setAsDefault) {
|
|
146
175
|
actions.splice(4, 0, {
|
|
@@ -152,6 +181,17 @@ export default function PriceActions({
|
|
|
152
181
|
});
|
|
153
182
|
}
|
|
154
183
|
|
|
184
|
+
if (creditCurrencyId && creditCurrencyConfig?.recharge_config?.base_price_id !== data.id) {
|
|
185
|
+
const insertIndex = !setAsDefault ? 5 : 4;
|
|
186
|
+
actions.splice(insertIndex, 0, {
|
|
187
|
+
label: t('admin.price.credit.saveAsBasePrice'),
|
|
188
|
+
handler: onSaveAsTopupPrice,
|
|
189
|
+
color: 'text.primary',
|
|
190
|
+
disabled: false,
|
|
191
|
+
divider: false,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
155
195
|
return (
|
|
156
196
|
<>
|
|
157
197
|
<Actions variant={variant} actions={actions} />
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable no-nested-ternary */
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
3
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
4
|
-
import { api, findCurrency, formatError, formatPrice, usePaymentContext } from '@blocklet/payment-react';
|
|
4
|
+
import { api, findCurrency, formatError, formatPrice, usePaymentContext, Collapse } from '@blocklet/payment-react';
|
|
5
5
|
import { AddOutlined } from '@mui/icons-material';
|
|
6
6
|
import { Box, Button, Divider } from '@mui/material';
|
|
7
7
|
import { cloneDeep } from 'lodash';
|
|
@@ -9,7 +9,6 @@ import { Fragment, useState, useEffect } from 'react';
|
|
|
9
9
|
import { FormProvider, useFieldArray, useForm } from 'react-hook-form';
|
|
10
10
|
import { dispatch } from 'use-bus';
|
|
11
11
|
|
|
12
|
-
import Collapse from '../../../../components/collapse';
|
|
13
12
|
import DrawerForm from '../../../../components/drawer-form';
|
|
14
13
|
import PriceActions from '../../../../components/price/actions';
|
|
15
14
|
import PriceForm, { DEFAULT_PRICE } from '../../../../components/price/form';
|
|
@@ -216,10 +216,10 @@ function EditForm({ item, onClose, onSuccess, isOwner }: EditFormProps) {
|
|
|
216
216
|
</Box>
|
|
217
217
|
</InputAdornment>
|
|
218
218
|
),
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
},
|
|
220
|
+
htmlInput: {
|
|
221
|
+
min: 0,
|
|
222
|
+
max: MAX_SAFE_AMOUNT,
|
|
223
223
|
},
|
|
224
224
|
}}
|
|
225
225
|
/>
|
|
@@ -269,10 +269,10 @@ function EditForm({ item, onClose, onSuccess, isOwner }: EditFormProps) {
|
|
|
269
269
|
</Box>
|
|
270
270
|
</InputAdornment>
|
|
271
271
|
),
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
272
|
+
},
|
|
273
|
+
htmlInput: {
|
|
274
|
+
min: 0,
|
|
275
|
+
max: MAX_SAFE_AMOUNT,
|
|
276
276
|
},
|
|
277
277
|
}}
|
|
278
278
|
/>
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '@blocklet/payment-react';
|
|
11
11
|
import type { TCreditGrantExpanded } from '@blocklet/payment-types';
|
|
12
12
|
import { ArrowBackOutlined } from '@mui/icons-material';
|
|
13
|
-
import { Alert, Avatar, Box, CircularProgress, Divider, Stack, Typography } from '@mui/material';
|
|
13
|
+
import { Alert, Avatar, Box, Button, CircularProgress, Divider, Stack, Typography } from '@mui/material';
|
|
14
14
|
import { useRequest } from 'ahooks';
|
|
15
15
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
16
16
|
import { styled } from '@mui/system';
|
|
@@ -93,6 +93,14 @@ export default function CustomerCreditGrantDetail() {
|
|
|
93
93
|
{t('admin.customer.creditGrants.title')}
|
|
94
94
|
</Typography>
|
|
95
95
|
</Stack>
|
|
96
|
+
{data.metadata?.invoice_id && (
|
|
97
|
+
<Button
|
|
98
|
+
variant="outlined"
|
|
99
|
+
size="small"
|
|
100
|
+
onClick={() => navigate(`/customer/invoice/${data.metadata!.invoice_id}`)}>
|
|
101
|
+
{t('admin.customer.creditGrants.viewInvoice')}
|
|
102
|
+
</Button>
|
|
103
|
+
)}
|
|
96
104
|
</Stack>
|
|
97
105
|
<Box
|
|
98
106
|
mt={4}
|