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.
Files changed (36) hide show
  1. package/api/src/libs/notification/template/customer-credit-grant-low-balance.ts +1 -1
  2. package/api/src/libs/notification/template/customer-credit-insufficient.ts +1 -1
  3. package/api/src/libs/security.ts +6 -3
  4. package/api/src/libs/util.ts +3 -1
  5. package/api/src/queues/credit-consume.ts +15 -2
  6. package/api/src/routes/checkout-sessions.ts +9 -4
  7. package/api/src/routes/credit-grants.ts +24 -2
  8. package/api/src/routes/customers.ts +36 -12
  9. package/api/src/routes/payment-currencies.ts +8 -0
  10. package/api/src/routes/payment-methods.ts +1 -0
  11. package/api/src/routes/webhook-endpoints.ts +0 -3
  12. package/api/src/store/migrations/20250610-billing-credit.ts +0 -3
  13. package/api/src/store/migrations/20250708-currency-precision.ts +14 -0
  14. package/api/src/store/models/webhook-attempt.ts +1 -1
  15. package/blocklet.yml +1 -1
  16. package/package.json +25 -25
  17. package/src/components/conditional-section.tsx +87 -0
  18. package/src/components/customer/credit-overview.tsx +30 -17
  19. package/src/components/customer/form.tsx +2 -1
  20. package/src/components/edit-in-line.tsx +197 -0
  21. package/src/components/metadata/form.tsx +2 -2
  22. package/src/components/meter/add-usage-dialog.tsx +2 -2
  23. package/src/components/meter/form.tsx +2 -2
  24. package/src/components/meter/products.tsx +2 -2
  25. package/src/components/payment-link/item.tsx +2 -2
  26. package/src/components/payouts/portal/list.tsx +6 -11
  27. package/src/components/price/currency-select.tsx +13 -9
  28. package/src/components/price/form.tsx +47 -16
  29. package/src/components/product/form.tsx +3 -8
  30. package/src/components/subscription/portal/list.tsx +0 -1
  31. package/src/locales/en.tsx +6 -3
  32. package/src/locales/zh.tsx +6 -3
  33. package/src/pages/admin/customers/customers/detail.tsx +5 -13
  34. package/src/pages/admin/settings/payment-methods/index.tsx +56 -85
  35. package/src/pages/customer/index.tsx +17 -15
  36. package/src/pages/customer/recharge/account.tsx +1 -1
