@blocklet/payment-react 1.13.124 → 1.13.126
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/es/api.js +0 -1
- package/es/components/confirm.d.ts +14 -0
- package/es/components/confirm.js +31 -0
- package/es/components/pricing-table.js +14 -5
- package/es/index.d.ts +4 -1
- package/es/index.js +7 -1
- package/es/locales/en.js +5 -1
- package/es/locales/zh.js +4 -0
- package/es/payment/index.js +3 -3
- package/es/portal/invoice/list.d.ts +6 -0
- package/es/portal/invoice/list.js +84 -0
- package/es/portal/payment/list.d.ts +6 -0
- package/es/portal/payment/list.js +84 -0
- package/es/util.d.ts +4 -0
- package/es/util.js +56 -0
- package/lib/api.js +0 -1
- package/lib/components/confirm.d.ts +14 -0
- package/lib/components/confirm.js +49 -0
- package/lib/components/pricing-table.js +144 -135
- package/lib/index.d.ts +4 -1
- package/lib/index.js +24 -0
- package/lib/locales/en.js +5 -1
- package/lib/locales/zh.js +4 -0
- package/lib/payment/index.js +3 -3
- package/lib/portal/invoice/list.d.ts +6 -0
- package/lib/portal/invoice/list.js +150 -0
- package/lib/portal/payment/list.d.ts +6 -0
- package/lib/portal/payment/list.js +149 -0
- package/lib/util.d.ts +4 -0
- package/lib/util.js +62 -1
- package/package.json +3 -3
- package/src/api.ts +1 -1
- package/src/components/confirm.tsx +39 -0
- package/src/components/pricing-table.tsx +124 -113
- package/src/index.ts +6 -0
- package/src/locales/en.tsx +5 -1
- package/src/locales/zh.tsx +4 -0
- package/src/payment/index.tsx +3 -3
- package/src/portal/invoice/list.tsx +122 -0
- package/src/portal/payment/list.tsx +120 -0
- package/src/util.ts +60 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/* eslint-disable react/no-unstable-nested-components */
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
import type { Paginated, TInvoiceExpanded } from '@blocklet/payment-types';
|
|
4
|
+
import { Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
|
|
5
|
+
import { fromUnitToToken } from '@ocap/util';
|
|
6
|
+
import { useInfiniteScroll } from 'ahooks';
|
|
7
|
+
|
|
8
|
+
import api from '../../api';
|
|
9
|
+
import Status from '../../components/status';
|
|
10
|
+
import { formatToDate, getInvoiceStatusColor } from '../../util';
|
|
11
|
+
|
|
12
|
+
const groupByDate = (items: TInvoiceExpanded[]) => {
|
|
13
|
+
const grouped: { [key: string]: TInvoiceExpanded[] } = {};
|
|
14
|
+
items.forEach((item) => {
|
|
15
|
+
const date = new Date(item.created_at).toLocaleDateString();
|
|
16
|
+
if (!grouped[date]) {
|
|
17
|
+
grouped[date] = [];
|
|
18
|
+
}
|
|
19
|
+
grouped[date]?.push(item);
|
|
20
|
+
});
|
|
21
|
+
return grouped;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const fetchData = (params: Record<string, any> = {}): Promise<Paginated<TInvoiceExpanded>> => {
|
|
25
|
+
const search = new URLSearchParams();
|
|
26
|
+
Object.keys(params).forEach((key) => {
|
|
27
|
+
search.set(key, String(params[key]));
|
|
28
|
+
});
|
|
29
|
+
return api.get(`/api/invoices?${search.toString()}`).then((res: any) => res.data);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type Props = {
|
|
33
|
+
customer_id: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const pageSize = 10;
|
|
37
|
+
|
|
38
|
+
export default function CustomerInvoiceList({ customer_id }: Props) {
|
|
39
|
+
const { t } = useLocaleContext();
|
|
40
|
+
|
|
41
|
+
const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TInvoiceExpanded>>(
|
|
42
|
+
(d) => {
|
|
43
|
+
const page = d ? Math.ceil(d.list.length / pageSize) + 1 : 1;
|
|
44
|
+
return fetchData({ page, pageSize, status: 'open,paid,uncollectible', customer_id });
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
reloadDeps: [customer_id],
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (loading || !data) {
|
|
52
|
+
return <CircularProgress />;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (data && data.list.length === 0) {
|
|
56
|
+
return <Typography color="text.secondary">{t('payment.customer.invoice.empty')}</Typography>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const hasMore = data && data.list.length < data.count;
|
|
60
|
+
|
|
61
|
+
const grouped = groupByDate(data.list as any);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Stack direction="column" gap={1} sx={{ mt: 1 }}>
|
|
65
|
+
{Object.entries(grouped).map(([date, invoices]) => (
|
|
66
|
+
<Box key={date}>
|
|
67
|
+
<Typography sx={{ fontWeight: 'bold', color: 'text.secondary', mt: 2, mb: 1 }}>{date}</Typography>
|
|
68
|
+
{invoices.map((invoice) => (
|
|
69
|
+
<Stack
|
|
70
|
+
key={invoice.id}
|
|
71
|
+
direction={{
|
|
72
|
+
xs: 'column',
|
|
73
|
+
sm: 'row',
|
|
74
|
+
}}
|
|
75
|
+
sx={{ my: 1 }}
|
|
76
|
+
gap={{
|
|
77
|
+
xs: 0.5,
|
|
78
|
+
sm: 1.5,
|
|
79
|
+
md: 3,
|
|
80
|
+
}}
|
|
81
|
+
flexWrap="nowrap">
|
|
82
|
+
<Box flex={3}>
|
|
83
|
+
<a href={`/customer/invoice/${invoice.id}`}>
|
|
84
|
+
<Typography component="span">{invoice.number}</Typography>
|
|
85
|
+
</a>
|
|
86
|
+
</Box>
|
|
87
|
+
<Box flex={3}>
|
|
88
|
+
<Typography>{formatToDate(invoice.created_at)}</Typography>
|
|
89
|
+
</Box>
|
|
90
|
+
<Box flex={2}>
|
|
91
|
+
<Typography textAlign="right">
|
|
92
|
+
{fromUnitToToken(invoice.total, invoice.paymentCurrency.decimal)}
|
|
93
|
+
{invoice.paymentCurrency.symbol}
|
|
94
|
+
</Typography>
|
|
95
|
+
</Box>
|
|
96
|
+
<Box flex={2}>
|
|
97
|
+
<Status label={invoice.status} color={getInvoiceStatusColor(invoice.status)} />
|
|
98
|
+
</Box>
|
|
99
|
+
<Box flex={4}>
|
|
100
|
+
<Typography>{invoice.description || invoice.id}</Typography>
|
|
101
|
+
</Box>
|
|
102
|
+
</Stack>
|
|
103
|
+
))}
|
|
104
|
+
</Box>
|
|
105
|
+
))}
|
|
106
|
+
<Box>
|
|
107
|
+
{hasMore && (
|
|
108
|
+
<Button variant="text" type="button" color="inherit" onClick={loadMore} disabled={loadingMore}>
|
|
109
|
+
{loadingMore
|
|
110
|
+
? t('common.loadingMore', { resource: t('payment.customer.invoices') })
|
|
111
|
+
: t('common.loadMore', { resource: t('payament.customer.invoices') })}
|
|
112
|
+
</Button>
|
|
113
|
+
)}
|
|
114
|
+
{!hasMore && data.count > pageSize && (
|
|
115
|
+
<Typography color="text.secondary">
|
|
116
|
+
{t('common.noMore', { resource: t('payment.customer.invoices') })}
|
|
117
|
+
</Typography>
|
|
118
|
+
)}
|
|
119
|
+
</Box>
|
|
120
|
+
</Stack>
|
|
121
|
+
);
|
|
122
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* eslint-disable react/no-unstable-nested-components */
|
|
2
|
+
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
|
+
import type { Paginated, TPaymentIntentExpanded } from '@blocklet/payment-types';
|
|
4
|
+
import { Box, Button, CircularProgress, Stack, Typography } from '@mui/material';
|
|
5
|
+
import { fromUnitToToken } from '@ocap/util';
|
|
6
|
+
import { useInfiniteScroll } from 'ahooks';
|
|
7
|
+
|
|
8
|
+
import api from '../../api';
|
|
9
|
+
import Status from '../../components/status';
|
|
10
|
+
import { formatToDate, getPaymentIntentStatusColor } from '../../util';
|
|
11
|
+
|
|
12
|
+
const groupByDate = (items: TPaymentIntentExpanded[]) => {
|
|
13
|
+
const grouped: { [key: string]: TPaymentIntentExpanded[] } = {};
|
|
14
|
+
items.forEach((item) => {
|
|
15
|
+
const date = new Date(item.created_at).toLocaleDateString();
|
|
16
|
+
if (!grouped[date]) {
|
|
17
|
+
grouped[date] = [];
|
|
18
|
+
}
|
|
19
|
+
grouped[date]?.push(item);
|
|
20
|
+
});
|
|
21
|
+
return grouped;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const fetchData = (params: Record<string, any> = {}): Promise<Paginated<TPaymentIntentExpanded>> => {
|
|
25
|
+
const search = new URLSearchParams();
|
|
26
|
+
Object.keys(params).forEach((key) => {
|
|
27
|
+
search.set(key, String(params[key]));
|
|
28
|
+
});
|
|
29
|
+
return api.get(`/api/payment-intents?${search.toString()}`).then((res: any) => res.data);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type Props = {
|
|
33
|
+
customer_id: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const pageSize = 10;
|
|
37
|
+
|
|
38
|
+
export default function CustomerPaymentList({ customer_id }: Props) {
|
|
39
|
+
const { t } = useLocaleContext();
|
|
40
|
+
|
|
41
|
+
const { data, loadMore, loadingMore, loading } = useInfiniteScroll<Paginated<TPaymentIntentExpanded>>(
|
|
42
|
+
(d) => {
|
|
43
|
+
const page = d ? Math.ceil(d.list.length / pageSize) + 1 : 1;
|
|
44
|
+
return fetchData({ page, pageSize, customer_id });
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
reloadDeps: [customer_id],
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (loading || !data) {
|
|
52
|
+
return <CircularProgress />;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (data && data.list.length === 0) {
|
|
56
|
+
return <Typography color="text.secondary">{t('payment.customer.payment.empty')}</Typography>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const hasMore = data && data.list.length < data.count;
|
|
60
|
+
|
|
61
|
+
const grouped = groupByDate(data.list as any);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<Stack direction="column" gap={1} sx={{ mt: 1 }}>
|
|
65
|
+
{Object.entries(grouped).map(([date, payments]) => (
|
|
66
|
+
<Box key={date}>
|
|
67
|
+
<Typography sx={{ fontWeight: 'bold', color: 'text.secondary', mt: 2, mb: 1 }}>{date}</Typography>
|
|
68
|
+
{payments.map((item) => (
|
|
69
|
+
<Stack
|
|
70
|
+
key={item.id}
|
|
71
|
+
direction={{
|
|
72
|
+
xs: 'column',
|
|
73
|
+
sm: 'row',
|
|
74
|
+
}}
|
|
75
|
+
sx={{ my: 1 }}
|
|
76
|
+
gap={{
|
|
77
|
+
xs: 0.5,
|
|
78
|
+
sm: 1.5,
|
|
79
|
+
md: 3,
|
|
80
|
+
}}
|
|
81
|
+
flexWrap="nowrap">
|
|
82
|
+
<Box flex={3} sx={{ minWidth: '220px' }}>
|
|
83
|
+
<Typography component="span">{item.id}</Typography>
|
|
84
|
+
</Box>
|
|
85
|
+
<Box flex={3}>
|
|
86
|
+
<Typography>{formatToDate(item.created_at)}</Typography>
|
|
87
|
+
</Box>
|
|
88
|
+
<Box flex={2}>
|
|
89
|
+
<Typography textAlign="right">
|
|
90
|
+
{fromUnitToToken(item.amount, item.paymentCurrency.decimal)}
|
|
91
|
+
{item.paymentCurrency.symbol}
|
|
92
|
+
</Typography>
|
|
93
|
+
</Box>
|
|
94
|
+
<Box flex={3}>
|
|
95
|
+
<Status label={item.status} color={getPaymentIntentStatusColor(item.status)} />
|
|
96
|
+
</Box>
|
|
97
|
+
<Box flex={4}>
|
|
98
|
+
<Typography>{item.description || item.id}</Typography>
|
|
99
|
+
</Box>
|
|
100
|
+
</Stack>
|
|
101
|
+
))}
|
|
102
|
+
</Box>
|
|
103
|
+
))}
|
|
104
|
+
<Box>
|
|
105
|
+
{hasMore && (
|
|
106
|
+
<Button variant="text" type="button" color="inherit" onClick={loadMore} disabled={loadingMore}>
|
|
107
|
+
{loadingMore
|
|
108
|
+
? t('common.loadingMore', { resource: t('payment.customer.payments') })
|
|
109
|
+
: t('common.loadMore', { resource: t('payment.customer.payments') })}
|
|
110
|
+
</Button>
|
|
111
|
+
)}
|
|
112
|
+
{!hasMore && data.count > pageSize && (
|
|
113
|
+
<Typography color="text.secondary">
|
|
114
|
+
{t('common.noMore', { resource: t('payment.customer.payments') })}
|
|
115
|
+
</Typography>
|
|
116
|
+
)}
|
|
117
|
+
</Box>
|
|
118
|
+
</Stack>
|
|
119
|
+
);
|
|
120
|
+
}
|
package/src/util.ts
CHANGED
|
@@ -256,6 +256,66 @@ export function formatLineItemPricing(
|
|
|
256
256
|
};
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
+
export function getSubscriptionStatusColor(status: string) {
|
|
260
|
+
switch (status) {
|
|
261
|
+
case 'active':
|
|
262
|
+
return 'success';
|
|
263
|
+
case 'trialing':
|
|
264
|
+
return 'primary';
|
|
265
|
+
case 'incomplete':
|
|
266
|
+
case 'incomplete_expired':
|
|
267
|
+
case 'paused':
|
|
268
|
+
return 'warning';
|
|
269
|
+
case 'past_due':
|
|
270
|
+
case 'unpaid':
|
|
271
|
+
return 'error';
|
|
272
|
+
case 'canceled':
|
|
273
|
+
default:
|
|
274
|
+
return 'default';
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function getPaymentIntentStatusColor(status: string) {
|
|
279
|
+
switch (status) {
|
|
280
|
+
case 'succeeded':
|
|
281
|
+
return 'success';
|
|
282
|
+
case 'requires_payment_method':
|
|
283
|
+
case 'requires_confirmation':
|
|
284
|
+
case 'requires_action':
|
|
285
|
+
case 'requires_capture':
|
|
286
|
+
return 'warning';
|
|
287
|
+
case 'canceled':
|
|
288
|
+
case 'processing':
|
|
289
|
+
default:
|
|
290
|
+
return 'default';
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function getInvoiceStatusColor(status: string) {
|
|
295
|
+
switch (status) {
|
|
296
|
+
case 'paid':
|
|
297
|
+
return 'success';
|
|
298
|
+
case 'open':
|
|
299
|
+
return 'secondary';
|
|
300
|
+
case 'uncollectible':
|
|
301
|
+
return 'warning';
|
|
302
|
+
case 'draft':
|
|
303
|
+
case 'void':
|
|
304
|
+
default:
|
|
305
|
+
return 'default';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function getWebhookStatusColor(status: string) {
|
|
310
|
+
switch (status) {
|
|
311
|
+
case 'enabled':
|
|
312
|
+
return 'success';
|
|
313
|
+
case 'disabled':
|
|
314
|
+
default:
|
|
315
|
+
return 'default';
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
259
319
|
export function getCheckoutAmount(
|
|
260
320
|
items: TLineItemExpanded[],
|
|
261
321
|
currency: TPaymentCurrency,
|