payment-kit 1.22.32 → 1.23.1
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 +4 -0
- package/api/src/integrations/arcblock/token.ts +599 -0
- package/api/src/libs/credit-grant.ts +7 -6
- package/api/src/libs/util.ts +34 -0
- package/api/src/queues/credit-consume.ts +29 -4
- package/api/src/queues/credit-grant.ts +245 -50
- package/api/src/queues/credit-reconciliation.ts +253 -0
- package/api/src/queues/refund.ts +263 -30
- package/api/src/queues/token-transfer.ts +331 -0
- package/api/src/routes/checkout-sessions.ts +94 -29
- package/api/src/routes/credit-grants.ts +35 -9
- package/api/src/routes/credit-tokens.ts +38 -0
- package/api/src/routes/credit-transactions.ts +20 -3
- package/api/src/routes/index.ts +2 -0
- package/api/src/routes/meter-events.ts +4 -0
- package/api/src/routes/meters.ts +32 -10
- package/api/src/routes/payment-currencies.ts +103 -0
- package/api/src/routes/payment-links.ts +3 -1
- package/api/src/routes/products.ts +2 -2
- package/api/src/routes/settings.ts +4 -3
- package/api/src/store/migrations/20251120-add-token-config-to-currencies.ts +20 -0
- package/api/src/store/migrations/20251204-add-chain-fields.ts +74 -0
- package/api/src/store/migrations/20251211-optimize-slow-queries.ts +33 -0
- package/api/src/store/models/credit-grant.ts +47 -9
- package/api/src/store/models/credit-transaction.ts +18 -1
- package/api/src/store/models/index.ts +2 -1
- package/api/src/store/models/payment-currency.ts +31 -4
- package/api/src/store/models/refund.ts +12 -2
- package/api/src/store/models/types.ts +48 -0
- package/api/src/store/sequelize.ts +1 -0
- package/api/third.d.ts +2 -0
- package/blocklet.yml +1 -1
- package/package.json +7 -6
- package/src/app.tsx +10 -0
- package/src/components/customer/credit-overview.tsx +19 -3
- package/src/components/meter/form.tsx +191 -18
- package/src/components/price/form.tsx +49 -37
- package/src/locales/en.tsx +25 -1
- package/src/locales/zh.tsx +27 -1
- package/src/pages/admin/billing/meters/create.tsx +42 -13
- package/src/pages/admin/billing/meters/detail.tsx +56 -5
- package/src/pages/admin/customers/customers/credit-grant/detail.tsx +13 -0
- package/src/pages/admin/customers/customers/credit-transaction/detail.tsx +324 -0
- package/src/pages/admin/customers/index.tsx +5 -0
- package/src/pages/customer/credit-grant/detail.tsx +14 -1
- package/src/pages/customer/credit-transaction/detail.tsx +289 -0
- package/src/pages/customer/invoice/detail.tsx +1 -1
- package/src/pages/customer/recharge/subscription.tsx +1 -1
- package/src/pages/customer/subscription/detail.tsx +1 -1
|
@@ -835,3 +835,51 @@ export type StructuredSourceDataField = {
|
|
|
835
835
|
};
|
|
836
836
|
|
|
837
837
|
export type SourceData = SimpleSourceData | StructuredSourceDataField[];
|
|
838
|
+
|
|
839
|
+
// Credit Grant on-chain operation status (combined mint, burn and transfer status)
|
|
840
|
+
export type CreditGrantChainStatus =
|
|
841
|
+
| 'mint_pending'
|
|
842
|
+
| 'mint_completed'
|
|
843
|
+
| 'mint_failed'
|
|
844
|
+
| 'burn_pending'
|
|
845
|
+
| 'burn_completed'
|
|
846
|
+
| 'burn_failed'
|
|
847
|
+
| 'transfer_completed' // for expired credits transferred to system wallet
|
|
848
|
+
| 'transfer_failed';
|
|
849
|
+
|
|
850
|
+
// Credit Grant chain operation details
|
|
851
|
+
export type CreditGrantChainDetail = {
|
|
852
|
+
// Mint operation details
|
|
853
|
+
mint?: {
|
|
854
|
+
hash?: string;
|
|
855
|
+
at?: number; // timestamp
|
|
856
|
+
error?: string;
|
|
857
|
+
failed_at?: number;
|
|
858
|
+
};
|
|
859
|
+
// Burn operation details (for expiration)
|
|
860
|
+
burn?: {
|
|
861
|
+
hash?: string;
|
|
862
|
+
at?: number; // timestamp
|
|
863
|
+
amount?: string;
|
|
864
|
+
error?: string;
|
|
865
|
+
failed_at?: number;
|
|
866
|
+
};
|
|
867
|
+
// Expired transfer operation details (for expiration - transfer to system wallet)
|
|
868
|
+
expired_transfer?: {
|
|
869
|
+
hash?: string;
|
|
870
|
+
at?: number; // timestamp
|
|
871
|
+
amount?: string;
|
|
872
|
+
error?: string;
|
|
873
|
+
failed_at?: number;
|
|
874
|
+
};
|
|
875
|
+
// Refund burn operation details
|
|
876
|
+
refund?: {
|
|
877
|
+
id?: string; // refund id
|
|
878
|
+
burn_hash?: string;
|
|
879
|
+
burned_amount?: string;
|
|
880
|
+
system_retained?: string; // amount retained by system (consumed portion)
|
|
881
|
+
burn_error?: string;
|
|
882
|
+
};
|
|
883
|
+
// Voided reason
|
|
884
|
+
voided_reason?: 'refund' | 'expired' | 'manual';
|
|
885
|
+
};
|
|
@@ -17,6 +17,7 @@ Sequelize.useCLS(namespace);
|
|
|
17
17
|
export const sequelize = new Sequelize({
|
|
18
18
|
dialect: 'sqlite',
|
|
19
19
|
logging: process.env.SQL_LOG === '1',
|
|
20
|
+
benchmark: process.env.SQL_LOG === '1' && process.env.SQL_BENCHMARK === '1',
|
|
20
21
|
storage: join(env.dataDir, 'payment-kit.db'),
|
|
21
22
|
pool: {
|
|
22
23
|
min: sequelizeOptionsPoolMin,
|
package/api/third.d.ts
CHANGED
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.23.1",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -53,13 +53,14 @@
|
|
|
53
53
|
"@arcblock/react-hooks": "^3.2.11",
|
|
54
54
|
"@arcblock/ux": "^3.2.11",
|
|
55
55
|
"@arcblock/validator": "^1.27.14",
|
|
56
|
+
"@arcblock/vc": "^1.27.14",
|
|
56
57
|
"@blocklet/did-space-js": "^1.2.9",
|
|
57
58
|
"@blocklet/error": "^0.3.4",
|
|
58
59
|
"@blocklet/js-sdk": "^1.17.4",
|
|
59
60
|
"@blocklet/logger": "^1.17.4",
|
|
60
|
-
"@blocklet/payment-broker-client": "1.
|
|
61
|
-
"@blocklet/payment-react": "1.
|
|
62
|
-
"@blocklet/payment-vendor": "1.
|
|
61
|
+
"@blocklet/payment-broker-client": "1.23.1",
|
|
62
|
+
"@blocklet/payment-react": "1.23.1",
|
|
63
|
+
"@blocklet/payment-vendor": "1.23.1",
|
|
63
64
|
"@blocklet/sdk": "^1.17.4",
|
|
64
65
|
"@blocklet/ui-react": "^3.2.11",
|
|
65
66
|
"@blocklet/uploader": "^0.3.14",
|
|
@@ -129,7 +130,7 @@
|
|
|
129
130
|
"devDependencies": {
|
|
130
131
|
"@abtnode/types": "^1.17.4",
|
|
131
132
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
132
|
-
"@blocklet/payment-types": "1.
|
|
133
|
+
"@blocklet/payment-types": "1.23.1",
|
|
133
134
|
"@types/cookie-parser": "^1.4.9",
|
|
134
135
|
"@types/cors": "^2.8.19",
|
|
135
136
|
"@types/debug": "^4.1.12",
|
|
@@ -176,5 +177,5 @@
|
|
|
176
177
|
"parser": "typescript"
|
|
177
178
|
}
|
|
178
179
|
},
|
|
179
|
-
"gitHead": "
|
|
180
|
+
"gitHead": "393e83d16adecbe9aa9d145732c45b2198a2e8b9"
|
|
180
181
|
}
|
package/src/app.tsx
CHANGED
|
@@ -28,6 +28,7 @@ const CustomerSubscriptionEmbed = React.lazy(() => import('./pages/customer/subs
|
|
|
28
28
|
const CustomerSubscriptionChangePlan = React.lazy(() => import('./pages/customer/subscription/change-plan'));
|
|
29
29
|
const CustomerSubscriptionChangePayment = React.lazy(() => import('./pages/customer/subscription/change-payment'));
|
|
30
30
|
const CustomerCreditGrantDetail = React.lazy(() => import('./pages/customer/credit-grant/detail'));
|
|
31
|
+
const CustomerCreditTransactionDetail = React.lazy(() => import('./pages/customer/credit-transaction/detail'));
|
|
31
32
|
const CustomerRecharge = React.lazy(() => import('./pages/customer/recharge/subscription'));
|
|
32
33
|
const CustomerPayoutDetail = React.lazy(() => import('./pages/customer/payout/detail'));
|
|
33
34
|
const IntegrationsPage = React.lazy(() => import('./pages/integrations'));
|
|
@@ -161,6 +162,15 @@ function App() {
|
|
|
161
162
|
</UserLayout>
|
|
162
163
|
}
|
|
163
164
|
/>
|
|
165
|
+
<Route
|
|
166
|
+
key="customer-credit-transaction"
|
|
167
|
+
path="/customer/credit-transaction/:id"
|
|
168
|
+
element={
|
|
169
|
+
<UserLayout>
|
|
170
|
+
<CustomerCreditTransactionDetail />
|
|
171
|
+
</UserLayout>
|
|
172
|
+
}
|
|
173
|
+
/>
|
|
164
174
|
<Route key="customer-fallback" path="/customer/*" element={<Navigate to="/customer" />} />,
|
|
165
175
|
<Route path="*" element={<Navigate to="/" />} />
|
|
166
176
|
</Routes>
|
|
@@ -136,7 +136,7 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
136
136
|
const grantData = creditSummary?.grants?.[currencyId];
|
|
137
137
|
const pendingAmount = creditSummary?.pendingAmount?.[currencyId] || '0';
|
|
138
138
|
|
|
139
|
-
if (!grantData) {
|
|
139
|
+
if (!grantData || grantData.status === 'inactive') {
|
|
140
140
|
return null;
|
|
141
141
|
}
|
|
142
142
|
|
|
@@ -145,6 +145,8 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
145
145
|
const totalAmount = grantData.totalAmount || '0';
|
|
146
146
|
const remainingAmount = grantData.remainingAmount || '0';
|
|
147
147
|
|
|
148
|
+
const cardTitle = grantData.meter?.name || currency.name;
|
|
149
|
+
|
|
148
150
|
return (
|
|
149
151
|
<Card
|
|
150
152
|
key={currency.id}
|
|
@@ -173,7 +175,7 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
173
175
|
pb: 2,
|
|
174
176
|
}}>
|
|
175
177
|
<Typography variant="h6" component="div">
|
|
176
|
-
{
|
|
178
|
+
{cardTitle}
|
|
177
179
|
</Typography>
|
|
178
180
|
{showRecharge && (
|
|
179
181
|
<SplitButton
|
|
@@ -217,7 +219,15 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
217
219
|
{totalAmount === '0' && remainingAmount === '0' ? (
|
|
218
220
|
<>0 </>
|
|
219
221
|
) : (
|
|
220
|
-
<>
|
|
222
|
+
<>
|
|
223
|
+
{formatBNStr(remainingAmount, currency.decimal, 6, true)}
|
|
224
|
+
{currency.symbol !== cardTitle && (
|
|
225
|
+
<Typography variant="body2" component="span">
|
|
226
|
+
{' '}
|
|
227
|
+
{currency.symbol}
|
|
228
|
+
</Typography>
|
|
229
|
+
)}
|
|
230
|
+
</>
|
|
221
231
|
)}
|
|
222
232
|
</Typography>
|
|
223
233
|
</Box>
|
|
@@ -239,6 +249,12 @@ export default function CreditOverview({ customerId, settings, mode = 'portal' }
|
|
|
239
249
|
color: 'error.main',
|
|
240
250
|
}}>
|
|
241
251
|
{formatBNStr(pendingAmount, currency.decimal, 6, true)}
|
|
252
|
+
{currency.symbol !== cardTitle && (
|
|
253
|
+
<Typography variant="body2" component="span">
|
|
254
|
+
{' '}
|
|
255
|
+
{currency.symbol}
|
|
256
|
+
</Typography>
|
|
257
|
+
)}
|
|
242
258
|
</Typography>
|
|
243
259
|
</Box>
|
|
244
260
|
)}
|
|
@@ -1,23 +1,54 @@
|
|
|
1
1
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
2
|
-
import { FormInput, FormLabel, Collapse } from '@blocklet/payment-react';
|
|
3
|
-
import {
|
|
4
|
-
|
|
2
|
+
import { FormInput, FormLabel, Collapse, usePaymentContext } from '@blocklet/payment-react';
|
|
3
|
+
import {
|
|
4
|
+
FormControl,
|
|
5
|
+
Select,
|
|
6
|
+
MenuItem,
|
|
7
|
+
Typography,
|
|
8
|
+
Box,
|
|
9
|
+
FormHelperText,
|
|
10
|
+
Stack,
|
|
11
|
+
Radio,
|
|
12
|
+
RadioGroup,
|
|
13
|
+
FormControlLabel,
|
|
14
|
+
Switch,
|
|
15
|
+
Autocomplete,
|
|
16
|
+
TextField,
|
|
17
|
+
} from '@mui/material';
|
|
18
|
+
import { useFormContext, useWatch, Controller } from 'react-hook-form';
|
|
5
19
|
import { InfoOutlined } from '@mui/icons-material';
|
|
20
|
+
import { useRequest } from 'ahooks';
|
|
21
|
+
import { useState, useMemo } from 'react';
|
|
22
|
+
import GraphQLClient from '@ocap/client';
|
|
6
23
|
|
|
7
|
-
import type { TMeter } from '@blocklet/payment-types';
|
|
24
|
+
import type { TMeter, TPaymentCurrency } from '@blocklet/payment-types';
|
|
8
25
|
import MetadataForm from '../metadata/form';
|
|
9
26
|
|
|
27
|
+
export type TokenConfig = { name: string; symbol: string; tokenFactoryAddress: string };
|
|
28
|
+
|
|
10
29
|
type Props = {
|
|
11
30
|
mode?: 'create' | 'edit';
|
|
31
|
+
tokenConfig?: TokenConfig;
|
|
32
|
+
onTokenConfigChange?: (next: TokenConfig) => void;
|
|
33
|
+
paymentCurrency?: TPaymentCurrency;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type TMeterForm = TMeter & {
|
|
37
|
+
mode?: 'OffChain' | 'OnChain';
|
|
12
38
|
};
|
|
13
39
|
|
|
14
|
-
function MeterForm({
|
|
40
|
+
function MeterForm({
|
|
41
|
+
mode = 'create',
|
|
42
|
+
tokenConfig = undefined,
|
|
43
|
+
onTokenConfigChange = undefined,
|
|
44
|
+
paymentCurrency = undefined,
|
|
45
|
+
}: Props) {
|
|
15
46
|
const { t } = useLocaleContext();
|
|
16
47
|
const {
|
|
17
48
|
register,
|
|
18
49
|
control,
|
|
19
50
|
formState: { errors },
|
|
20
|
-
} = useFormContext<
|
|
51
|
+
} = useFormContext<TMeterForm>();
|
|
21
52
|
|
|
22
53
|
// 监听聚合方法的变化,用于显示动态帮助信息
|
|
23
54
|
const aggregationMethod = useWatch({
|
|
@@ -26,20 +57,52 @@ function MeterForm({ mode = 'create' }: Props) {
|
|
|
26
57
|
defaultValue: 'sum',
|
|
27
58
|
});
|
|
28
59
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return t('admin.meter.aggregationMethod.lastDescription');
|
|
37
|
-
default:
|
|
38
|
-
return '';
|
|
39
|
-
}
|
|
40
|
-
};
|
|
60
|
+
const creditMode = useWatch({
|
|
61
|
+
control,
|
|
62
|
+
name: 'mode',
|
|
63
|
+
defaultValue: 'OffChain',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const [tokenType, setTokenType] = useState<'new' | 'existing'>(tokenConfig?.tokenFactoryAddress ? 'existing' : 'new');
|
|
41
67
|
|
|
42
68
|
const isEditing = mode === 'edit';
|
|
69
|
+
const { settings } = usePaymentContext();
|
|
70
|
+
|
|
71
|
+
// Check if current currency already has token_config
|
|
72
|
+
const hasTokenConfig = !!paymentCurrency?.token_config;
|
|
73
|
+
|
|
74
|
+
// Get GraphQL client for arcblock chain
|
|
75
|
+
const arcblockClient = useMemo(() => {
|
|
76
|
+
const arcblockMethod = settings.paymentMethods?.find((m) => m.type === 'arcblock') as any;
|
|
77
|
+
const apiHost = arcblockMethod?.api_host;
|
|
78
|
+
return apiHost ? new GraphQLClient(apiHost) : null;
|
|
79
|
+
}, [settings.paymentMethods]);
|
|
80
|
+
|
|
81
|
+
// Fetch tokens created by blocklet owner
|
|
82
|
+
const { data: tokens = [] } = useRequest(
|
|
83
|
+
async () => {
|
|
84
|
+
if (!arcblockClient || !window.blocklet?.appId) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
const result = await arcblockClient.listTokens({
|
|
88
|
+
issuerAddress: window.blocklet.appId,
|
|
89
|
+
});
|
|
90
|
+
return result?.tokens || [];
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
refreshDeps: [creditMode, arcblockClient],
|
|
94
|
+
ready: creditMode === 'OnChain' && !hasTokenConfig && !!arcblockClient,
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const getAggregationDescription = (method: string) => {
|
|
99
|
+
const map: Record<string, string> = {
|
|
100
|
+
sum: t('admin.meter.aggregationMethod.sumDescription'),
|
|
101
|
+
count: t('admin.meter.aggregationMethod.countDescription'),
|
|
102
|
+
last: t('admin.meter.aggregationMethod.lastDescription'),
|
|
103
|
+
};
|
|
104
|
+
return map[method] || '';
|
|
105
|
+
};
|
|
43
106
|
|
|
44
107
|
return (
|
|
45
108
|
<Box sx={{ maxWidth: 600 }}>
|
|
@@ -223,6 +286,116 @@ function MeterForm({ mode = 'create' }: Props) {
|
|
|
223
286
|
</FormHelperText>
|
|
224
287
|
</Box>
|
|
225
288
|
|
|
289
|
+
{/* On-chain token configuration */}
|
|
290
|
+
<Box sx={{ p: 2, border: '1px solid', borderColor: 'divider', borderRadius: 1 }}>
|
|
291
|
+
<Controller
|
|
292
|
+
name="mode"
|
|
293
|
+
control={control}
|
|
294
|
+
render={({ field }) => (
|
|
295
|
+
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
|
296
|
+
<Box>
|
|
297
|
+
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
|
|
298
|
+
{t('admin.meter.creditMode.onchain')}
|
|
299
|
+
</Typography>
|
|
300
|
+
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
|
|
301
|
+
{t('admin.meter.creditMode.onChainDescription')}
|
|
302
|
+
</Typography>
|
|
303
|
+
</Box>
|
|
304
|
+
<Switch
|
|
305
|
+
checked={hasTokenConfig ? field.value !== 'OffChain' : field.value === 'OnChain'}
|
|
306
|
+
onChange={(e) => field.onChange(e.target.checked ? 'OnChain' : 'OffChain')}
|
|
307
|
+
/>
|
|
308
|
+
</Stack>
|
|
309
|
+
)}
|
|
310
|
+
/>
|
|
311
|
+
|
|
312
|
+
{(hasTokenConfig || creditMode === 'OnChain') && (
|
|
313
|
+
<Box sx={{ mt: 3, pt: 3, borderTop: '1px solid', borderColor: 'divider' }}>
|
|
314
|
+
<Typography variant="subtitle2" sx={{ mb: 2 }}>
|
|
315
|
+
{t('admin.meter.creditMode.tokenConfiguration')}
|
|
316
|
+
</Typography>
|
|
317
|
+
{!hasTokenConfig && (
|
|
318
|
+
<RadioGroup
|
|
319
|
+
value={tokenType}
|
|
320
|
+
onChange={(e) => {
|
|
321
|
+
setTokenType(e.target.value as any);
|
|
322
|
+
onTokenConfigChange?.({ name: '', symbol: '', tokenFactoryAddress: '' });
|
|
323
|
+
}}
|
|
324
|
+
row>
|
|
325
|
+
<FormControlLabel
|
|
326
|
+
value="new"
|
|
327
|
+
control={<Radio />}
|
|
328
|
+
label={t('admin.meter.creditMode.createNewToken')}
|
|
329
|
+
/>
|
|
330
|
+
<FormControlLabel
|
|
331
|
+
value="existing"
|
|
332
|
+
control={<Radio />}
|
|
333
|
+
label={t('admin.meter.creditMode.useExistingToken')}
|
|
334
|
+
/>
|
|
335
|
+
</RadioGroup>
|
|
336
|
+
)}
|
|
337
|
+
{!hasTokenConfig && tokenType === 'new' && tokenConfig && onTokenConfigChange ? (
|
|
338
|
+
<Box sx={{ mt: 2 }}>
|
|
339
|
+
<TextField
|
|
340
|
+
value={tokenConfig.name}
|
|
341
|
+
onChange={(e) => onTokenConfigChange({ ...tokenConfig, name: e.target.value })}
|
|
342
|
+
fullWidth
|
|
343
|
+
label={t('admin.meter.creditMode.tokenName')}
|
|
344
|
+
inputProps={{ maxLength: 64 }}
|
|
345
|
+
sx={{ mb: 2 }}
|
|
346
|
+
/>
|
|
347
|
+
<TextField
|
|
348
|
+
value={tokenConfig.symbol}
|
|
349
|
+
onChange={(e) => onTokenConfigChange({ ...tokenConfig, symbol: e.target.value.toUpperCase() })}
|
|
350
|
+
fullWidth
|
|
351
|
+
label={t('admin.meter.creditMode.tokenSymbol')}
|
|
352
|
+
inputProps={{ maxLength: 6, style: { textTransform: 'uppercase', fontFamily: 'monospace' } }}
|
|
353
|
+
/>
|
|
354
|
+
</Box>
|
|
355
|
+
) : (
|
|
356
|
+
<Box sx={{ mt: 2 }}>
|
|
357
|
+
{hasTokenConfig && paymentCurrency?.token_config ? (
|
|
358
|
+
<TextField
|
|
359
|
+
fullWidth
|
|
360
|
+
label={t('admin.meter.creditMode.selectToken')}
|
|
361
|
+
value={`${paymentCurrency.token_config.symbol} (${paymentCurrency.token_config.name})`}
|
|
362
|
+
disabled
|
|
363
|
+
/>
|
|
364
|
+
) : (
|
|
365
|
+
<Autocomplete
|
|
366
|
+
options={tokens}
|
|
367
|
+
getOptionLabel={(o: any) => (typeof o === 'string' ? o : `${o.symbol} (${o.name})`)}
|
|
368
|
+
value={
|
|
369
|
+
tokens.find((f: any) => f.tokenFactoryAddress === tokenConfig?.tokenFactoryAddress) || null
|
|
370
|
+
}
|
|
371
|
+
onChange={(_, v: any) =>
|
|
372
|
+
onTokenConfigChange?.({
|
|
373
|
+
name: v?.name || '',
|
|
374
|
+
symbol: v?.symbol || '',
|
|
375
|
+
tokenFactoryAddress: v?.tokenFactoryAddress || '',
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
renderInput={(params) => (
|
|
379
|
+
<TextField {...params} label={t('admin.meter.creditMode.selectToken')} />
|
|
380
|
+
)}
|
|
381
|
+
renderOption={(props, o: any) => (
|
|
382
|
+
<li {...props}>
|
|
383
|
+
<Box>
|
|
384
|
+
<Typography variant="body1">{o.symbol}</Typography>
|
|
385
|
+
<Typography variant="caption" color="text.secondary">
|
|
386
|
+
{o.name}
|
|
387
|
+
</Typography>
|
|
388
|
+
</Box>
|
|
389
|
+
</li>
|
|
390
|
+
)}
|
|
391
|
+
/>
|
|
392
|
+
)}
|
|
393
|
+
</Box>
|
|
394
|
+
)}
|
|
395
|
+
</Box>
|
|
396
|
+
)}
|
|
397
|
+
</Box>
|
|
398
|
+
|
|
226
399
|
{/* Metadata Section */}
|
|
227
400
|
<Collapse trigger={t('common.metadata.label')}>
|
|
228
401
|
<Box sx={{ mb: 2 }}>
|
|
@@ -835,65 +835,77 @@ export default function PriceForm({ prefix = '', simple = false, productType = u
|
|
|
835
835
|
/>
|
|
836
836
|
|
|
837
837
|
{/* 可用时长配置 */}
|
|
838
|
-
<
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
838
|
+
<Controller
|
|
839
|
+
name={getFieldName('metadata')}
|
|
840
|
+
control={control}
|
|
841
|
+
render={({ field }) => (
|
|
842
|
+
<Box sx={{ width: '100%', mb: 2 }}>
|
|
843
|
+
<FormLabel tooltip={t('admin.creditProduct.validDuration.help')}>
|
|
844
|
+
{t('admin.creditProduct.validDuration.label')}
|
|
845
|
+
</FormLabel>
|
|
846
|
+
<Stack
|
|
847
|
+
direction="row"
|
|
848
|
+
spacing={1}
|
|
849
|
+
sx={{
|
|
850
|
+
alignItems: 'center',
|
|
851
|
+
}}>
|
|
852
852
|
<TextField
|
|
853
|
-
{...field}
|
|
854
853
|
size="small"
|
|
855
854
|
type="number"
|
|
856
855
|
sx={{ flex: 1 }}
|
|
857
|
-
value={field.value ?? '0'}
|
|
856
|
+
value={field.value?.credit_config?.valid_duration_value ?? '0'}
|
|
858
857
|
placeholder="0"
|
|
859
858
|
disabled={isLocked}
|
|
859
|
+
onChange={(e) => {
|
|
860
|
+
const metadata = field.value || {};
|
|
861
|
+
const creditConfig = metadata.credit_config || {};
|
|
862
|
+
const value = +e.target.value;
|
|
863
|
+
if (!Number.isNaN(value) && value >= 0) {
|
|
864
|
+
creditConfig.valid_duration_value = +value;
|
|
865
|
+
} else {
|
|
866
|
+
delete creditConfig.valid_duration_value;
|
|
867
|
+
}
|
|
868
|
+
metadata.credit_config = creditConfig;
|
|
869
|
+
field.onChange(metadata);
|
|
870
|
+
}}
|
|
860
871
|
slotProps={{
|
|
861
872
|
htmlInput: {
|
|
862
873
|
min: 0,
|
|
874
|
+
step: 0.1,
|
|
863
875
|
},
|
|
864
876
|
}}
|
|
865
877
|
/>
|
|
866
|
-
)}
|
|
867
|
-
/>
|
|
868
|
-
<Controller
|
|
869
|
-
name={getFieldName('metadata.credit_config.valid_duration_unit')}
|
|
870
|
-
control={control}
|
|
871
|
-
render={({ field }) => (
|
|
872
878
|
<Select
|
|
873
|
-
{...field}
|
|
874
879
|
size="small"
|
|
875
880
|
sx={{ minWidth: 120 }}
|
|
876
881
|
disabled={isLocked}
|
|
877
|
-
value={field.value || 'days'}
|
|
882
|
+
value={field.value?.credit_config?.valid_duration_unit || 'days'}
|
|
883
|
+
onChange={(e) => {
|
|
884
|
+
const metadata = field.value || {};
|
|
885
|
+
const creditConfig = metadata.credit_config || {};
|
|
886
|
+
creditConfig.valid_duration_unit = e.target.value;
|
|
887
|
+
metadata.credit_config = creditConfig;
|
|
888
|
+
field.onChange(metadata);
|
|
889
|
+
}}>
|
|
878
890
|
{!livemode && <MenuItem value="hours">{t('admin.creditProduct.validDuration.hours')}</MenuItem>}
|
|
879
891
|
<MenuItem value="days">{t('admin.creditProduct.validDuration.days')}</MenuItem>
|
|
880
892
|
<MenuItem value="weeks">{t('admin.creditProduct.validDuration.weeks')}</MenuItem>
|
|
881
893
|
<MenuItem value="months">{t('admin.creditProduct.validDuration.months')}</MenuItem>
|
|
882
894
|
<MenuItem value="years">{t('admin.creditProduct.validDuration.years')}</MenuItem>
|
|
883
895
|
</Select>
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
896
|
+
</Stack>
|
|
897
|
+
<Typography
|
|
898
|
+
variant="caption"
|
|
899
|
+
sx={{
|
|
900
|
+
color: 'text.secondary',
|
|
901
|
+
display: 'block',
|
|
902
|
+
mt: 0.5,
|
|
903
|
+
}}>
|
|
904
|
+
{t('admin.creditProduct.validDuration.description')}
|
|
905
|
+
</Typography>
|
|
906
|
+
</Box>
|
|
907
|
+
)}
|
|
908
|
+
/>
|
|
897
909
|
|
|
898
910
|
{/* 关联特定价格 */}
|
|
899
911
|
<Controller
|
package/src/locales/en.tsx
CHANGED
|
@@ -273,6 +273,30 @@ export default flat({
|
|
|
273
273
|
edit: 'Edit meter',
|
|
274
274
|
save: 'Save meter',
|
|
275
275
|
saved: 'Meter successfully saved',
|
|
276
|
+
creditMode: {
|
|
277
|
+
onchain: 'OnChain',
|
|
278
|
+
onChainDescription: 'Once enabled, your credit will be published as a Token on the ArcBlock blockchain',
|
|
279
|
+
tokenConfiguration: 'Token Configuration',
|
|
280
|
+
createNewToken: 'Create New Token',
|
|
281
|
+
useExistingToken: 'Use Existing Token',
|
|
282
|
+
createTokenHint: 'Fill in the token name and symbol to create a new Token',
|
|
283
|
+
tokenName: 'Token Name',
|
|
284
|
+
tokenNamePlaceholder: 'Enter token name',
|
|
285
|
+
tokenNameRequired: 'Token name is required',
|
|
286
|
+
tokenSymbol: 'Token Symbol',
|
|
287
|
+
tokenSymbolPlaceholder: 'Enter token symbol',
|
|
288
|
+
tokenSymbolRequired: 'Token symbol is required',
|
|
289
|
+
tokenSymbolFormat: 'Symbol must be 1-6 uppercase letters or numbers',
|
|
290
|
+
createTokenButton: 'Create Token',
|
|
291
|
+
creatingToken: 'Creating...',
|
|
292
|
+
tokenCreated: 'Token created successfully',
|
|
293
|
+
tokenCreatedLabel: 'Token Created',
|
|
294
|
+
tokenAddress: 'Token Address',
|
|
295
|
+
selectToken: 'Select Token',
|
|
296
|
+
selectTokenPlaceholder: 'Select an existing token',
|
|
297
|
+
tokenAddressRequired: 'Token address is required',
|
|
298
|
+
enabled: 'Enabled',
|
|
299
|
+
},
|
|
276
300
|
activate: 'Activate meter',
|
|
277
301
|
activated: 'Meter activated successfully',
|
|
278
302
|
deactivate: 'Deactivate meter',
|
|
@@ -293,7 +317,7 @@ export default flat({
|
|
|
293
317
|
label: 'Meter name',
|
|
294
318
|
required: 'Meter name is required',
|
|
295
319
|
placeholder: 'API requests',
|
|
296
|
-
help:
|
|
320
|
+
help: "A descriptive name for this meter that will be displayed in your dashboard and user's personal bill.",
|
|
297
321
|
},
|
|
298
322
|
eventName: {
|
|
299
323
|
label: 'Event name',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -283,13 +283,39 @@ export default flat({
|
|
|
283
283
|
basicInfo: '基本信息',
|
|
284
284
|
basicInfoDescription: '配置计量器的核心设置以跟踪使用事件。',
|
|
285
285
|
editDescription: '更新计量器名称、描述和元数据。事件名称和聚合方法等核心设置无法更改。',
|
|
286
|
+
|
|
287
|
+
creditMode: {
|
|
288
|
+
onchain: 'OnChain',
|
|
289
|
+
onChainDescription:
|
|
290
|
+
'开启后你的 Credit 将作为 Token 发布在 ArcBlock 区块链上,用户的充值、退款和消费将在链上公开记录且不可篡改。',
|
|
291
|
+
tokenConfiguration: 'Token 配置',
|
|
292
|
+
createNewToken: '创建新 Token',
|
|
293
|
+
useExistingToken: '使用现有 Token',
|
|
294
|
+
createTokenHint: '填写 Token 名称和符号以创建新 Token',
|
|
295
|
+
tokenName: 'Token 名称',
|
|
296
|
+
tokenNamePlaceholder: '请输入 Token 名称',
|
|
297
|
+
tokenNameRequired: 'Token 名称不能为空',
|
|
298
|
+
tokenSymbol: 'Token 符号',
|
|
299
|
+
tokenSymbolPlaceholder: '请输入 Token 符号',
|
|
300
|
+
tokenSymbolRequired: 'Token 符号不能为空',
|
|
301
|
+
tokenSymbolFormat: '符号必须为 1-6 个大写字母或数字',
|
|
302
|
+
createTokenButton: '创建 Token',
|
|
303
|
+
creatingToken: '创建中...',
|
|
304
|
+
tokenCreated: 'Token 创建成功',
|
|
305
|
+
tokenCreatedLabel: 'Token 已创建',
|
|
306
|
+
tokenAddress: 'Token 地址',
|
|
307
|
+
selectToken: '选择 Token',
|
|
308
|
+
selectTokenPlaceholder: '请选择一个现有的 Token',
|
|
309
|
+
tokenAddressRequired: 'Token 地址不能为空',
|
|
310
|
+
enabled: '已启用',
|
|
311
|
+
},
|
|
286
312
|
inactive: '计量器未激活',
|
|
287
313
|
inactiveTip: '此计量器未收集使用数据。激活它以开始跟踪事件。',
|
|
288
314
|
name: {
|
|
289
315
|
label: '计量器名称',
|
|
290
316
|
required: '计量器名称为必填项',
|
|
291
317
|
placeholder: 'API 请求',
|
|
292
|
-
help: '
|
|
318
|
+
help: '此计量器的描述性名称,将在您的仪表板以及用户个人账单中显示。',
|
|
293
319
|
},
|
|
294
320
|
eventName: {
|
|
295
321
|
label: '事件名称',
|