payment-kit 1.17.3 → 1.17.5

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 (63) hide show
  1. package/api/src/crons/currency.ts +1 -1
  2. package/api/src/integrations/arcblock/nft.ts +1 -1
  3. package/api/src/integrations/arcblock/stake.ts +4 -3
  4. package/api/src/libs/constants.ts +3 -0
  5. package/api/src/libs/invoice.ts +6 -5
  6. package/api/src/libs/middleware.ts +2 -2
  7. package/api/src/libs/notification/template/subscription-renew-failed.ts +4 -3
  8. package/api/src/libs/notification/template/subscription-renewed.ts +4 -3
  9. package/api/src/libs/notification/template/subscription-succeeded.ts +4 -3
  10. package/api/src/libs/notification/template/subscription-trial-start.ts +11 -3
  11. package/api/src/libs/notification/template/subscription-upgraded.ts +2 -1
  12. package/api/src/libs/payment.ts +5 -4
  13. package/api/src/libs/product.ts +24 -1
  14. package/api/src/libs/security.ts +2 -2
  15. package/api/src/locales/en.ts +2 -2
  16. package/api/src/queues/payment.ts +7 -5
  17. package/api/src/queues/refund.ts +8 -6
  18. package/api/src/routes/connect/change-payment.ts +3 -2
  19. package/api/src/routes/connect/change-plan.ts +3 -2
  20. package/api/src/routes/connect/collect-batch.ts +5 -4
  21. package/api/src/routes/connect/collect.ts +6 -5
  22. package/api/src/routes/connect/pay.ts +9 -4
  23. package/api/src/routes/connect/recharge.ts +9 -4
  24. package/api/src/routes/connect/setup.ts +3 -2
  25. package/api/src/routes/connect/shared.ts +25 -7
  26. package/api/src/routes/connect/subscribe.ts +3 -2
  27. package/api/src/routes/payment-currencies.ts +11 -10
  28. package/api/src/routes/payment-methods.ts +35 -19
  29. package/api/src/routes/payment-stats.ts +9 -3
  30. package/api/src/routes/prices.ts +19 -1
  31. package/api/src/routes/products.ts +60 -28
  32. package/api/src/routes/subscriptions.ts +4 -3
  33. package/api/src/store/models/payment-method.ts +11 -8
  34. package/api/src/store/models/types.ts +27 -1
  35. package/blocklet.yml +1 -1
  36. package/package.json +24 -24
  37. package/public/methods/base.png +0 -0
  38. package/src/components/customer/overdraft-protection.tsx +87 -56
  39. package/src/components/payment-method/base.tsx +79 -0
  40. package/src/components/payment-method/form.tsx +3 -0
  41. package/src/components/price/upsell-select.tsx +1 -0
  42. package/src/components/subscription/metrics.tsx +1 -1
  43. package/src/components/subscription/portal/actions.tsx +1 -1
  44. package/src/libs/util.ts +1 -1
  45. package/src/locales/en.tsx +36 -11
  46. package/src/locales/zh.tsx +24 -0
  47. package/src/pages/admin/billing/invoices/detail.tsx +1 -1
  48. package/src/pages/admin/customers/customers/detail.tsx +2 -2
  49. package/src/pages/admin/overview.tsx +15 -2
  50. package/src/pages/admin/payments/intents/detail.tsx +1 -1
  51. package/src/pages/admin/payments/payouts/detail.tsx +1 -1
  52. package/src/pages/admin/payments/refunds/detail.tsx +1 -1
  53. package/src/pages/admin/products/links/detail.tsx +1 -0
  54. package/src/pages/admin/products/prices/actions.tsx +2 -1
  55. package/src/pages/admin/products/prices/detail.tsx +1 -0
  56. package/src/pages/admin/products/products/detail.tsx +1 -0
  57. package/src/pages/admin/settings/payment-methods/create.tsx +7 -0
  58. package/src/pages/admin/settings/payment-methods/index.tsx +99 -11
  59. package/src/pages/customer/index.tsx +1 -1
  60. package/src/pages/customer/invoice/detail.tsx +1 -1
  61. package/src/pages/customer/recharge.tsx +1 -1
  62. package/src/pages/customer/refund/list.tsx +7 -3
  63. package/src/pages/customer/subscription/change-payment.tsx +1 -1
