payment-kit 1.20.20 → 1.20.22
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/stripe/resource.ts +1 -1
- package/api/src/libs/discount/coupon.ts +41 -73
- package/api/src/libs/invoice.ts +17 -0
- package/api/src/libs/notification/template/subscription-renew-failed.ts +22 -1
- package/api/src/libs/notification/template/subscription-will-renew.ts +22 -0
- package/api/src/locales/en.ts +1 -0
- package/api/src/locales/zh.ts +1 -0
- package/api/src/queues/checkout-session.ts +2 -2
- package/api/src/queues/vendors/fulfillment-coordinator.ts +11 -2
- package/api/src/queues/vendors/status-check.ts +1 -3
- package/api/src/routes/checkout-sessions.ts +84 -0
- package/api/src/routes/connect/collect-batch.ts +2 -2
- package/api/src/routes/connect/pay.ts +1 -1
- package/api/src/routes/vendor.ts +93 -47
- package/api/src/store/migrations/20250926-change-customer-did-unique.ts +49 -0
- package/api/src/store/models/checkout-session.ts +1 -0
- package/api/src/store/models/customer.ts +1 -0
- package/api/tests/libs/coupon.spec.ts +219 -0
- package/api/tests/libs/discount.spec.ts +250 -0
- package/blocklet.yml +1 -1
- package/package.json +7 -7
- package/src/components/discount/discount-info.tsx +0 -1
- package/src/components/invoice/action.tsx +26 -0
- package/src/components/invoice/table.tsx +2 -9
- package/src/components/invoice-pdf/styles.ts +2 -0
- package/src/components/invoice-pdf/template.tsx +44 -12
- package/src/components/metadata/list.tsx +1 -0
- package/src/components/subscription/metrics.tsx +7 -3
- package/src/components/subscription/vendor-service-list.tsx +56 -58
- package/src/locales/en.tsx +9 -2
- package/src/locales/zh.tsx +9 -2
- package/src/pages/admin/billing/subscriptions/detail.tsx +43 -4
- package/src/pages/admin/products/coupons/applicable-products.tsx +20 -37
- package/src/pages/admin/products/products/detail.tsx +4 -14
- package/src/pages/admin/products/vendors/index.tsx +57 -48
- package/src/pages/customer/invoice/detail.tsx +1 -1
- package/src/pages/customer/subscription/detail.tsx +17 -4
|
@@ -13,9 +13,16 @@ interface VendorConfig {
|
|
|
13
13
|
interface VendorServiceListProps {
|
|
14
14
|
vendorServices: VendorConfig[];
|
|
15
15
|
subscriptionId: string;
|
|
16
|
+
isOwner?: boolean;
|
|
17
|
+
isCanceled: boolean;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
export default function VendorServiceList({
|
|
20
|
+
export default function VendorServiceList({
|
|
21
|
+
vendorServices,
|
|
22
|
+
subscriptionId,
|
|
23
|
+
isOwner = true,
|
|
24
|
+
isCanceled,
|
|
25
|
+
}: VendorServiceListProps) {
|
|
19
26
|
const { t } = useLocaleContext();
|
|
20
27
|
|
|
21
28
|
if (!vendorServices || vendorServices.length === 0) {
|
|
@@ -31,7 +38,6 @@ export default function VendorServiceList({ vendorServices, subscriptionId }: Ve
|
|
|
31
38
|
</Typography>
|
|
32
39
|
<Box className="section-body">
|
|
33
40
|
<Stack
|
|
34
|
-
spacing={2}
|
|
35
41
|
sx={{
|
|
36
42
|
display: 'grid',
|
|
37
43
|
gridTemplateColumns: { xs: '1fr', md: '1fr 1fr', lg: '1fr 1fr 1fr' },
|
|
@@ -39,12 +45,14 @@ export default function VendorServiceList({ vendorServices, subscriptionId }: Ve
|
|
|
39
45
|
}}>
|
|
40
46
|
{vendorServices.map((vendor, index) => {
|
|
41
47
|
const isLauncher = vendor.vendor_type === 'launcher';
|
|
42
|
-
|
|
43
48
|
return (
|
|
44
49
|
<Box
|
|
45
50
|
key={vendor.vendor_key || index}
|
|
51
|
+
className="vendor-service-item"
|
|
46
52
|
sx={{
|
|
47
53
|
p: 2,
|
|
54
|
+
display: 'flex',
|
|
55
|
+
alignItems: 'center',
|
|
48
56
|
border: '1px solid',
|
|
49
57
|
borderColor: 'divider',
|
|
50
58
|
borderRadius: 2,
|
|
@@ -54,40 +62,28 @@ export default function VendorServiceList({ vendorServices, subscriptionId }: Ve
|
|
|
54
62
|
},
|
|
55
63
|
transition: 'background-color 0.2s ease',
|
|
56
64
|
}}>
|
|
57
|
-
<Stack
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
<Stack direction="row" spacing={1} sx={{ alignItems: 'center', flex: 1 }}>
|
|
66
|
+
<Box
|
|
67
|
+
sx={{
|
|
68
|
+
width: 8,
|
|
69
|
+
height: 8,
|
|
70
|
+
borderRadius: '50%',
|
|
71
|
+
bgcolor: isCanceled ? 'error.main' : 'success.main',
|
|
72
|
+
flexShrink: 0,
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
<Typography
|
|
76
|
+
variant="body1"
|
|
66
77
|
sx={{
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
fontWeight: 600,
|
|
79
|
+
fontSize: '1rem',
|
|
80
|
+
color: 'text.primary',
|
|
69
81
|
}}>
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
bgcolor: 'success.main',
|
|
76
|
-
flexShrink: 0,
|
|
77
|
-
}}
|
|
78
|
-
/>
|
|
79
|
-
<Typography
|
|
80
|
-
variant="body1"
|
|
81
|
-
sx={{
|
|
82
|
-
fontWeight: 600,
|
|
83
|
-
fontSize: '1rem',
|
|
84
|
-
color: 'text.primary',
|
|
85
|
-
}}>
|
|
86
|
-
{vendor.name || vendor.vendor_key}
|
|
87
|
-
</Typography>
|
|
88
|
-
</Stack>
|
|
89
|
-
{/* Launcher 类型的链接 */}
|
|
90
|
-
{isLauncher && (
|
|
82
|
+
{vendor.name || vendor.vendor_key}
|
|
83
|
+
</Typography>
|
|
84
|
+
</Stack>
|
|
85
|
+
{isLauncher && (
|
|
86
|
+
<Box>
|
|
91
87
|
<Stack direction="row" spacing={0.5}>
|
|
92
88
|
<Tooltip title={t('admin.subscription.serviceHome')} placement="top">
|
|
93
89
|
<IconButton
|
|
@@ -105,30 +101,32 @@ export default function VendorServiceList({ vendorServices, subscriptionId }: Ve
|
|
|
105
101
|
<Home fontSize="small" />
|
|
106
102
|
</IconButton>
|
|
107
103
|
</Tooltip>
|
|
108
|
-
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
104
|
+
{isOwner && (
|
|
105
|
+
<Tooltip title={t('admin.subscription.serviceDashboard')} placement="top">
|
|
106
|
+
<IconButton
|
|
107
|
+
size="small"
|
|
108
|
+
component="a"
|
|
109
|
+
href={joinURL(
|
|
110
|
+
prefix,
|
|
111
|
+
'/api/vendors/open/',
|
|
112
|
+
subscriptionId,
|
|
113
|
+
`?vendorId=${vendor.vendor_id}&target=dashboard`
|
|
114
|
+
)}
|
|
115
|
+
target="_blank"
|
|
116
|
+
rel="noopener noreferrer"
|
|
117
|
+
sx={{
|
|
118
|
+
color: 'primary.main',
|
|
119
|
+
'&:hover': {
|
|
120
|
+
backgroundColor: 'primary.lighter',
|
|
121
|
+
},
|
|
122
|
+
}}>
|
|
123
|
+
<Dashboard fontSize="small" />
|
|
124
|
+
</IconButton>
|
|
125
|
+
</Tooltip>
|
|
126
|
+
)}
|
|
129
127
|
</Stack>
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
</Box>
|
|
129
|
+
)}
|
|
132
130
|
</Box>
|
|
133
131
|
);
|
|
134
132
|
})}
|
package/src/locales/en.tsx
CHANGED
|
@@ -132,6 +132,7 @@ export default flat({
|
|
|
132
132
|
paymentMethods: 'Payment methods',
|
|
133
133
|
customers: 'Customers',
|
|
134
134
|
products: 'Products',
|
|
135
|
+
invoiceItems: 'Invoice Items',
|
|
135
136
|
pricing: 'Pricing',
|
|
136
137
|
coupons: 'Coupons',
|
|
137
138
|
pricingTables: 'Pricing tables',
|
|
@@ -1086,6 +1087,11 @@ export default flat({
|
|
|
1086
1087
|
download: 'Download PDF',
|
|
1087
1088
|
edit: 'Edit Invoice',
|
|
1088
1089
|
duplicate: 'Duplicate Invoice',
|
|
1090
|
+
retryUncollectible: {
|
|
1091
|
+
title: 'Retry collection',
|
|
1092
|
+
tip: 'Are you sure you want to retry collecting this invoice? This will attempt to charge the customer again.',
|
|
1093
|
+
success: 'Retry request submitted',
|
|
1094
|
+
},
|
|
1089
1095
|
returnStake: {
|
|
1090
1096
|
title: 'Return Stake',
|
|
1091
1097
|
tip: 'Are you sure you want to return the stake? This action will return the stake to the customer immediately.',
|
|
@@ -1164,8 +1170,8 @@ export default flat({
|
|
|
1164
1170
|
apiConfig: 'API Configuration',
|
|
1165
1171
|
commissionConfig: 'Commission Configuration',
|
|
1166
1172
|
status: 'Status',
|
|
1167
|
-
|
|
1168
|
-
|
|
1173
|
+
brokerDID: 'Broker DID',
|
|
1174
|
+
brokerPublicKey: 'Broker Public Key',
|
|
1169
1175
|
},
|
|
1170
1176
|
subscription: {
|
|
1171
1177
|
view: 'View subscription',
|
|
@@ -1546,6 +1552,7 @@ export default flat({
|
|
|
1546
1552
|
subscriptions: 'No Subscriptions',
|
|
1547
1553
|
customers: 'No Customers',
|
|
1548
1554
|
products: 'No Products',
|
|
1555
|
+
invoiceItems: 'No Invoice Items',
|
|
1549
1556
|
payouts: 'No Payouts',
|
|
1550
1557
|
paymentLinks: 'No Payment Links',
|
|
1551
1558
|
paymentMethods: 'No Payment Methods',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -131,6 +131,7 @@ export default flat({
|
|
|
131
131
|
paymentMethods: '支付方式',
|
|
132
132
|
customers: '客户管理',
|
|
133
133
|
products: '产品定价',
|
|
134
|
+
invoiceItems: '账单明细',
|
|
134
135
|
coupons: '优惠券',
|
|
135
136
|
pricing: '定价',
|
|
136
137
|
pricingTables: '定价表',
|
|
@@ -1057,6 +1058,11 @@ export default flat({
|
|
|
1057
1058
|
download: '下载PDF',
|
|
1058
1059
|
edit: '编辑账单',
|
|
1059
1060
|
duplicate: '复制账单',
|
|
1061
|
+
retryUncollectible: {
|
|
1062
|
+
title: '重新收款',
|
|
1063
|
+
tip: '确定要重新尝试收取该笔账单吗?系统将再次尝试向客户发起扣款。',
|
|
1064
|
+
success: '重新收款请求已提交',
|
|
1065
|
+
},
|
|
1060
1066
|
attention: '未完成的账单',
|
|
1061
1067
|
returnStake: {
|
|
1062
1068
|
title: '退还质押',
|
|
@@ -1136,8 +1142,8 @@ export default flat({
|
|
|
1136
1142
|
apiConfig: 'API配置',
|
|
1137
1143
|
commissionConfig: '分成配置',
|
|
1138
1144
|
status: '状态',
|
|
1139
|
-
|
|
1140
|
-
|
|
1145
|
+
brokerDID: '平台 DID',
|
|
1146
|
+
brokerPublicKey: '平台公钥',
|
|
1141
1147
|
},
|
|
1142
1148
|
subscription: {
|
|
1143
1149
|
view: '查看订阅',
|
|
@@ -1496,6 +1502,7 @@ export default flat({
|
|
|
1496
1502
|
image: '无图片',
|
|
1497
1503
|
refunds: '没有退款记录',
|
|
1498
1504
|
invoices: '没有账单',
|
|
1505
|
+
invoiceItems: '没有账单明细',
|
|
1499
1506
|
subscriptions: '没有订阅记录',
|
|
1500
1507
|
customers: '没有客户',
|
|
1501
1508
|
products: '没有产品',
|
|
@@ -33,6 +33,8 @@ import SubscriptionMetrics from '../../../../components/subscription/metrics';
|
|
|
33
33
|
import DiscountInfo from '../../../../components/discount/discount-info';
|
|
34
34
|
import { goBackOrFallback } from '../../../../libs/util';
|
|
35
35
|
import InfoRowGroup from '../../../../components/info-row-group';
|
|
36
|
+
import VendorServiceList from '../../../../components/subscription/vendor-service-list';
|
|
37
|
+
import { useSessionContext } from '../../../../contexts/session';
|
|
36
38
|
|
|
37
39
|
const fetchData = (id: string): Promise<TSubscriptionExpanded> => {
|
|
38
40
|
return api.get(`/api/subscriptions/${id}`).then((res) => res.data);
|
|
@@ -40,6 +42,7 @@ const fetchData = (id: string): Promise<TSubscriptionExpanded> => {
|
|
|
40
42
|
|
|
41
43
|
export default function SubscriptionDetail(props: { id: string }) {
|
|
42
44
|
const { t } = useLocaleContext();
|
|
45
|
+
const { session } = useSessionContext();
|
|
43
46
|
const { isMobile } = useMobile();
|
|
44
47
|
const [state, setState] = useSetState({
|
|
45
48
|
adding: {
|
|
@@ -178,7 +181,7 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
178
181
|
md: 3,
|
|
179
182
|
},
|
|
180
183
|
}}>
|
|
181
|
-
<SubscriptionMetrics subscription={data}
|
|
184
|
+
<SubscriptionMetrics subscription={data} mode="admin" />
|
|
182
185
|
</Stack>
|
|
183
186
|
</Box>
|
|
184
187
|
<Divider />
|
|
@@ -273,7 +276,6 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
273
276
|
<InfoRow label={t('common.resumesAt')} value={formatTime(data.pause_collection.resumes_at * 1000)} />
|
|
274
277
|
)}
|
|
275
278
|
<InfoRow label={t('admin.subscription.collectionMethod')} value={data.collection_method} />
|
|
276
|
-
<InfoRow label={t('admin.subscription.discount')} value={data.discount_id ? data.discount_id : ''} />
|
|
277
279
|
<InfoRow
|
|
278
280
|
label={t('admin.paymentCurrency.name')}
|
|
279
281
|
value={
|
|
@@ -314,9 +316,17 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
314
316
|
</InfoRowGroup>
|
|
315
317
|
</Box>
|
|
316
318
|
<Divider />
|
|
317
|
-
|
|
318
319
|
{/* Discount Information */}
|
|
319
|
-
{(data as any).discountStats &&
|
|
320
|
+
{(data as any).discountStats && (
|
|
321
|
+
<Box className="section">
|
|
322
|
+
<Typography variant="h3" className="section-header" sx={{ mb: 1.5 }}>
|
|
323
|
+
{t('admin.subscription.discount')}
|
|
324
|
+
</Typography>
|
|
325
|
+
<Box className="section-body">
|
|
326
|
+
<DiscountInfo discountStats={(data as any).discountStats} />
|
|
327
|
+
</Box>
|
|
328
|
+
</Box>
|
|
329
|
+
)}
|
|
320
330
|
|
|
321
331
|
<Box className="section">
|
|
322
332
|
<SectionHeader title={t('admin.product.pricing')} />
|
|
@@ -324,6 +334,35 @@ export default function SubscriptionDetail(props: { id: string }) {
|
|
|
324
334
|
<SubscriptionItemList data={data.items} currency={data.paymentCurrency} mode="admin" />
|
|
325
335
|
</Box>
|
|
326
336
|
</Box>
|
|
337
|
+
{(() => {
|
|
338
|
+
const vendorServices = data.items?.map((item) => item.price?.product?.vendor_config || []).flat();
|
|
339
|
+
if (!vendorServices || vendorServices.length === 0) return null;
|
|
340
|
+
return (
|
|
341
|
+
<>
|
|
342
|
+
<Divider />
|
|
343
|
+
<Box
|
|
344
|
+
className="section"
|
|
345
|
+
sx={{
|
|
346
|
+
'.section-header': {
|
|
347
|
+
fontSize: {
|
|
348
|
+
xs: '18px',
|
|
349
|
+
md: '1.09375rem',
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
'.vendor-service-item': {
|
|
353
|
+
maxWidth: '400px',
|
|
354
|
+
},
|
|
355
|
+
}}>
|
|
356
|
+
<VendorServiceList
|
|
357
|
+
vendorServices={vendorServices}
|
|
358
|
+
subscriptionId={data.id}
|
|
359
|
+
isOwner={session?.user?.did === data.customer?.did}
|
|
360
|
+
isCanceled={data.status === 'canceled'}
|
|
361
|
+
/>
|
|
362
|
+
</Box>
|
|
363
|
+
</>
|
|
364
|
+
);
|
|
365
|
+
})()}
|
|
327
366
|
<Divider />
|
|
328
367
|
{isCredit ? (
|
|
329
368
|
<Box className="section">
|
|
@@ -11,7 +11,6 @@ import {
|
|
|
11
11
|
} from '@blocklet/payment-react';
|
|
12
12
|
import type { TProductExpanded } from '@blocklet/payment-types';
|
|
13
13
|
import { Avatar, Stack, Typography, Box } from '@mui/material';
|
|
14
|
-
import { styled } from '@mui/system';
|
|
15
14
|
import { Link } from 'react-router-dom';
|
|
16
15
|
|
|
17
16
|
interface Props {
|
|
@@ -126,41 +125,25 @@ export default function ApplicableProductsList({ products }: Props) {
|
|
|
126
125
|
].filter(Boolean);
|
|
127
126
|
|
|
128
127
|
return (
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
/>
|
|
150
|
-
</ApplicableProductsTableRoot>
|
|
128
|
+
<Table
|
|
129
|
+
data={products}
|
|
130
|
+
columns={columns}
|
|
131
|
+
loading={false}
|
|
132
|
+
footer={false}
|
|
133
|
+
toolbar={false}
|
|
134
|
+
components={{
|
|
135
|
+
TableToolbar: () => null,
|
|
136
|
+
TableFooter: () => null,
|
|
137
|
+
}}
|
|
138
|
+
mobileTDFlexDirection="row"
|
|
139
|
+
options={{
|
|
140
|
+
count: products.length,
|
|
141
|
+
page: 0,
|
|
142
|
+
rowsPerPage: 100,
|
|
143
|
+
selectableRows: 'none',
|
|
144
|
+
pagination: false,
|
|
145
|
+
}}
|
|
146
|
+
emptyNodeText={t('admin.coupon.noApplicableProducts')}
|
|
147
|
+
/>
|
|
151
148
|
);
|
|
152
149
|
}
|
|
153
|
-
|
|
154
|
-
const ApplicableProductsTableRoot = styled(Box)`
|
|
155
|
-
@media (max-width: ${({ theme }) => theme.breakpoints.values.md}px) {
|
|
156
|
-
.MuiTable-root > .MuiTableBody-root > .MuiTableRow-root > td.MuiTableCell-root {
|
|
157
|
-
align-items: center;
|
|
158
|
-
padding: 4px 0;
|
|
159
|
-
> div {
|
|
160
|
-
width: fit-content;
|
|
161
|
-
flex: inherit;
|
|
162
|
-
font-size: 14px;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
`;
|
|
@@ -389,7 +389,6 @@ export default function ProductDetail(props: { id: string }) {
|
|
|
389
389
|
</Box>
|
|
390
390
|
</Box>
|
|
391
391
|
<Divider />
|
|
392
|
-
{/* 供应商配置展示 */}
|
|
393
392
|
{data.type === 'service' && (
|
|
394
393
|
<>
|
|
395
394
|
<Box className="section">
|
|
@@ -429,13 +428,9 @@ export default function ProductDetail(props: { id: string }) {
|
|
|
429
428
|
borderColor: 'divider',
|
|
430
429
|
borderRadius: 1,
|
|
431
430
|
backgroundColor: 'background.paper',
|
|
431
|
+
maxWidth: '600px',
|
|
432
432
|
}}>
|
|
433
|
-
<Stack
|
|
434
|
-
direction="row"
|
|
435
|
-
sx={{
|
|
436
|
-
justifyContent: 'space-between',
|
|
437
|
-
alignItems: 'center',
|
|
438
|
-
}}>
|
|
433
|
+
<Stack direction="row" sx={{ justifyContent: 'space-between', alignItems: 'center' }}>
|
|
439
434
|
<Box>
|
|
440
435
|
<Typography variant="body1" sx={{ color: 'text.primary', fontWeight: 500 }}>
|
|
441
436
|
{vendor.name || vendor.vendor_key || vendor.vendor_id}
|
|
@@ -446,15 +441,10 @@ export default function ProductDetail(props: { id: string }) {
|
|
|
446
441
|
</Typography>
|
|
447
442
|
)}
|
|
448
443
|
</Box>
|
|
449
|
-
<Stack
|
|
450
|
-
direction="row"
|
|
451
|
-
spacing={3}
|
|
452
|
-
sx={{
|
|
453
|
-
alignItems: 'center',
|
|
454
|
-
}}>
|
|
444
|
+
<Stack direction="row" spacing={3} sx={{ alignItems: 'center' }}>
|
|
455
445
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
456
446
|
{vendor.commission_type === 'percentage'
|
|
457
|
-
? t('admin.vendor.
|
|
447
|
+
? t('admin.vendor.commission')
|
|
458
448
|
: t('admin.vendor.fixedAmount')}
|
|
459
449
|
</Typography>
|
|
460
450
|
<Typography variant="body1" sx={{ color: 'text.primary', fontWeight: 600 }}>
|
|
@@ -185,69 +185,78 @@ export default function VendorsList() {
|
|
|
185
185
|
setDetailOpen(true);
|
|
186
186
|
};
|
|
187
187
|
|
|
188
|
-
const
|
|
188
|
+
const handleCopyValue = async (value: string) => {
|
|
189
189
|
try {
|
|
190
|
-
await navigator.clipboard.writeText(
|
|
190
|
+
await navigator.clipboard.writeText(value);
|
|
191
191
|
setCopySuccess(true);
|
|
192
192
|
setTimeout(() => setCopySuccess(false), 2000);
|
|
193
193
|
} catch (err) {
|
|
194
|
-
console.error('Failed to copy
|
|
194
|
+
console.error('Failed to copy value:', err);
|
|
195
195
|
}
|
|
196
196
|
};
|
|
197
197
|
|
|
198
|
+
const brokerInfo = [
|
|
199
|
+
...(window.blocklet.appId ? [{ label: t('admin.vendor.brokerDID'), value: window.blocklet.appId }] : []),
|
|
200
|
+
...(window.blocklet.appPk ? [{ label: t('admin.vendor.brokerPublicKey'), value: window.blocklet.appPk }] : []),
|
|
201
|
+
];
|
|
202
|
+
|
|
198
203
|
return (
|
|
199
204
|
<>
|
|
200
|
-
{/*
|
|
201
|
-
{
|
|
205
|
+
{/* Broker Information */}
|
|
206
|
+
{brokerInfo.length > 0 && (
|
|
202
207
|
<Box
|
|
203
208
|
sx={{
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
gap: { xs: 1, md: 0 },
|
|
208
|
-
p: { xs: 1.5, md: 2 },
|
|
209
|
-
mt: { xs: 1.5, md: 1 },
|
|
209
|
+
px: 2,
|
|
210
|
+
py: 1.5,
|
|
211
|
+
my: 2,
|
|
210
212
|
borderRadius: 1,
|
|
211
213
|
border: '1px solid',
|
|
212
214
|
borderColor: 'divider',
|
|
215
|
+
backgroundColor: 'transparent',
|
|
216
|
+
display: 'grid',
|
|
217
|
+
gridTemplateColumns: 'max-content 1fr',
|
|
218
|
+
gap: 1,
|
|
219
|
+
alignItems: 'center',
|
|
213
220
|
}}>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
221
|
+
{brokerInfo.map((info) => (
|
|
222
|
+
<>
|
|
223
|
+
<Typography
|
|
224
|
+
key={`${info.label}-label`}
|
|
225
|
+
variant="body2"
|
|
226
|
+
color="text.secondary"
|
|
227
|
+
sx={{ justifySelf: 'start' }}>
|
|
228
|
+
{info.label}:
|
|
229
|
+
</Typography>
|
|
230
|
+
<Box sx={{ display: 'flex', alignItems: 'center', overflow: 'hidden' }}>
|
|
231
|
+
<Chip
|
|
232
|
+
key={`${info.label}-chip`}
|
|
233
|
+
label={info.value}
|
|
234
|
+
size="small"
|
|
235
|
+
sx={{
|
|
236
|
+
flexShrink: 1,
|
|
237
|
+
overflow: 'hidden',
|
|
238
|
+
'& .MuiChip-label': {
|
|
239
|
+
overflow: 'hidden',
|
|
240
|
+
textOverflow: 'ellipsis',
|
|
241
|
+
},
|
|
242
|
+
}}
|
|
243
|
+
/>
|
|
244
|
+
<Tooltip key={`${info.label}-tooltip`} title={copySuccess ? t('common.copied') : t('common.copy')}>
|
|
245
|
+
<IconButton
|
|
246
|
+
size="small"
|
|
247
|
+
onClick={() => handleCopyValue(info.value)}
|
|
248
|
+
sx={{
|
|
249
|
+
color: copySuccess ? 'success.main' : 'text.secondary',
|
|
250
|
+
'&:hover': {
|
|
251
|
+
color: 'primary.main',
|
|
252
|
+
},
|
|
253
|
+
}}>
|
|
254
|
+
<ContentCopy sx={{ fontSize: 16 }} />
|
|
255
|
+
</IconButton>
|
|
256
|
+
</Tooltip>
|
|
257
|
+
</Box>
|
|
258
|
+
</>
|
|
259
|
+
))}
|
|
251
260
|
</Box>
|
|
252
261
|
)}
|
|
253
262
|
<Table
|
|
@@ -609,7 +609,6 @@ export default function CustomerSubscriptionDetail() {
|
|
|
609
609
|
)}
|
|
610
610
|
|
|
611
611
|
<InfoRow label={t('admin.subscription.collectionMethod')} value={data.collection_method} />
|
|
612
|
-
<InfoRow label={t('admin.subscription.discount')} value={data.discount_id ? data.discount_id : ''} />
|
|
613
612
|
|
|
614
613
|
<InfoRow
|
|
615
614
|
label={t('admin.paymentMethod._name')}
|
|
@@ -682,7 +681,17 @@ export default function CustomerSubscriptionDetail() {
|
|
|
682
681
|
<Divider />
|
|
683
682
|
|
|
684
683
|
{/* Discount Information */}
|
|
685
|
-
{(data as any).discountStats &&
|
|
684
|
+
{(data as any).discountStats && (
|
|
685
|
+
<Box className="section">
|
|
686
|
+
<Typography variant="h3" className="section-header" sx={{ mb: 1.5 }}>
|
|
687
|
+
{t('admin.subscription.discount')}
|
|
688
|
+
</Typography>
|
|
689
|
+
<Box className="section-body">
|
|
690
|
+
<DiscountInfo discountStats={(data as any).discountStats} />
|
|
691
|
+
</Box>
|
|
692
|
+
</Box>
|
|
693
|
+
)}
|
|
694
|
+
<Box className="divider" />
|
|
686
695
|
|
|
687
696
|
<Box className="section">
|
|
688
697
|
<Typography
|
|
@@ -706,12 +715,16 @@ export default function CustomerSubscriptionDetail() {
|
|
|
706
715
|
<>
|
|
707
716
|
<Divider />
|
|
708
717
|
<Box className="section">
|
|
709
|
-
<VendorServiceList
|
|
718
|
+
<VendorServiceList
|
|
719
|
+
vendorServices={vendorServices}
|
|
720
|
+
subscriptionId={id}
|
|
721
|
+
isCanceled={data.status === 'canceled'}
|
|
722
|
+
/>
|
|
710
723
|
</Box>
|
|
711
724
|
</>
|
|
712
725
|
);
|
|
713
726
|
})()}
|
|
714
|
-
<
|
|
727
|
+
<Box className="divider" />
|
|
715
728
|
{isCredit ? (
|
|
716
729
|
<Box className="section">
|
|
717
730
|
<Typography variant="h3" className="section-header">
|