@@ -361,7 +361,7 @@ export default flat({
361
361
  },
362
362
  unit_label: {
363
363
  label: '单位标签',
364
- placeholder: '座位',
364
+ placeholder: '单位',
365
365
  },
366
366
  billingType: {
367
367
  label: '计费类型',
@@ -424,6 +424,7 @@ export default flat({
424
424
  lookup_key: {
425
425
  label: '查找键',
426
426
  placeholder: '',
427
+ description: '查找键用于在API中识别价格',
427
428
  },
428
429
  recurring: {
429
430
  interval: '计费周期',
@@ -495,6 +496,7 @@ export default flat({
495
496
  format: '可售{num}件',
496
497
  noLimit: '不限制可售数量',
497
498
  valid: '可售数量不得少于已售数量',
499
+ description: '输入可售数量,0表示不限制可售数量',
498
500
  },
499
501
  quantitySold: {
500
502
  label: '已售数量',
@@ -505,6 +507,7 @@ export default flat({
505
507
  placeholder: '0表示无限制',
506
508
  format: '单次最多购买{num}件',
507
509
  noLimit: '不限制单次购买数量',
510
+ description: '输入限制单次购买的最大数量, 0表示无限制',
508
511
  },
509
512
  inventory: '库存设置',
510
513
  },
@@ -945,7 +948,7 @@ export default flat({
945
948
  totalAmount: '总额度',
946
949
  pendingAmount: '欠费额度',
947
950
  grantCount: '额度数量',
948
- noGrantsDescription: '您还没有任何信用额度,请联系管理员添加。',
951
+ noGrantsDescription: '您还没有任何信用额度',
949
952
  status: {
950
953
  granted: '生效中',
951
954
  pending: '待生效',
@@ -1121,7 +1124,7 @@ export default flat({
1121
1124
  totalAmount: '总额度',
1122
1125
  pendingAmount: '欠费额度',
1123
1126
  grantCount: '额度数量',
1124
- noGrantsDescription: '您还没有任何信用额度,请联系管理员添加。',
1127
+ noGrantsDescription: '您还没有任何信用额度',
1125
1128
  status: {
1126
1129
  granted: '生效中',
1127
1130
  pending: '待生效',
@@ -42,10 +42,6 @@ const fetchData = async (
42
42
  ): Promise<{
43
43
  customer: TCustomerExpanded;
44
44
  summary: { [key: string]: GroupedBN };
45
- creditSummary?: {
46
- grants?: { [currencyId: string]: { totalAmount: string; remainingAmount: string } };
47
- pendingAmount?: { [currencyId: string]: string };
48
- } | null;
49
45
  }> => {
50
46
  const [customer, summary] = await Promise.all([
51
47
  api.get(`/api/customers/${id}`).then((res) => res.data),
@@ -311,15 +307,11 @@ export default function CustomerDetail(props: { id: string }) {
311
307
  }}>
312
308
  <Box className="payment-link-column-1" sx={{ flex: 1, gap: 2.5, display: 'flex', flexDirection: 'column' }}>
313
309
  {/* 信用管理区域 */}
314
- {data.creditSummary && (
315
- <>
316
- <Box className="section">
317
- <SectionHeader title={t('admin.creditGrants.title')} mb={0} />
318
- <CreditOverview customerId={props.id} settings={settings} mode="dashboard" />
319
- </Box>
320
- <Divider />
321
- </>
322
- )}
310
+ <Box className="section">
311
+ <SectionHeader title={t('admin.creditGrants.title')} mb={0} />
312
+ <CreditOverview customerId={props.id} settings={settings} mode="dashboard" />
313
+ </Box>
314
+ <Divider />
323
315
 
324
316
  <Box className="section" sx={{ containerType: 'inline-size' }}>
325
317
  <SectionHeader title={t('admin.details')} />
@@ -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, apiHost: string) => {
66
- return api.put(`/api/payment-methods/${id}/settings`, { arcblock: { api_host: apiHost } }).then((res) => res.data);
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
- if (!tempValue.trim()) {
78
- setError(t('common.required'));
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
- const handleCancel = () => {
93
- setTempValue(value);
94
- setEdit(false);
95
- setError('');
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.api_host.label')}
99
+ label={t('admin.paymentMethod.arcblock.explorer_host.label')}
101
100
  value={
102
- <Stack
103
- direction="row"
104
- spacing={1}
105
- sx={{
106
- alignItems: 'center',
107
- flexWrap: 'wrap',
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
- <InfoRow label={t('admin.paymentMethod.props.explorer_host')} value={explorerHost} />
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: 0,
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 method={method} balances={balances} addresses={addresses} setDidDialog={setDidDialog} />
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} />
@@ -40,6 +40,7 @@ import { memo, useEffect, useState } from 'react';
40
40
  import { useNavigate, useSearchParams } from 'react-router-dom';
41
41
  import { joinURL } from 'ufo';
42
42
  import CreditOverview from '../../components/customer/credit-overview';
43
+ import ConditionalSection from '../../components/conditional-section';
43
44
 
44
45
  import { useTransitionContext } from '../../components/progress-bar';
45
46
  import CurrentSubscriptions from '../../components/subscription/portal/list';
@@ -214,7 +215,6 @@ export default function CustomerHome() {
214
215
  const navigate = useNavigate();
215
216
  const [subscriptionStatus, setSubscriptionStatus] = useState(false);
216
217
  const [hasSubscriptions, setHasSubscriptions] = useState(false);
217
- const [hasRevenues, setHasRevenues] = useState(false);
218
218
  const { startTransition } = useTransitionContext();
219
219
  const {
220
220
  data,
@@ -480,15 +480,15 @@ export default function CustomerHome() {
480
480
  );
481
481
 
482
482
  // 独立的Credit Card组件
483
- const CreditCard = loadingCard ? (
484
- <CardSkeleton height={400} />
485
- ) : (
486
- <Box className="base-card section section-credit">
487
- <Box className="section-header" sx={{ mb: 2 }}>
488
- <Typography variant="h3">{t('admin.creditGrants.title')}</Typography>
483
+ const CreditCard = (
484
+ <ConditionalSection skeleton={loadingCard}>
485
+ <Box className="base-card section section-credit">
486
+ <Box className="section-header" sx={{ mb: 2 }}>
487
+ <Typography variant="h3">{t('admin.creditGrants.title')}</Typography>
488
+ </Box>
489
+ <CreditOverview customerId={data?.id || ''} settings={settings} />
489
490
  </Box>
490
- <CreditOverview customerId={data?.id} settings={settings} />
491
- </Box>
491
+ </ConditionalSection>
492
492
  );
493
493
 
494
494
  const InvoiceCard = loadingCard ? (
@@ -519,13 +519,15 @@ export default function CustomerHome() {
519
519
  </Box>
520
520
  );
521
521
 
522
- const RevenueCard = loadingCard ? null : (
523
- <Box className="base-card section section-revenue" sx={{ visibility: hasRevenues ? 'visible' : 'hidden' }}>
524
- <Box className="section-header">
525
- <Typography variant="h3">{t('customer.payout.title')}</Typography>
522
+ const RevenueCard = (
523
+ <ConditionalSection skeleton={loadingCard}>
524
+ <Box className="base-card section section-revenue">
525
+ <Box className="section-header">
526
+ <Typography variant="h3">{t('customer.payout.title')}</Typography>
527
+ </Box>
528
+ <CustomerRevenueList />
526
529
  </Box>
527
- <CustomerRevenueList setHasRevenues={setHasRevenues} />
528
- </Box>
530
+ </ConditionalSection>
529
531
  );
530
532
 
531
533
  return (
@@ -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.decimal;
263
+ const precision = currency.maximum_precision;
264
264
  const errorMessage = formatAmountPrecisionLimit(value, locale, precision || 6);
265
265
  setAmountError(errorMessage || '');
266
266
  setAmount(value);