payment-kit 1.17.2 → 1.17.4
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/integrations/arcblock/nft.ts +1 -1
- package/api/src/libs/env.ts +0 -2
- package/api/src/libs/middleware.ts +2 -2
- package/api/src/libs/overdraft-protection.ts +4 -1
- package/api/src/libs/security.ts +2 -2
- package/api/src/libs/subscription.ts +1 -1
- package/api/src/locales/en.ts +2 -2
- package/api/src/locales/zh.ts +2 -2
- package/api/src/queues/notification.ts +1 -1
- package/api/src/queues/subscription.ts +1 -1
- package/api/src/routes/connect/overdraft-protection.ts +4 -1
- package/api/src/routes/connect/shared.ts +12 -7
- package/api/src/routes/invoices.ts +21 -5
- package/api/src/routes/subscriptions.ts +1 -1
- package/blocklet.yml +1 -1
- package/package.json +23 -23
- package/src/components/customer/overdraft-protection.tsx +219 -74
- package/src/components/subscription/metrics.tsx +83 -7
- package/src/components/subscription/portal/actions.tsx +188 -153
- package/src/components/subscription/portal/list.tsx +1 -0
- package/src/libs/dayjs.ts +132 -0
- package/src/locales/en.tsx +18 -11
- package/src/locales/zh.tsx +17 -10
- package/src/pages/admin/billing/subscriptions/detail.tsx +1 -1
- package/src/pages/customer/recharge.tsx +63 -27
- package/src/pages/customer/subscription/detail.tsx +153 -10
- package/src/pages/customer/subscription/embed.tsx +1 -0
- /package/api/src/libs/notification/template/{subscription.overdraft-protection.exhausted.ts → subscription-overdraft-protection-exhausted.ts} +0 -0
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
FormControl,
|
|
17
17
|
RadioGroup,
|
|
18
18
|
Radio,
|
|
19
|
+
Link,
|
|
20
|
+
Skeleton,
|
|
19
21
|
} from '@mui/material';
|
|
20
22
|
import Dialog from '@arcblock/ux/lib/Dialog';
|
|
21
23
|
import { EventHandler, useState } from 'react';
|
|
@@ -23,7 +25,10 @@ import { api, formatAmountPrecisionLimit, Switch, useMobile } from '@blocklet/pa
|
|
|
23
25
|
import { useRequest } from 'ahooks';
|
|
24
26
|
import { BN, fromTokenToUnit, fromUnitToToken } from '@ocap/util';
|
|
25
27
|
import type { TPaymentCurrency, TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
28
|
+
import { joinURL } from 'ufo';
|
|
29
|
+
import { OpenInNewOutlined } from '@mui/icons-material';
|
|
26
30
|
import Currency from '../currency';
|
|
31
|
+
import { formatSmartDuration, TimeUnit } from '../../libs/dayjs';
|
|
27
32
|
|
|
28
33
|
const fetchCycleAmount = (
|
|
29
34
|
subscriptionId: string,
|
|
@@ -58,7 +63,8 @@ type OverdraftProtectionDialogProps = {
|
|
|
58
63
|
onSave: (data: { enabled: boolean; additionalCount?: number; returnRemaining?: boolean }) => Promise<void>;
|
|
59
64
|
onCancel: EventHandler<any>;
|
|
60
65
|
open: boolean;
|
|
61
|
-
|
|
66
|
+
stakingAddress?: string;
|
|
67
|
+
payerAddress?: string;
|
|
62
68
|
currency: {
|
|
63
69
|
symbol: string;
|
|
64
70
|
logo: string;
|
|
@@ -66,10 +72,16 @@ type OverdraftProtectionDialogProps = {
|
|
|
66
72
|
maximum_precision?: number;
|
|
67
73
|
};
|
|
68
74
|
subscription: TSubscriptionExpanded;
|
|
75
|
+
initValues?: {
|
|
76
|
+
enabled: boolean;
|
|
77
|
+
return_stake: boolean;
|
|
78
|
+
} | null;
|
|
69
79
|
};
|
|
70
80
|
|
|
71
81
|
OverdraftProtectionDialog.defaultProps = {
|
|
72
|
-
|
|
82
|
+
payerAddress: '',
|
|
83
|
+
stakingAddress: '',
|
|
84
|
+
initValues: null,
|
|
73
85
|
};
|
|
74
86
|
|
|
75
87
|
export default function OverdraftProtectionDialog({
|
|
@@ -78,20 +90,23 @@ export default function OverdraftProtectionDialog({
|
|
|
78
90
|
onSave,
|
|
79
91
|
onCancel,
|
|
80
92
|
open,
|
|
81
|
-
|
|
93
|
+
stakingAddress,
|
|
94
|
+
payerAddress,
|
|
82
95
|
currency,
|
|
83
96
|
subscription,
|
|
97
|
+
initValues,
|
|
84
98
|
}: OverdraftProtectionDialogProps) {
|
|
85
99
|
const { t, locale } = useLocaleContext();
|
|
86
100
|
const { isMobile } = useMobile();
|
|
87
101
|
const [customAmount, setCustomAmount] = useState(false);
|
|
88
|
-
const [presetAmounts, setPresetAmounts] = useState<{ amount: string; cycles: number }[]>([]);
|
|
102
|
+
const [presetAmounts, setPresetAmounts] = useState<{ amount: string; cycles: number; label: string }[]>([]);
|
|
89
103
|
|
|
90
104
|
const {
|
|
91
105
|
data: cycleAmount = {
|
|
92
106
|
amount: '0',
|
|
93
107
|
gas: '0',
|
|
94
108
|
},
|
|
109
|
+
loading: cycleAmountLoading,
|
|
95
110
|
} = useRequest(
|
|
96
111
|
() =>
|
|
97
112
|
fetchCycleAmount(subscription.id, {
|
|
@@ -100,10 +115,47 @@ export default function OverdraftProtectionDialog({
|
|
|
100
115
|
{
|
|
101
116
|
refreshDeps: [subscription.id],
|
|
102
117
|
onSuccess: (data) => {
|
|
103
|
-
const presets =
|
|
118
|
+
const presets = (() => {
|
|
119
|
+
const { interval, interval_count: intervalCount } = subscription.pending_invoice_item_interval;
|
|
120
|
+
|
|
121
|
+
switch (interval) {
|
|
122
|
+
case 'hour':
|
|
123
|
+
return [
|
|
124
|
+
{ cycles: Math.ceil(24 / intervalCount), label: '1 day' },
|
|
125
|
+
{ cycles: Math.ceil((24 * 7) / intervalCount), label: '1 week' },
|
|
126
|
+
{ cycles: Math.ceil((24 * 30) / intervalCount), label: '1 month' },
|
|
127
|
+
{ cycles: Math.ceil((24 * 90) / intervalCount), label: '3 months' },
|
|
128
|
+
{ cycles: Math.ceil((24 * 180) / intervalCount), label: '6 months' },
|
|
129
|
+
];
|
|
130
|
+
case 'day':
|
|
131
|
+
return [
|
|
132
|
+
{ cycles: Math.ceil(7 / intervalCount), label: '1 week' },
|
|
133
|
+
{ cycles: Math.ceil(30 / intervalCount), label: '1 month' },
|
|
134
|
+
{ cycles: Math.ceil(90 / intervalCount), label: '3 months' },
|
|
135
|
+
{ cycles: Math.ceil(180 / intervalCount), label: '6 months' },
|
|
136
|
+
{ cycles: Math.ceil(365 / intervalCount), label: '1 year' },
|
|
137
|
+
];
|
|
138
|
+
default:
|
|
139
|
+
return [
|
|
140
|
+
{ cycles: 1, label: '1x' },
|
|
141
|
+
{ cycles: 2, label: '2x' },
|
|
142
|
+
{ cycles: 3, label: '6x' },
|
|
143
|
+
{ cycles: 6, label: '6x' },
|
|
144
|
+
{ cycles: 12, label: '12x' },
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
|
|
104
149
|
const getCycleAmount = (cycles: number) =>
|
|
105
150
|
fromUnitToToken(new BN(data.amount).mul(new BN(cycles)).toString(), data?.currency?.decimal);
|
|
106
|
-
|
|
151
|
+
|
|
152
|
+
setPresetAmounts(
|
|
153
|
+
presets.map(({ cycles, label }) => ({
|
|
154
|
+
amount: getCycleAmount(cycles),
|
|
155
|
+
cycles,
|
|
156
|
+
label,
|
|
157
|
+
}))
|
|
158
|
+
);
|
|
107
159
|
},
|
|
108
160
|
}
|
|
109
161
|
);
|
|
@@ -117,6 +169,7 @@ export default function OverdraftProtectionDialog({
|
|
|
117
169
|
enabled: !!subscription.overdraft_protection?.enabled,
|
|
118
170
|
return_stake: false,
|
|
119
171
|
amount: '0',
|
|
172
|
+
...(initValues || {}),
|
|
120
173
|
},
|
|
121
174
|
mode: 'onChange',
|
|
122
175
|
});
|
|
@@ -146,16 +199,28 @@ export default function OverdraftProtectionDialog({
|
|
|
146
199
|
const totalIntervals = cycles * intervalCount;
|
|
147
200
|
const availableUnitKeys = ['hour', 'day', 'week', 'month', 'year'];
|
|
148
201
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
202
|
+
if (!availableUnitKeys.includes(interval)) {
|
|
203
|
+
return t('customer.overdraftProtection.estimatedDuration', {
|
|
204
|
+
duration: totalIntervals,
|
|
205
|
+
unit: t('customer.overdraftProtection.intervals').toLowerCase(),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return t('common.estimatedDuration', {
|
|
209
|
+
duration: formatSmartDuration(totalIntervals, interval as TimeUnit, {
|
|
210
|
+
t,
|
|
211
|
+
}),
|
|
156
212
|
});
|
|
157
213
|
};
|
|
158
214
|
|
|
215
|
+
const getStakingAddressURL = () => {
|
|
216
|
+
return joinURL(
|
|
217
|
+
subscription.paymentMethod?.settings.arcblock?.explorer_host as string,
|
|
218
|
+
'/stakes',
|
|
219
|
+
stakingAddress as string,
|
|
220
|
+
'/tokens'
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
159
224
|
return (
|
|
160
225
|
<Dialog
|
|
161
226
|
open={open}
|
|
@@ -207,16 +272,67 @@ export default function OverdraftProtectionDialog({
|
|
|
207
272
|
|
|
208
273
|
{isEnabled ? (
|
|
209
274
|
<Stack gap={1} sx={{ mt: '-8px' }}>
|
|
210
|
-
{
|
|
211
|
-
<Stack
|
|
275
|
+
{payerAddress && (
|
|
276
|
+
<Stack
|
|
277
|
+
sx={
|
|
278
|
+
isMobile
|
|
279
|
+
? {
|
|
280
|
+
flexDirection: 'column',
|
|
281
|
+
gap: 1,
|
|
282
|
+
}
|
|
283
|
+
: {
|
|
284
|
+
flexDirection: 'row',
|
|
285
|
+
alignItems: 'center',
|
|
286
|
+
gap: 2,
|
|
287
|
+
}
|
|
288
|
+
}>
|
|
212
289
|
<Typography variant="subtitle2" color="text.secondary">
|
|
213
|
-
{t('customer.overdraftProtection.
|
|
290
|
+
{t('customer.overdraftProtection.payerAddress')}
|
|
214
291
|
</Typography>
|
|
215
292
|
<Typography variant="body2" sx={{ wordBreak: 'break-all', fontFamily: 'monospace', fontWeight: '700' }}>
|
|
216
|
-
{
|
|
293
|
+
{payerAddress}
|
|
294
|
+
</Typography>
|
|
295
|
+
</Stack>
|
|
296
|
+
)}
|
|
297
|
+
|
|
298
|
+
{stakingAddress && (
|
|
299
|
+
<Stack
|
|
300
|
+
sx={
|
|
301
|
+
isMobile
|
|
302
|
+
? {
|
|
303
|
+
flexDirection: 'column',
|
|
304
|
+
gap: 1,
|
|
305
|
+
}
|
|
306
|
+
: {
|
|
307
|
+
flexDirection: 'row',
|
|
308
|
+
alignItems: 'center',
|
|
309
|
+
gap: 2,
|
|
310
|
+
}
|
|
311
|
+
}>
|
|
312
|
+
<Typography variant="subtitle2" color="text.secondary">
|
|
313
|
+
{t('customer.overdraftProtection.stakingAddress')}
|
|
217
314
|
</Typography>
|
|
315
|
+
<Link
|
|
316
|
+
href={getStakingAddressURL()}
|
|
317
|
+
target="_blank"
|
|
318
|
+
rel="noopener noreferrer"
|
|
319
|
+
sx={{
|
|
320
|
+
wordBreak: 'break-all',
|
|
321
|
+
fontFamily: 'monospace',
|
|
322
|
+
fontWeight: '700',
|
|
323
|
+
textDecoration: 'none',
|
|
324
|
+
color: 'text.link',
|
|
325
|
+
display: 'flex',
|
|
326
|
+
flexWrap: 'wrap',
|
|
327
|
+
alignItems: 'center',
|
|
328
|
+
gap: 1,
|
|
329
|
+
}}>
|
|
330
|
+
{stakingAddress}
|
|
331
|
+
{!isMobile && <OpenInNewOutlined fontSize="small" />}
|
|
332
|
+
</Link>
|
|
218
333
|
</Stack>
|
|
219
334
|
)}
|
|
335
|
+
|
|
220
336
|
<Typography variant="body2" color="text.secondary">
|
|
221
337
|
{(() => {
|
|
222
338
|
if (Number(dueAmount) > 0 && !value.enabled) {
|
|
@@ -242,62 +358,91 @@ export default function OverdraftProtectionDialog({
|
|
|
242
358
|
</Typography>
|
|
243
359
|
|
|
244
360
|
<Grid container spacing={2} ml={-2} sx={{ mt: -1 }}>
|
|
245
|
-
{
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
height: '100%'
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
<
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
361
|
+
{cycleAmountLoading ? (
|
|
362
|
+
// 加载状态的占位
|
|
363
|
+
<>
|
|
364
|
+
{[1, 2, 3, 4, 5].map((key) => (
|
|
365
|
+
<Grid item xs={6} sm={4} key={key}>
|
|
366
|
+
<Card variant="outlined" sx={{ height: '100%' }}>
|
|
367
|
+
<CardActionArea sx={{ height: '100%', p: 1 }}>
|
|
368
|
+
<Stack spacing={1} alignItems="center">
|
|
369
|
+
<Skeleton variant="rectangular" width={80} height={32} />
|
|
370
|
+
<Skeleton width={100} />
|
|
371
|
+
</Stack>
|
|
372
|
+
</CardActionArea>
|
|
373
|
+
</Card>
|
|
374
|
+
</Grid>
|
|
375
|
+
))}
|
|
376
|
+
<Grid item xs={6} sm={4}>
|
|
377
|
+
<Card variant="outlined" sx={{ height: '100%' }}>
|
|
378
|
+
<CardActionArea sx={{ height: '100%', p: 2 }}>
|
|
379
|
+
<Stack spacing={1} alignItems="center">
|
|
380
|
+
<Skeleton variant="rectangular" width={80} height={24} />
|
|
381
|
+
</Stack>
|
|
382
|
+
</CardActionArea>
|
|
383
|
+
</Card>
|
|
384
|
+
</Grid>
|
|
385
|
+
</>
|
|
386
|
+
) : (
|
|
387
|
+
<>
|
|
388
|
+
{presetAmounts.map(({ amount: presetAmount, cycles }) => (
|
|
389
|
+
<Grid item xs={6} sm={4} key={presetAmount}>
|
|
390
|
+
<Card
|
|
391
|
+
variant="outlined"
|
|
392
|
+
sx={{
|
|
393
|
+
height: '100%',
|
|
394
|
+
transition: 'all 0.3s',
|
|
395
|
+
cursor: 'pointer',
|
|
396
|
+
'&:hover': {
|
|
397
|
+
transform: 'translateY(-4px)',
|
|
398
|
+
boxShadow: 3,
|
|
399
|
+
},
|
|
400
|
+
...(amount === presetAmount && !customAmount
|
|
401
|
+
? { borderColor: 'primary.main', borderWidth: 2 }
|
|
402
|
+
: {}),
|
|
403
|
+
}}>
|
|
404
|
+
<CardActionArea
|
|
405
|
+
onClick={() => {
|
|
406
|
+
methods.setValue('amount', presetAmount);
|
|
407
|
+
setCustomAmount(false);
|
|
408
|
+
}}
|
|
409
|
+
sx={{ height: '100%', p: 1 }}>
|
|
410
|
+
<Stack spacing={1} alignItems="center">
|
|
411
|
+
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
|
412
|
+
{presetAmount} {currency.symbol}
|
|
413
|
+
</Typography>
|
|
414
|
+
<Typography variant="caption" color="text.secondary">
|
|
415
|
+
{formatEstimatedDuration(cycles)}
|
|
416
|
+
</Typography>
|
|
417
|
+
</Stack>
|
|
418
|
+
</CardActionArea>
|
|
419
|
+
</Card>
|
|
420
|
+
</Grid>
|
|
421
|
+
))}
|
|
422
|
+
<Grid item xs={6} sm={4}>
|
|
423
|
+
<Card
|
|
424
|
+
variant="outlined"
|
|
425
|
+
sx={{
|
|
426
|
+
height: '100%',
|
|
427
|
+
transition: 'all 0.3s',
|
|
428
|
+
cursor: 'pointer',
|
|
429
|
+
'&:hover': {
|
|
430
|
+
transform: 'translateY(-4px)',
|
|
431
|
+
boxShadow: 3,
|
|
432
|
+
},
|
|
433
|
+
...(customAmount ? { borderColor: 'primary.main', borderWidth: 2 } : {}),
|
|
434
|
+
}}>
|
|
435
|
+
<CardActionArea onClick={handleCustomSelect} sx={{ height: '100%', p: 2 }}>
|
|
436
|
+
<Stack spacing={1} alignItems="center">
|
|
437
|
+
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
|
438
|
+
{t('common.custom')}
|
|
439
|
+
</Typography>
|
|
440
|
+
</Stack>
|
|
441
|
+
</CardActionArea>
|
|
442
|
+
</Card>
|
|
443
|
+
</Grid>
|
|
444
|
+
</>
|
|
445
|
+
)}
|
|
301
446
|
</Grid>
|
|
302
447
|
|
|
303
448
|
{customAmount && (
|
|
@@ -346,7 +491,7 @@ export default function OverdraftProtectionDialog({
|
|
|
346
491
|
</FormControl>
|
|
347
492
|
</Stack>
|
|
348
493
|
)}
|
|
349
|
-
{amount && Number(amount) > 0 && !methods.formState.errors.amount && (
|
|
494
|
+
{amount && Number(amount) > 0 && Number(estimateAmount) > 0 && !methods.formState.errors.amount && (
|
|
350
495
|
<Typography variant="body2" sx={{ color: 'text.lighter', mt: '8px !important' }} fontSize={12}>
|
|
351
496
|
{t('customer.overdraftProtection.total', {
|
|
352
497
|
total: safeAdd(currency, amount, availableAmount),
|
|
@@ -3,21 +3,36 @@ import { api, formatBNStr, formatTime } from '@blocklet/payment-react';
|
|
|
3
3
|
import type { TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
4
4
|
import { useRequest } from 'ahooks';
|
|
5
5
|
|
|
6
|
+
import { Button, Stack, Typography, Tooltip, Avatar, Box, CircularProgress, Skeleton } from '@mui/material';
|
|
7
|
+
import { BN } from '@ocap/util';
|
|
8
|
+
import { useNavigate } from 'react-router-dom';
|
|
9
|
+
import { ArrowForward, InfoOutlined } from '@mui/icons-material';
|
|
6
10
|
import InfoMetric from '../info-metric';
|
|
7
11
|
import SubscriptionStatus from './status';
|
|
8
12
|
|
|
9
13
|
type Props = {
|
|
10
14
|
subscription: TSubscriptionExpanded;
|
|
15
|
+
showBalance?: boolean;
|
|
11
16
|
};
|
|
12
17
|
|
|
13
18
|
const fetchUpcoming = (id: string): Promise<{ amount: string }> => {
|
|
14
19
|
return api.get(`/api/subscriptions/${id}/upcoming`).then((res) => res.data);
|
|
15
20
|
};
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
const fetchPayer = (id: string): Promise<{ token: string; paymentAddress: string }> => {
|
|
23
|
+
return api.get(`/api/subscriptions/${id}/payer-token`).then((res) => res.data);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default function SubscriptionMetrics({ subscription, showBalance = true }: Props) {
|
|
18
27
|
const { t } = useLocaleContext();
|
|
19
|
-
const { data: upcoming } = useRequest(() => fetchUpcoming(subscription.id));
|
|
28
|
+
const { data: upcoming, loading: upcomingLoading } = useRequest(() => fetchUpcoming(subscription.id));
|
|
29
|
+
const navigate = useNavigate();
|
|
30
|
+
|
|
31
|
+
const { data: payerValue, loading: payerLoading } = useRequest(() => fetchPayer(subscription.id), {
|
|
32
|
+
ready: showBalance,
|
|
33
|
+
});
|
|
20
34
|
|
|
35
|
+
const supportShowBalance = showBalance && ['arcblock', 'ethereum'].includes(subscription.paymentMethod.type);
|
|
21
36
|
// let scheduleToCancelTime = 0;
|
|
22
37
|
// if (['active', 'trialing', 'past_due'].includes(subscription.status) && subscription.cancel_at) {
|
|
23
38
|
// scheduleToCancelTime = subscription.cancel_at * 1000;
|
|
@@ -25,14 +40,43 @@ export default function SubscriptionMetrics({ subscription }: Props) {
|
|
|
25
40
|
// scheduleToCancelTime = subscription.current_period_end * 1000;
|
|
26
41
|
// }
|
|
27
42
|
|
|
43
|
+
const isInsufficientBalance = new BN(payerValue?.token || '0').lt(new BN(upcoming?.amount || '0'));
|
|
44
|
+
const renderBalanceValue = () => {
|
|
45
|
+
if (upcomingLoading || payerLoading) {
|
|
46
|
+
return <CircularProgress size={16} />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isInsufficientBalance) {
|
|
50
|
+
return (
|
|
51
|
+
<Button
|
|
52
|
+
component="a"
|
|
53
|
+
color="error"
|
|
54
|
+
onClick={() => navigate(`/customer/subscription/${subscription.id}/recharge`)}>
|
|
55
|
+
{t('admin.subscription.insufficientBalance')}
|
|
56
|
+
<ArrowForward sx={{ fontSize: '14px' }} />
|
|
57
|
+
</Button>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Stack flexDirection="row" alignItems="center" gap={0.5} sx={{ fontSize: '16px', fontWeight: 500 }}>
|
|
63
|
+
<Avatar
|
|
64
|
+
src={subscription.paymentCurrency?.logo}
|
|
65
|
+
sx={{ width: 16, height: 16 }}
|
|
66
|
+
alt={subscription.paymentCurrency?.name}
|
|
67
|
+
/>
|
|
68
|
+
<Box display="flex" alignItems="baseline">
|
|
69
|
+
{formatBNStr(payerValue?.token, subscription.paymentCurrency.decimal)}
|
|
70
|
+
<Typography sx={{ fontSize: '14px', color: 'text.secondary', ml: 0.5 }}>
|
|
71
|
+
{subscription.paymentCurrency.symbol}
|
|
72
|
+
</Typography>
|
|
73
|
+
</Box>
|
|
74
|
+
</Stack>
|
|
75
|
+
);
|
|
76
|
+
};
|
|
28
77
|
return (
|
|
29
78
|
<>
|
|
30
79
|
<InfoMetric label={t('common.status')} value={<SubscriptionStatus subscription={subscription} />} divider />
|
|
31
|
-
<InfoMetric
|
|
32
|
-
label={t('admin.subscription.startedAt')}
|
|
33
|
-
value={formatTime(subscription.start_date ? subscription.start_date * 1000 : subscription.created_at)}
|
|
34
|
-
divider
|
|
35
|
-
/>
|
|
36
80
|
{subscription.status === 'active' && !subscription.cancel_at && (
|
|
37
81
|
<InfoMetric
|
|
38
82
|
label={t('admin.subscription.nextInvoice')}
|
|
@@ -49,6 +93,34 @@ export default function SubscriptionMetrics({ subscription }: Props) {
|
|
|
49
93
|
divider
|
|
50
94
|
/>
|
|
51
95
|
)}
|
|
96
|
+
{supportShowBalance && ['active', 'trialing'].includes(subscription.status) && (
|
|
97
|
+
<InfoMetric
|
|
98
|
+
label={
|
|
99
|
+
<Stack direction="row" spacing={1} alignItems="center">
|
|
100
|
+
<Typography>{t('admin.subscription.currentBalance')}</Typography>
|
|
101
|
+
<Tooltip
|
|
102
|
+
title={
|
|
103
|
+
<Typography sx={{ fontFamily: 'monospace', fontSize: '13px' }}>
|
|
104
|
+
{t('admin.subscription.paymentAddress')}:
|
|
105
|
+
{payerLoading ? <Skeleton width={120} /> : payerValue?.paymentAddress}
|
|
106
|
+
</Typography>
|
|
107
|
+
}
|
|
108
|
+
arrow>
|
|
109
|
+
<InfoOutlined
|
|
110
|
+
sx={{
|
|
111
|
+
fontSize: '16px',
|
|
112
|
+
color: 'text.secondary',
|
|
113
|
+
cursor: 'pointer',
|
|
114
|
+
'&:hover': { color: 'primary.main' },
|
|
115
|
+
}}
|
|
116
|
+
/>
|
|
117
|
+
</Tooltip>
|
|
118
|
+
</Stack>
|
|
119
|
+
}
|
|
120
|
+
value={renderBalanceValue()}
|
|
121
|
+
divider
|
|
122
|
+
/>
|
|
123
|
+
)}
|
|
52
124
|
{/* {scheduleToCancelTime > 0 && (
|
|
53
125
|
<InfoMetric label={t('admin.subscription.cancel.schedule')} value={formatTime(scheduleToCancelTime)} divider />
|
|
54
126
|
)} */}
|
|
@@ -62,3 +134,7 @@ export default function SubscriptionMetrics({ subscription }: Props) {
|
|
|
62
134
|
</>
|
|
63
135
|
);
|
|
64
136
|
}
|
|
137
|
+
|
|
138
|
+
SubscriptionMetrics.defaultProps = {
|
|
139
|
+
showBalance: true,
|
|
140
|
+
};
|