payment-kit 1.19.1 → 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/notification/template/customer-credit-grant-low-balance.ts +1 -1
- package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -1
- package/api/src/libs/security.ts +6 -3
- package/api/src/libs/util.ts +3 -1
- package/api/src/queues/credit-consume.ts +15 -2
- package/api/src/routes/checkout-sessions.ts +9 -4
- package/api/src/routes/credit-grants.ts +24 -2
- package/api/src/routes/customers.ts +36 -12
- package/api/src/routes/payment-currencies.ts +8 -0
- package/api/src/routes/payment-methods.ts +1 -0
- package/api/src/routes/webhook-endpoints.ts +0 -3
- 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/conditional-section.tsx +87 -0
- package/src/components/customer/credit-overview.tsx +30 -17
- package/src/components/customer/form.tsx +2 -1
- package/src/components/edit-in-line.tsx +197 -0
- package/src/components/metadata/form.tsx +2 -2
- package/src/components/meter/add-usage-dialog.tsx +2 -2
- package/src/components/meter/form.tsx +2 -2
- package/src/components/meter/products.tsx +2 -2
- package/src/components/payment-link/item.tsx +2 -2
- package/src/components/payouts/portal/list.tsx +6 -11
- package/src/components/price/currency-select.tsx +13 -9
- package/src/components/price/form.tsx +47 -16
- package/src/components/product/form.tsx +3 -8
- package/src/components/subscription/portal/list.tsx +0 -1
- package/src/locales/en.tsx +6 -3
- package/src/locales/zh.tsx +6 -3
- package/src/pages/admin/customers/customers/detail.tsx +5 -13
- package/src/pages/admin/settings/payment-methods/index.tsx +56 -85
- package/src/pages/customer/index.tsx +17 -15
- package/src/pages/customer/recharge/account.tsx +1 -1
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import { FormInput } from '@blocklet/payment-react';
|
|
2
|
+
import { FormInput, FormLabel } from '@blocklet/payment-react';
|
|
3
3
|
import { AddOutlined, Autorenew, DeleteOutlineOutlined, FormatAlignLeft } from '@mui/icons-material';
|
|
4
|
-
import { Box, Button, Divider, IconButton, Stack, TextField, InputAdornment, Tooltip
|
|
4
|
+
import { Box, Button, Divider, IconButton, Stack, TextField, InputAdornment, Tooltip } from '@mui/material';
|
|
5
5
|
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
6
6
|
import { useFieldArray, useFormContext } from 'react-hook-form';
|
|
7
7
|
import { isObject, debounce } from 'lodash';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import { api, formatError, useMobile, getCustomerAvatar } from '@blocklet/payment-react';
|
|
2
|
+
import { api, formatError, useMobile, getCustomerAvatar, FormLabel } from '@blocklet/payment-react';
|
|
3
3
|
import type { TCustomer, TPaymentCurrency } from '@blocklet/payment-types';
|
|
4
|
-
import { Box, Stack, Autocomplete, TextField, Button, Avatar,
|
|
4
|
+
import { Box, Stack, Autocomplete, TextField, Button, Avatar, Typography } from '@mui/material';
|
|
5
5
|
import { useSetState } from 'ahooks';
|
|
6
6
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
7
7
|
import Dialog from '@arcblock/ux/lib/Dialog';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import { FormInput } from '@blocklet/payment-react';
|
|
3
|
-
import { FormControl, Select, MenuItem, Typography, Box, FormHelperText, Stack
|
|
2
|
+
import { FormInput, FormLabel } from '@blocklet/payment-react';
|
|
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
|
|
|
@@ -38,7 +38,7 @@ export default function MeterProducts({ meterId, meter = undefined }: MeterProdu
|
|
|
38
38
|
const [loading, setLoading] = useState(false);
|
|
39
39
|
const [creating, setCreating] = useState(false);
|
|
40
40
|
const [error, setError] = useState<string | null>(null);
|
|
41
|
-
const [activeTab, setActiveTab] = useState<ProductType>('
|
|
41
|
+
const [activeTab, setActiveTab] = useState<ProductType>('credit');
|
|
42
42
|
|
|
43
43
|
const loadProducts = async (type: ProductType = activeTab) => {
|
|
44
44
|
setLoading(true);
|
|
@@ -183,8 +183,8 @@ export default function MeterProducts({ meterId, meter = undefined }: MeterProdu
|
|
|
183
183
|
color: 'primary.main',
|
|
184
184
|
},
|
|
185
185
|
}}>
|
|
186
|
-
<Tab label={t('admin.meter.products.meterService')} value="meter" />
|
|
187
186
|
<Tab label={t('admin.meter.products.creditCharge')} value="credit" />
|
|
187
|
+
<Tab label={t('admin.meter.products.meterService')} value="meter" />
|
|
188
188
|
</Tabs>
|
|
189
189
|
</Stack>
|
|
190
190
|
<Stack
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
2
|
import Toast from '@arcblock/ux/lib/Toast';
|
|
3
|
-
import { api, findCurrency, formatError, formatPrice, usePaymentContext } from '@blocklet/payment-react';
|
|
3
|
+
import { api, findCurrency, formatError, formatPrice, usePaymentContext, FormLabel } from '@blocklet/payment-react';
|
|
4
4
|
import type { TPrice, TProduct, TProductExpanded } from '@blocklet/payment-types';
|
|
5
|
-
import { Box, Checkbox, FormControlLabel,
|
|
5
|
+
import { Box, Checkbox, FormControlLabel, Stack, TextField, Typography } from '@mui/material';
|
|
6
6
|
import { useSetState } from 'ahooks';
|
|
7
7
|
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
|
8
8
|
|
|
@@ -10,6 +10,7 @@ import { Link } from 'react-router-dom';
|
|
|
10
10
|
import { styled } from '@mui/system';
|
|
11
11
|
import { debounce } from '../../../libs/util';
|
|
12
12
|
import CustomerLink from '../../customer/link';
|
|
13
|
+
import { useConditionalSection } from '../../conditional-section';
|
|
13
14
|
|
|
14
15
|
const fetchData = (
|
|
15
16
|
params: Record<string, any> = {}
|
|
@@ -47,7 +48,6 @@ type ListProps = {
|
|
|
47
48
|
status?: string;
|
|
48
49
|
customer_id?: string;
|
|
49
50
|
currency_id?: string;
|
|
50
|
-
setHasRevenues?: (hasRevenues: boolean) => void;
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
const getListKey = (props: ListProps) => {
|
|
@@ -57,14 +57,10 @@ const getListKey = (props: ListProps) => {
|
|
|
57
57
|
return 'payouts-mine';
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
-
export default function CustomerRevenueList({
|
|
61
|
-
currency_id = '',
|
|
62
|
-
status = '',
|
|
63
|
-
customer_id = '',
|
|
64
|
-
setHasRevenues = () => {},
|
|
65
|
-
}: ListProps) {
|
|
60
|
+
export default function CustomerRevenueList({ currency_id = '', status = '', customer_id = '' }: ListProps) {
|
|
66
61
|
const { t } = useLocaleContext();
|
|
67
62
|
const { isMobile } = useMobile('sm');
|
|
63
|
+
const conditionalSection = useConditionalSection();
|
|
68
64
|
|
|
69
65
|
const listKey = getListKey({ customer_id });
|
|
70
66
|
const defaultPageSize = useDefaultPageSize(10);
|
|
@@ -87,12 +83,11 @@ export default function CustomerRevenueList({
|
|
|
87
83
|
debounce(() => {
|
|
88
84
|
fetchData(search).then((res: any) => {
|
|
89
85
|
setData(res);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
86
|
+
const hasData = res.count > 0;
|
|
87
|
+
conditionalSection?.hideRender(!hasData);
|
|
93
88
|
});
|
|
94
89
|
}, 300)();
|
|
95
|
-
}, [search]);
|
|
90
|
+
}, [search, conditionalSection]);
|
|
96
91
|
|
|
97
92
|
const columns = [
|
|
98
93
|
{
|
|
@@ -18,6 +18,7 @@ type Props = {
|
|
|
18
18
|
disabled?: boolean;
|
|
19
19
|
selectSX?: SxProps;
|
|
20
20
|
currencyFilter?: (currency: any) => boolean;
|
|
21
|
+
hideMethod?: boolean;
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
export default function CurrencySelect({
|
|
@@ -29,6 +30,7 @@ export default function CurrencySelect({
|
|
|
29
30
|
disabled = false,
|
|
30
31
|
selectSX = {},
|
|
31
32
|
currencyFilter = () => true,
|
|
33
|
+
hideMethod = false,
|
|
32
34
|
}: Props) {
|
|
33
35
|
const { t } = useLocaleContext();
|
|
34
36
|
const { settings } = usePaymentContext();
|
|
@@ -80,7 +82,7 @@ export default function CurrencySelect({
|
|
|
80
82
|
justifyContent: 'flex-end',
|
|
81
83
|
textAlign: 'right',
|
|
82
84
|
}}>
|
|
83
|
-
{selectedCurrency?.symbol} ({selectedPaymentMethod?.name})
|
|
85
|
+
{selectedCurrency?.symbol} {hideMethod ? '' : `(${selectedPaymentMethod?.name})`}
|
|
84
86
|
{canSelect && <ArrowDropDown sx={{ color: 'text.secondary', fontSize: 21 }} />}
|
|
85
87
|
</Typography>
|
|
86
88
|
);
|
|
@@ -97,7 +99,7 @@ export default function CurrencySelect({
|
|
|
97
99
|
value={value}
|
|
98
100
|
renderValue={() => (
|
|
99
101
|
<Typography variant="body1" sx={{ display: 'inline-flex', fontSize: '12px', color: 'text.secondary' }}>
|
|
100
|
-
{selectedCurrency?.symbol} ({selectedPaymentMethod?.name})
|
|
102
|
+
{selectedCurrency?.symbol} {hideMethod ? '' : `(${selectedPaymentMethod?.name})`}
|
|
101
103
|
</Typography>
|
|
102
104
|
)}
|
|
103
105
|
onChange={handleSelect}
|
|
@@ -113,18 +115,20 @@ export default function CurrencySelect({
|
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
return [
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
hideMethod ? null : (
|
|
119
|
+
<ListSubheader
|
|
120
|
+
key={method.id}
|
|
121
|
+
sx={{ fontSize: '0.875rem', color: 'text.secondary', lineHeight: '2.1875rem' }}>
|
|
122
|
+
{method.name}
|
|
123
|
+
</ListSubheader>
|
|
124
|
+
),
|
|
121
125
|
...filteredCurrencies.map((currency) => (
|
|
122
126
|
<MenuItem key={currency.id} sx={{ pl: 3 }} value={currency.id}>
|
|
123
127
|
<Stack direction="row" sx={{ width: '100%', justifyContent: 'space-between', gap: 2 }}>
|
|
124
|
-
<Currency logo={currency.logo} name={currency.name} />
|
|
128
|
+
{hideMethod ? null : <Currency logo={currency.logo} name={currency.name} />}
|
|
125
129
|
<Typography
|
|
126
130
|
sx={{
|
|
127
|
-
fontWeight: 'bold',
|
|
131
|
+
fontWeight: hideMethod ? 'normal' : 'bold',
|
|
128
132
|
}}>
|
|
129
133
|
{currency.symbol}
|
|
130
134
|
</Typography>
|
|
@@ -50,7 +50,6 @@ import ProductSelect from '../payment-link/product-select';
|
|
|
50
50
|
import Collapse from '../collapse';
|
|
51
51
|
import { useProductsContext } from '../../contexts/products';
|
|
52
52
|
import CurrencySelect from './currency-select';
|
|
53
|
-
import MetadataForm from '../metadata/form';
|
|
54
53
|
import { getProductByPriceId } from '../../libs/util';
|
|
55
54
|
import InfoCard from '../info-card';
|
|
56
55
|
|
|
@@ -225,8 +224,11 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
225
224
|
if (value !== null) {
|
|
226
225
|
setValue(field.name, value);
|
|
227
226
|
}
|
|
228
|
-
if (value === 'one_time'
|
|
227
|
+
if (value === 'one_time') {
|
|
229
228
|
setValue(getFieldName('model'), 'standard');
|
|
229
|
+
setValue(getFieldName('currency_id'), settings.baseCurrency?.id);
|
|
230
|
+
setValue(getFieldName('recurring.meter_id'), '');
|
|
231
|
+
setValue(getFieldName('recurring.usage_type'), 'licensed');
|
|
230
232
|
}
|
|
231
233
|
}}
|
|
232
234
|
exclusive
|
|
@@ -299,7 +301,7 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
299
301
|
fullWidth
|
|
300
302
|
size="small"
|
|
301
303
|
onChange={(e) => {
|
|
302
|
-
if (e.target.value === 'standard'
|
|
304
|
+
if (e.target.value === 'standard') {
|
|
303
305
|
setValue(getFieldName('currency_id'), settings.baseCurrency?.id);
|
|
304
306
|
}
|
|
305
307
|
field.onChange(e.target.value);
|
|
@@ -313,13 +315,15 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
313
315
|
}}>
|
|
314
316
|
<MenuItem value="standard">{t('admin.price.models.standard')}</MenuItem>
|
|
315
317
|
<MenuItem value="package">{t('admin.price.models.package')}</MenuItem>
|
|
318
|
+
<MenuItem value="credit_metered" disabled={isCreditMode}>
|
|
319
|
+
{t('admin.price.models.creditMetered')}
|
|
320
|
+
</MenuItem>
|
|
316
321
|
<MenuItem value="graduated" disabled>
|
|
317
322
|
{t('admin.price.models.graduated')}
|
|
318
323
|
</MenuItem>
|
|
319
324
|
<MenuItem value="volume" disabled>
|
|
320
325
|
{t('admin.price.models.volume')}
|
|
321
326
|
</MenuItem>
|
|
322
|
-
<MenuItem value="credit_metered">{t('admin.price.models.creditMetered')}</MenuItem>
|
|
323
327
|
</Select>
|
|
324
328
|
</Box>
|
|
325
329
|
)}
|
|
@@ -771,7 +775,7 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
771
775
|
}
|
|
772
776
|
expanded={isLocked}
|
|
773
777
|
style={{ width: INPUT_WIDTH }}>
|
|
774
|
-
<Box sx={{ width: INPUT_WIDTH, mb: 2 }}>
|
|
778
|
+
<Box sx={{ width: INPUT_WIDTH, mb: 2, pl: 2, pr: 1 }}>
|
|
775
779
|
{/* Credit 数量配置 */}
|
|
776
780
|
<Controller
|
|
777
781
|
name={getFieldName('metadata')}
|
|
@@ -815,6 +819,7 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
815
819
|
}}
|
|
816
820
|
value={field.value?.credit_config?.currency_id || creditCurrencies?.[0]?.id}
|
|
817
821
|
disabled={isLocked}
|
|
822
|
+
hideMethod
|
|
818
823
|
selectSX={{ '.MuiOutlinedInput-notchedOutline': { border: 'none' } }}
|
|
819
824
|
/>
|
|
820
825
|
</InputAdornment>
|
|
@@ -1045,16 +1050,17 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
1045
1050
|
},
|
|
1046
1051
|
}}>
|
|
1047
1052
|
<Stack
|
|
1048
|
-
spacing={2}
|
|
1049
1053
|
sx={{
|
|
1050
1054
|
alignItems: 'flex-start',
|
|
1051
1055
|
width: INPUT_WIDTH,
|
|
1056
|
+
pl: 2,
|
|
1057
|
+
pr: 1,
|
|
1052
1058
|
}}>
|
|
1053
1059
|
<Controller
|
|
1054
1060
|
name={getFieldName('quantity_available')}
|
|
1055
1061
|
control={control}
|
|
1056
1062
|
render={({ field }) => (
|
|
1057
|
-
|
|
1063
|
+
<Box sx={{ width: '100%', mb: 2 }}>
|
|
1058
1064
|
<FormLabel>{t('admin.price.quantityAvailable.label')}</FormLabel>
|
|
1059
1065
|
<TextField
|
|
1060
1066
|
{...field}
|
|
@@ -1065,14 +1071,23 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
1065
1071
|
error={!quantityPositive(field.value)}
|
|
1066
1072
|
helperText={!quantityPositive(field.value) && t('admin.price.quantity.tip')}
|
|
1067
1073
|
/>
|
|
1068
|
-
|
|
1074
|
+
<Typography
|
|
1075
|
+
variant="caption"
|
|
1076
|
+
sx={{
|
|
1077
|
+
color: 'text.secondary',
|
|
1078
|
+
display: 'block',
|
|
1079
|
+
mt: 0.5,
|
|
1080
|
+
}}>
|
|
1081
|
+
{t('admin.price.quantityAvailable.description')}
|
|
1082
|
+
</Typography>
|
|
1083
|
+
</Box>
|
|
1069
1084
|
)}
|
|
1070
1085
|
/>
|
|
1071
1086
|
<Controller
|
|
1072
1087
|
name={getFieldName('quantity_limit_per_checkout')}
|
|
1073
1088
|
control={control}
|
|
1074
1089
|
render={({ field }) => (
|
|
1075
|
-
|
|
1090
|
+
<Box sx={{ width: '100%', mb: 2 }}>
|
|
1076
1091
|
<FormLabel>{t('admin.price.quantityLimitPerCheckout.label')}</FormLabel>
|
|
1077
1092
|
<TextField
|
|
1078
1093
|
{...field}
|
|
@@ -1084,7 +1099,16 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
1084
1099
|
error={!quantityPositive(field.value)}
|
|
1085
1100
|
helperText={!quantityPositive(field.value) && t('admin.price.quantity.tip')}
|
|
1086
1101
|
/>
|
|
1087
|
-
|
|
1102
|
+
<Typography
|
|
1103
|
+
variant="caption"
|
|
1104
|
+
sx={{
|
|
1105
|
+
color: 'text.secondary',
|
|
1106
|
+
display: 'block',
|
|
1107
|
+
mt: 0.5,
|
|
1108
|
+
}}>
|
|
1109
|
+
{t('admin.price.quantityLimitPerCheckout.description')}
|
|
1110
|
+
</Typography>
|
|
1111
|
+
</Box>
|
|
1088
1112
|
)}
|
|
1089
1113
|
/>
|
|
1090
1114
|
<Controller
|
|
@@ -1097,10 +1121,10 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
1097
1121
|
},
|
|
1098
1122
|
}}
|
|
1099
1123
|
render={({ field }) => (
|
|
1100
|
-
|
|
1124
|
+
<Box sx={{ width: '100%', mb: 2 }}>
|
|
1101
1125
|
<FormLabel>{t('admin.price.nickname.label')}</FormLabel>
|
|
1102
1126
|
<TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} inputProps={{ maxLength: 64 }} />
|
|
1103
|
-
|
|
1127
|
+
</Box>
|
|
1104
1128
|
)}
|
|
1105
1129
|
/>
|
|
1106
1130
|
<Controller
|
|
@@ -1113,14 +1137,21 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
1113
1137
|
},
|
|
1114
1138
|
}}
|
|
1115
1139
|
render={({ field }) => (
|
|
1116
|
-
|
|
1140
|
+
<Box sx={{ width: '100%', mb: 2 }}>
|
|
1117
1141
|
<FormLabel>{t('admin.price.lookup_key.label')}</FormLabel>
|
|
1118
1142
|
<TextField {...field} size="small" sx={{ width: INPUT_WIDTH }} inputProps={{ maxLength: 64 }} />
|
|
1119
|
-
|
|
1143
|
+
<Typography
|
|
1144
|
+
variant="caption"
|
|
1145
|
+
sx={{
|
|
1146
|
+
color: 'text.secondary',
|
|
1147
|
+
display: 'block',
|
|
1148
|
+
mt: 0.5,
|
|
1149
|
+
}}>
|
|
1150
|
+
{t('admin.price.lookup_key.description')}
|
|
1151
|
+
</Typography>
|
|
1152
|
+
</Box>
|
|
1120
1153
|
)}
|
|
1121
1154
|
/>
|
|
1122
|
-
{/* 元数据 */}
|
|
1123
|
-
<MetadataForm title={t('common.metadata.label')} color="inherit" name={getFieldName('metadata')} />
|
|
1124
1155
|
</Stack>
|
|
1125
1156
|
</Collapse>
|
|
1126
1157
|
</>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* eslint-disable no-nested-ternary */
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
-
import { FormInput } from '@blocklet/payment-react';
|
|
3
|
+
import { FormInput, FormLabel } from '@blocklet/payment-react';
|
|
4
4
|
import type { InferFormType, TProduct } from '@blocklet/payment-types';
|
|
5
|
-
import { Box, Stack, Typography,
|
|
5
|
+
import { Box, Stack, Typography, Select, MenuItem } from '@mui/material';
|
|
6
6
|
import { useFormContext, useWatch, Controller } from 'react-hook-form';
|
|
7
7
|
|
|
8
8
|
import Collapse from '../collapse';
|
|
@@ -46,7 +46,7 @@ export default function ProductForm({ simple = false }: Props) {
|
|
|
46
46
|
rules={{ required: true }}
|
|
47
47
|
render={({ field }) => (
|
|
48
48
|
<Box sx={{ width: '100%' }}>
|
|
49
|
-
<FormLabel sx={{ color: 'text.primary' }}>{t('admin.product.type.label')}</FormLabel>
|
|
49
|
+
<FormLabel sx={{ color: 'text.primary', fontSize: '0.875rem' }}>{t('admin.product.type.label')}</FormLabel>
|
|
50
50
|
<Select {...field} fullWidth size="small">
|
|
51
51
|
<MenuItem value="good">{t('admin.product.type.good')}</MenuItem>
|
|
52
52
|
<MenuItem value="service">{t('admin.product.type.service')}</MenuItem>
|
|
@@ -135,11 +135,6 @@ export default function ProductForm({ simple = false }: Props) {
|
|
|
135
135
|
<FormInput
|
|
136
136
|
name="unit_label"
|
|
137
137
|
label={t('admin.product.unit_label.label')}
|
|
138
|
-
placeholder={
|
|
139
|
-
productType === 'credit'
|
|
140
|
-
? t('admin.creditProduct.unitLabel.placeholder')
|
|
141
|
-
: t('admin.product.unit_label.placeholder')
|
|
142
|
-
}
|
|
143
138
|
rules={{
|
|
144
139
|
maxLength: { value: 12, message: t('common.maxLength', { len: 12 }) },
|
|
145
140
|
}}
|
package/src/locales/en.tsx
CHANGED
|
@@ -384,7 +384,7 @@ export default flat({
|
|
|
384
384
|
},
|
|
385
385
|
unit_label: {
|
|
386
386
|
label: 'Unit label',
|
|
387
|
-
placeholder: '
|
|
387
|
+
placeholder: 'Unit',
|
|
388
388
|
},
|
|
389
389
|
billingType: {
|
|
390
390
|
label: 'Billing type',
|
|
@@ -452,6 +452,7 @@ export default flat({
|
|
|
452
452
|
lookup_key: {
|
|
453
453
|
label: 'Lookup key',
|
|
454
454
|
placeholder: '',
|
|
455
|
+
description: 'Lookup key is used to identify the price in the API',
|
|
455
456
|
},
|
|
456
457
|
recurring: {
|
|
457
458
|
interval: 'Billing period',
|
|
@@ -526,6 +527,7 @@ export default flat({
|
|
|
526
527
|
format: 'Available {num} pieces',
|
|
527
528
|
noLimit: 'No limit on available quantity',
|
|
528
529
|
valid: 'Available quantity must be greater than or equal to sold quantity',
|
|
530
|
+
description: 'Enter the number of units that can be sold, 0 means unlimited',
|
|
529
531
|
},
|
|
530
532
|
quantitySold: {
|
|
531
533
|
label: 'Sold quantity',
|
|
@@ -536,6 +538,7 @@ export default flat({
|
|
|
536
538
|
placeholder: '0 means unlimited',
|
|
537
539
|
format: 'Limit {num} pieces per checkout',
|
|
538
540
|
noLimit: 'No limit on quantity per checkout',
|
|
541
|
+
description: 'Enter the number of units that can be purchased in a single checkout, 0 means unlimited',
|
|
539
542
|
},
|
|
540
543
|
inventory: 'Inventory Settings',
|
|
541
544
|
},
|
|
@@ -987,7 +990,7 @@ export default flat({
|
|
|
987
990
|
viewAllActivity: 'View All Activity',
|
|
988
991
|
pendingAmount: 'Outstanding Charges',
|
|
989
992
|
grantCount: 'Grant Count',
|
|
990
|
-
noGrantsDescription: "You don't have any credit grants yet.
|
|
993
|
+
noGrantsDescription: "You don't have any credit grants yet.",
|
|
991
994
|
addCredit: 'Add Credit',
|
|
992
995
|
},
|
|
993
996
|
},
|
|
@@ -1162,7 +1165,7 @@ export default flat({
|
|
|
1162
1165
|
totalAmount: 'Total Amount',
|
|
1163
1166
|
pendingAmount: 'Pending Amount',
|
|
1164
1167
|
grantCount: 'Grant Count',
|
|
1165
|
-
noGrantsDescription: "You don't have any credit grants yet.
|
|
1168
|
+
noGrantsDescription: "You don't have any credit grants yet.",
|
|
1166
1169
|
status: {
|
|
1167
1170
|
granted: 'Active',
|
|
1168
1171
|
pending: 'Pending',
|