@@ -325,6 +325,9 @@ export default flat({
325
325
  save: 'Save payment method',
326
326
  saved: 'Payment method successfully saved',
327
327
  settings: 'Settings',
328
+ gasTip:
329
+ 'Ensure your account on the {chain} network has sufficient balance to cover transaction fees when using {method}.',
330
+ showQR: 'Show QR Code',
328
331
  props: {
329
332
  type: 'Type',
330
333
  confirmation: 'Confirmation',
@@ -395,6 +398,28 @@ export default flat({
395
398
  tip: 'How many blocks since transaction execution',
396
399
  },
397
400
  },
401
+ base: {
402
+ chain_id: {
403
+ label: 'Chain ID',
404
+ tip: 'Must be a valid EVM chain id, usually an integer, https://chainlist.org',
405
+ },
406
+ api_host: {
407
+ label: 'RPC Endpoint',
408
+ tip: 'The RPC endpoint to send transaction to',
409
+ },
410
+ explorer_host: {
411
+ label: 'Explorer Host',
412
+ tip: 'The webapp endpoint to view transaction details',
413
+ },
414
+ native_symbol: {
415
+ label: 'Native Symbol',
416
+ tip: 'The symbol for native token on this chain',
417
+ },
418
+ confirmation: {
419
+ label: 'Confirmation Count',
420
+ tip: 'How many blocks since transaction execution',
421
+ },
422
+ },
398
423
  },
399
424
  paymentCurrency: {
400
425
  name: 'Payment Currency',
@@ -652,39 +677,39 @@ export default flat({
652
677
  error: 'Delegate failed',
653
678
  },
654
679
  overdraftProtection: {
655
- title: 'SubGuard',
656
- setting: 'Set SubGuard',
657
- tip: 'To avoid service interruption due to unpaid invoices, you can enable SubGuard by staking. Timely payment will not incur additional fees. Please settle your invoices promptly. If your available stake is insufficient or payment is overdue, we will deduct the amount from your stake and charge a service fee.',
680
+ title: 'SubGuard',
681
+ setting: 'Set SubGuard',
682
+ tip: 'To avoid service interruption due to unpaid invoices, you can enable SubGuard by staking. Timely payment will not incur additional fees. Please settle your invoices promptly. If your available stake is insufficient or payment is overdue, we will deduct the amount from your stake and charge a service fee.',
658
683
  enabled: 'Enabled',
659
684
  disabled: 'Disabled',
660
685
  returnRemaining: 'Return Remaining Stake',
661
686
  returnRemainingTip:
662
- 'Once the remaining stake is returned, the SubGuard will be automatically disabled. Please confirm the action.',
687
+ 'Once the remaining stake is returned, the SubGuard will be automatically disabled. Please confirm the action.',
663
688
  applyRemainingSuccess: 'Stake return application successful',
664
689
  remaining:
665
690
  'Your current remaining stake: {amount} {symbol}, estimated required stake per cycle: {estimateAmount} {symbol}.',
666
691
  noRemaining:
667
- 'No remaining stake available. Please stake at least {estimateAmount} {symbol} as soon as possible to ensure SubGuard is enabled.',
692
+ 'No remaining stake available. Please stake at least {estimateAmount} {symbol} as soon as possible to ensure SubGuard is enabled.',
668
693
  remainingNotEnough:
669
694
  'You have unpaid invoices totaling {due} {symbol}. If not paid, your remaining stake will be insufficient to cover the next invoice. Available stake: {unused} {symbol}. Please stake at least {min} {symbol}.',
670
695
  due: 'Please pay the outstanding amount first',
671
696
  insufficient: 'Insufficient Stake to cover the next invoice, please add stake',
672
- insufficientTip: 'Insufficient Stake, please stake to ensure SubGuard is enabled.',
697
+ insufficientTip: 'Insufficient Stake, please stake to ensure SubGuard is enabled.',
673
698
  intervals: 'cycles',
674
699
  estimatedDuration: '{duration} {unit} est.',
675
700
  rule: 'Rule: N * ( P + Fee )',
676
701
  ruleTip:
677
- 'N is the number of cycles, P is the subscription bill amount, Fee is the SubGuard service fee, the single fee is {gas} {symbol}',
702
+ 'N is the number of cycles, P is the subscription bill amount, Fee is the SubGuard service fee, the single fee is {gas} {symbol}',
678
703
  min: 'The amount must be greater or equal to {min} {symbol}',
679
- settingSuccess: 'Set SubGuard Successful',
680
- settingError: 'Set SubGuard Failed',
681
- keepStake: 'Keep Remaining Stake For SubGuard',
704
+ settingSuccess: 'Set SubGuard Successful',
705
+ settingError: 'Set SubGuard Failed',
706
+ keepStake: 'Keep Remaining Stake For SubGuard',
682
707
  returnStake: 'Return Remaining Stake',
683
708
  stake: 'Stake',
684
709
  address: 'Staking Address',
685
710
  total: 'Total Stake: {total} {symbol}, ',
686
711
  disableConfirm: 'You currently have unpaid invoices, please settle your invoices first.',
687
- open: 'Enable SubGuard',
712
+ open: 'Enable SubGuard',
688
713
  payerAddress: 'Payer',
689
714
  stakingAddress: 'Staking Address',
690
715
  },
@@ -315,6 +315,8 @@ export default flat({
315
315
  save: '保存支付方式',
316
316
  saved: '支付方式已成功保存',
317
317
  settings: '设置',
318
+ gasTip: '使用 {method} 支付需保证账户在 {chain} 链上有余额支付手续费',
319
+ showQR: '显示二维码',
318
320
  props: {
319
321
  type: '类型',
320
322
  confirmation: '确认',
@@ -385,6 +387,28 @@ export default flat({
385
387
  tip: '交易标记为确认需要的区块数',
386
388
  },
387
389
  },
390
+ base: {
391
+ chain_id: {
392
+ label: '链 ID',
393
+ tip: '必须是有效的EVM链ID,通常是一个整数,https://chainlist.org',
394
+ },
395
+ api_host: {
396
+ label: 'RPC 端点',
397
+ tip: '发送交易的RPC端点',
398
+ },
399
+ explorer_host: {
400
+ label: '区块浏览器',
401
+ tip: '查看交易详情的区块浏览器',
402
+ },
403
+ native_symbol: {
404
+ label: '货币符号',
405
+ tip: '链上主货币符号',
406
+ },
407
+ confirmation: {
408
+ label: '确认区块数',
409
+ tip: '交易标记为确认需要的区块数',
410
+ },
411
+ },
388
412
  },
389
413
  paymentCurrency: {
390
414
  name: '支付货币',
@@ -304,7 +304,7 @@ export default function InvoiceDetail(props: { id: string }) {
304
304
  direction={InfoDirection}
305
305
  alignItems={InfoAlignItems}
306
306
  />
307
- {!!data.paymentIntent?.payment_details?.ethereum && (
307
+ {(!!data.paymentIntent?.payment_details?.ethereum || !!data.paymentIntent?.payment_details?.base) && (
308
308
  <InfoRow
309
309
  label={t('common.txGas')}
310
310
  value={<TxGas details={data.paymentIntent.payment_details as any} method={data.paymentMethod} />}
@@ -85,7 +85,7 @@ function getTokenBalances(customer: TCustomerExpanded, paymentMethods: TPaymentM
85
85
  export default function CustomerDetail(props: { id: string }) {
86
86
  const { t } = useLocaleContext();
87
87
  const { isMobile } = useMobile();
88
- const { settings } = usePaymentContext();
88
+ const { settings, livemode } = usePaymentContext();
89
89
  const [state, setState] = useSetState({
90
90
  adding: {
91
91
  price: false,
@@ -282,7 +282,7 @@ export default function CustomerDetail(props: { id: string }) {
282
282
  <Stack>
283
283
  <InfoRow
284
284
  label={t('common.did')}
285
- value={<DidAddress did={data.customer.did} showQrcode />}
285
+ value={<DidAddress did={data.customer.did} chainId={livemode ? 'main' : 'beta'} showQrcode />}
286
286
  direction={InfoDirection}
287
287
  alignItems={InfoAlignItems}
288
288
  />
@@ -79,7 +79,7 @@ export const groupData = (data: TPaymentStat[], currencies: { [key: string]: any
79
79
 
80
80
  export default function Overview() {
81
81
  const { t, locale } = useLocaleContext();
82
- const { settings } = usePaymentContext();
82
+ const { settings, livemode } = usePaymentContext();
83
83
  const maxDate = dayjs().endOf('day').toDate();
84
84
  const [state, setState] = useSetState({
85
85
  anchorEl: null,
@@ -200,6 +200,13 @@ export default function Overview() {
200
200
  });
201
201
  }
202
202
 
203
+ const getChainId = (type: string) => {
204
+ if (type === 'arcblock') {
205
+ return livemode ? 'main' : 'beta';
206
+ }
207
+ return '';
208
+ };
209
+
203
210
  return (
204
211
  <Grid container gap={{ xs: 2, sm: 5, md: 8 }} sx={{ mb: 4 }}>
205
212
  <Grid item xs={12} sm={12} md={8}>
@@ -244,7 +251,13 @@ export default function Overview() {
244
251
  </Stack>
245
252
  <Stack direction="column" spacing={1} sx={{ mt: 2 }}>
246
253
  {Object.keys(summary.data.addresses).map((chain) => (
247
- <DID key={chain} did={summary.data?.addresses?.[chain] as string} copyable showQrcode />
254
+ <DID
255
+ key={chain}
256
+ did={summary.data?.addresses?.[chain] as string}
257
+ chainId={getChainId(chain)}
258
+ copyable
259
+ showQrcode
260
+ />
248
261
  ))}
249
262
  </Stack>
250
263
  </Box>
@@ -330,7 +330,7 @@ export default function PaymentIntentDetail(props: { id: string }) {
330
330
  direction={InfoDirection}
331
331
  alignItems={InfoAlignItems}
332
332
  />
333
- {!!data.payment_details?.ethereum && (
333
+ {(!!data.payment_details?.ethereum || !!data.payment_details?.base) && (
334
334
  <InfoRow
335
335
  label={t('common.txGas')}
336
336
  value={<TxGas details={data.payment_details as any} method={data.paymentMethod} />}
@@ -299,7 +299,7 @@ export default function PayoutDetail(props: { id: string }) {
299
299
  direction={InfoDirection}
300
300
  alignItems={InfoAlignItems}
301
301
  />
302
- {!!data.payment_details?.ethereum && (
302
+ {(!!data.payment_details?.ethereum || !!data.payment_details?.base) && (
303
303
  <InfoRow
304
304
  label={t('common.txGas')}
305
305
  value={<TxGas details={data.payment_details as any} method={data.paymentMethod} />}
@@ -317,7 +317,7 @@ export default function RefundDetail(props: { id: string }) {
317
317
  direction={InfoDirection}
318
318
  alignItems={InfoAlignItems}
319
319
  />
320
- {!!data.payment_details?.ethereum && (
320
+ {(!!data.payment_details?.ethereum || !!data.payment_details?.base) && (
321
321
  <InfoRow
322
322
  label={t('common.txGas')}
323
323
  value={<TxGas details={data.payment_details as any} method={data.paymentMethod} />}
@@ -82,6 +82,7 @@ export default function PaymentLinkDetail(props: { id: string }) {
82
82
  } catch (err) {
83
83
  console.error(err);
84
84
  Toast.error(formatError(err));
85
+ throw err;
85
86
  } finally {
86
87
  setState((prev) => ({ loading: { ...prev.loading, price: false } }));
87
88
  }
@@ -47,8 +47,9 @@ export default function PriceActions({ data, onChange, variant, setAsDefault }:
47
47
  } catch (err) {
48
48
  console.error(err);
49
49
  Toast.error(formatError(err));
50
+ throw err;
50
51
  } finally {
51
- setState({ loading: false, action: '' });
52
+ setState({ loading: false });
52
53
  }
53
54
  };
54
55
  const onArchivePrice = async () => {
@@ -65,6 +65,7 @@ export default function PriceDetail(props: { id: string }) {
65
65
  } catch (err) {
66
66
  console.error(err);
67
67
  Toast.error(formatError(err));
68
+ throw err;
68
69
  } finally {
69
70
  setState((prev) => ({ loading: { ...prev.loading, [key]: false } }));
70
71
  }
@@ -99,6 +99,7 @@ export default function ProductDetail(props: { id: string }) {
99
99
  } catch (err) {
100
100
  console.error(err);
101
101
  Toast.error(formatError(err));
102
+ throw err;
102
103
  } finally {
103
104
  setState((prev) => ({ loading: { ...prev.loading, price: false } }));
104
105
  }
@@ -39,6 +39,13 @@ export default function PaymentMethodCreate() {
39
39
  confirmation: 1,
40
40
  logo: '',
41
41
  },
42
+ base: {
43
+ api_host: '',
44
+ explorer_host: '',
45
+ native_symbol: 'ETH',
46
+ confirmation: 1,
47
+ logo: '',
48
+ },
42
49
  bitcoin: {
43
50
  chain_id: 0,
44
51
  api_host: '',
@@ -1,7 +1,15 @@
1
1
  import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
2
- import { Switch, api, formatError } from '@blocklet/payment-react';
2
+ import { Switch, api, formatError, usePaymentContext } from '@blocklet/payment-react';
3
3
  import type { TPaymentMethodExpanded } from '@blocklet/payment-types';
4
- import { AddOutlined, Check, Close, DeleteOutlined, EditOutlined } from '@mui/icons-material';
4
+ import {
5
+ AddOutlined,
6
+ Check,
7
+ Close,
8
+ DeleteOutlined,
9
+ EditOutlined,
10
+ InfoOutlined,
11
+ QrCodeOutlined,
12
+ } from '@mui/icons-material';
5
13
  import {
6
14
  Alert,
7
15
  Avatar,
@@ -15,6 +23,7 @@ import {
15
23
  ListItemText,
16
24
  Stack,
17
25
  TextField,
26
+ Tooltip,
18
27
  Typography,
19
28
  } from '@mui/material';
20
29
  import { useRequest, useSetState } from 'ahooks';
@@ -22,12 +31,15 @@ import useBus from 'use-bus';
22
31
 
23
32
  import { useState } from 'react';
24
33
  import Toast from '@arcblock/ux/lib/Toast';
34
+ import { DIDDialog } from '@arcblock/ux/lib/DID';
25
35
  import IconCollapse from '../../../../components/collapse';
26
36
  import InfoCard from '../../../../components/info-card';
27
37
  import InfoRow from '../../../../components/info-row';
28
38
  import PaymentCurrencyAdd from '../../../../components/payment-currency/add';
29
39
 
30
- const getMethods = (params: Record<string, any> = {}): Promise<TPaymentMethodExpanded[]> => {
40
+ const getMethods = (
41
+ params: Record<string, any> = {}
42
+ ): Promise<{ list: TPaymentMethodExpanded[]; addresses: { arcblock: string; ethereum: string } }> => {
31
43
  const search = new URLSearchParams();
32
44
  Object.keys(params).forEach((key) => {
33
45
  search.set(key, String(params[key]));
@@ -132,29 +144,97 @@ const groupByType = (methods: TPaymentMethodExpanded[]) => {
132
144
 
133
145
  export default function PaymentMethods() {
134
146
  const { t } = useLocaleContext();
135
- const { loading, error, data, runAsync } = useRequest(() => getMethods({}));
147
+ const {
148
+ loading,
149
+ error,
150
+ data = { list: [], addresses: {} } as any,
151
+ runAsync,
152
+ } = useRequest(() =>
153
+ getMethods({
154
+ addresses: true,
155
+ })
156
+ );
136
157
  const [state, setState] = useSetState({ method: '' });
158
+ const [didDialog, setDidDialog] = useSetState({ open: false, chainId: '', did: '' });
159
+ const { refresh } = usePaymentContext();
137
160
 
138
- useBus('paymentMethod.created', runAsync, []);
139
- useBus('paymentCurrency.added', () => runAsync().then(() => setState({ method: '' })), []);
161
+ const { list: methods, addresses } = data;
162
+ useBus(
163
+ 'paymentMethod.created',
164
+ () => {
165
+ runAsync();
166
+ refresh(true);
167
+ },
168
+ []
169
+ );
170
+ useBus(
171
+ 'paymentCurrency.added',
172
+ () => {
173
+ runAsync().then(() => setState({ method: '' }));
174
+ refresh(true);
175
+ },
176
+ []
177
+ );
140
178
 
141
179
  if (error) {
142
180
  return <Alert severity="error">{error.message}</Alert>;
143
181
  }
144
182
 
145
- if (loading || !data) {
183
+ if (loading || !data || methods?.length === 0) {
146
184
  return <CircularProgress />;
147
185
  }
148
186
 
149
- const groups = groupByType(data);
187
+ const groups = groupByType(methods);
150
188
 
189
+ const getAddress = (type: string) => {
190
+ if (['ethereum', 'base'].includes(type)) {
191
+ return addresses?.ethereum || '';
192
+ }
193
+ return addresses?.arcblock || '';
194
+ };
151
195
  return (
152
196
  <>
153
197
  {Object.keys(groups).map((x) => (
154
198
  <Box key={x} mt={3}>
155
- <Typography variant="h6" sx={{ mb: 1, textTransform: 'uppercase' }}>
156
- {x}
157
- </Typography>
199
+ <Stack direction="row" alignItems="center" mb={1}>
200
+ <Typography variant="h6" sx={{ textTransform: 'uppercase' }}>
201
+ {x}
202
+ </Typography>
203
+ {['ethereum', 'base'].includes(x) && (
204
+ <Stack
205
+ direction="row"
206
+ alignItems="center"
207
+ spacing={0.5}
208
+ sx={{
209
+ ml: 1,
210
+ px: 0.5,
211
+ color: 'text.secondary',
212
+ }}>
213
+ <InfoOutlined sx={{ fontSize: 16 }} color="warning" />
214
+ <Typography variant="body2">
215
+ {t('admin.paymentMethod.gasTip', {
216
+ method: x,
217
+ address: '222',
218
+ chain: groups[x]?.[0]?.name || x,
219
+ })}
220
+ </Typography>
221
+ <Tooltip title={t('admin.paymentMethod.showQR')}>
222
+ <IconButton
223
+ size="small"
224
+ onClick={() =>
225
+ setDidDialog({
226
+ open: true,
227
+ chainId: groups[x]?.[0]?.id || '',
228
+ did: getAddress(x),
229
+ })
230
+ }
231
+ sx={{ p: 0.5 }}>
232
+ <QrCodeOutlined sx={{ fontSize: 16 }} />
233
+ </IconButton>
234
+ </Tooltip>
235
+ </Stack>
236
+ )}
237
+ </Stack>
158
238
  {(groups[x] as TPaymentMethodExpanded[]).map((method) => (
159
239
  <IconCollapse
160
240
  key={method.id}
@@ -231,6 +311,14 @@ export default function PaymentMethods() {
231
311
  ))}
232
312
  </Box>
233
313
  ))}
314
+ {didDialog.open && didDialog.did && (
315
+ <DIDDialog
316
+ open={didDialog.open}
317
+ onClose={() => setDidDialog({ open: false, chainId: '', did: '' })}
318
+ did={didDialog.did}
319
+ chainId={didDialog.chainId}
320
+ />
321
+ )}
234
322
  </>
235
323
  );
236
324
  }
@@ -360,7 +360,7 @@ export default function CustomerHome() {
360
360
  {data.name}
361
361
  </Typography>
362
362
  <Typography variant="body2" color="text.secondary">
363
- <DID did={data.did} copyable showQrcode />
363
+ <DID did={data.did} copyable showQrcode chainId={livemode ? 'main' : 'beta'} />
364
364
  </Typography>
365
365
  </Box>
366
366
  </Box>
@@ -268,7 +268,7 @@ export default function CustomerInvoiceDetail() {
268
268
  direction={InfoDirection}
269
269
  alignItems={InfoAlignItems}
270
270
  />
271
- {!!data.paymentIntent?.payment_details?.ethereum && (
271
+ {(!!data.paymentIntent?.payment_details?.ethereum || !!data.paymentIntent?.payment_details?.base) && (
272
272
  <InfoRow
273
273
  label={t('common.txGas')}
274
274
  value={<TxGas details={data.paymentIntent.payment_details as any} method={data.paymentMethod} />}
@@ -217,7 +217,7 @@ export default function RechargePage() {
217
217
  }
218
218
  const currentBalance = formatBNStr(payerValue?.token || '0', paymentCurrency?.decimal, 6, false);
219
219
 
220
- const supportRecharge = ['arcblock', 'ethereum'].includes(paymentMethod?.type || '');
220
+ const supportRecharge = ['arcblock', 'ethereum', 'base'].includes(paymentMethod?.type || '');
221
221
 
222
222
  const formatEstimatedDuration = (cycles: number) => {
223
223
  const { interval, interval_count: intervalCount } = subscription.pending_invoice_item_interval;
@@ -142,14 +142,16 @@ const RefundTable = memo(({ invoice_id }: Props) => {
142
142
  options: {
143
143
  customBodyRenderLite: (_: string, index: number) => {
144
144
  const item = data.list[index] as TRefundExpanded;
145
- return item.payment_details?.arcblock?.tx_hash || item.payment_details?.ethereum?.tx_hash ? (
145
+ const paymentDetails = item.payment_details as Record<string, { tx_hash?: string }>;
146
+ const txHash = ['arcblock', 'ethereum', 'base'].map((key) => !!paymentDetails?.[key]?.tx_hash).find(Boolean);
147
+ return txHash ? (
146
148
  <Box
147
149
  sx={{
148
150
  '.MuiTypography-root': {
149
151
  color: 'text.link',
150
152
  },
151
153
  }}>
152
- <TxLink details={item.payment_details} method={item.paymentMethod} mode="customer" />
154
+ <TxLink details={paymentDetails} method={item.paymentMethod} mode="customer" />
153
155
  </Box>
154
156
  ) : (
155
157
  t('common.none')
@@ -271,7 +273,9 @@ const RefundList = memo(({ invoice_id }: Props) => {
271
273
  </Typography>
272
274
  </Box>
273
275
  <Box flex={3} sx={{ minWidth: '220px' }}>
274
- {(item.payment_details?.arcblock?.tx_hash || item.payment_details?.ethereum?.tx_hash) && (
276
+ {(item.payment_details?.arcblock?.tx_hash ||
277
+ item.payment_details?.ethereum?.tx_hash ||
278
+ item.payment_details?.base?.tx_hash) && (
275
279
  <TxLink details={item.payment_details} method={item.paymentMethod} mode="customer" />
276
280
  )}
277
281
  </Box>
@@ -153,7 +153,7 @@ function CustomerSubscriptionChangePayment({ subscription, customer, onComplete
153
153
  submitting: false,
154
154
  });
155
155
 
156
- if (['arcblock', 'ethereum'].includes(method.type)) {
156
+ if (['arcblock', 'ethereum', 'base'].includes(method.type)) {
157
157
  setState({ paying: true });
158
158
  if (result.data.delegation?.sufficient) {
159
159
  await handleConnected();