payment-kit 1.18.38 → 1.18.40
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/handlers/invoice.ts +22 -0
- package/api/src/integrations/stripe/handlers/payment-intent.ts +9 -1
- package/api/src/integrations/stripe/handlers/subscription.ts +137 -1
- package/api/src/libs/subscription.ts +107 -97
- package/api/src/queues/payment.ts +4 -0
- package/api/src/routes/checkout-sessions.ts +3 -1
- package/api/src/routes/subscriptions.ts +27 -18
- package/blocklet.yml +1 -1
- package/package.json +19 -19
- package/scripts/sdk.js +27 -1
- package/src/components/customer/link.tsx +6 -0
- package/src/components/info-card.tsx +2 -2
- package/src/components/info-metric.tsx +1 -1
- package/src/components/metadata/list.tsx +4 -2
- package/src/components/subscription/description.tsx +8 -6
- package/src/components/subscription/portal/actions.tsx +224 -45
- package/src/components/subscription/portal/list.tsx +153 -74
- package/src/components/subscription/status.tsx +17 -5
- package/src/locales/en.tsx +12 -7
- package/src/locales/zh.tsx +6 -2
- package/src/pages/admin/billing/invoices/detail.tsx +1 -5
- package/src/pages/admin/billing/subscriptions/detail.tsx +1 -5
- package/src/pages/admin/customers/customers/detail.tsx +1 -5
- package/src/pages/admin/developers/events/detail.tsx +1 -5
- package/src/pages/admin/developers/index.tsx +1 -1
- package/src/pages/admin/developers/webhooks/detail.tsx +1 -3
- package/src/pages/admin/overview.tsx +4 -4
- package/src/pages/admin/payments/intents/detail.tsx +5 -6
- package/src/pages/admin/payments/payouts/detail.tsx +1 -5
- package/src/pages/admin/payments/refunds/detail.tsx +1 -5
- package/src/pages/admin/products/links/detail.tsx +1 -5
- package/src/pages/admin/products/prices/detail.tsx +1 -5
- package/src/pages/admin/products/pricing-tables/detail.tsx +1 -5
- package/src/pages/admin/products/products/detail.tsx +1 -5
- package/src/pages/admin/settings/payment-methods/index.tsx +1 -1
- package/src/pages/customer/index.tsx +67 -138
- package/src/pages/customer/payout/detail.tsx +37 -49
- package/src/pages/customer/subscription/change-payment.tsx +2 -35
- package/src/pages/customer/subscription/detail.tsx +5 -6
- package/src/pages/integrations/donations/index.tsx +1 -1
- package/src/pages/integrations/overview.tsx +1 -1
package/scripts/sdk.js
CHANGED
|
@@ -196,6 +196,32 @@ const checkoutModule = {
|
|
|
196
196
|
console.log('createCheckoutWithSettingId', checkoutSession);
|
|
197
197
|
return { setting, checkoutSession };
|
|
198
198
|
},
|
|
199
|
+
|
|
200
|
+
async createCheckoutWithServiceAction() {
|
|
201
|
+
const checkoutSession = await payment.checkout.sessions.create({
|
|
202
|
+
mode: 'subscription',
|
|
203
|
+
line_items: [{ price_id: 'price_fQFIS12yi0JR3KePLmitjrhA', quantity: 1 }],
|
|
204
|
+
subscription_data: {
|
|
205
|
+
service_actions: [
|
|
206
|
+
{
|
|
207
|
+
text: {
|
|
208
|
+
zh: '全链接测试',
|
|
209
|
+
en: 'Full Link Test',
|
|
210
|
+
},
|
|
211
|
+
link: 'https://baidu.com',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
text: {
|
|
215
|
+
zh: '相对路径访问',
|
|
216
|
+
en: 'Relative Path Access',
|
|
217
|
+
},
|
|
218
|
+
link: '/.well-known/service/user/notifications',
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
console.log('createCheckoutWithServiceAction', checkoutSession);
|
|
224
|
+
},
|
|
199
225
|
};
|
|
200
226
|
|
|
201
227
|
const paymentModule = {
|
|
@@ -515,7 +541,6 @@ const subscriptionModule = {
|
|
|
515
541
|
},
|
|
516
542
|
};
|
|
517
543
|
|
|
518
|
-
// 测试模块注册
|
|
519
544
|
const testModules = {
|
|
520
545
|
checkout: checkoutModule,
|
|
521
546
|
payment: paymentModule,
|
|
@@ -523,6 +548,7 @@ const testModules = {
|
|
|
523
548
|
subscription: subscriptionModule,
|
|
524
549
|
};
|
|
525
550
|
|
|
551
|
+
// 测试入口
|
|
526
552
|
async function runTest() {
|
|
527
553
|
payment.environments.setTestMode(true);
|
|
528
554
|
await testModules.checkout.createBatchSubscriptionWithCustomField();
|
|
@@ -2,18 +2,21 @@ import type { TCustomer } from '@blocklet/payment-types';
|
|
|
2
2
|
import { Link } from 'react-router-dom';
|
|
3
3
|
import UserCard from '@arcblock/ux/lib/UserCard';
|
|
4
4
|
import { getCustomerAvatar } from '@blocklet/payment-react';
|
|
5
|
+
import { InfoType, UserCardProps } from '@arcblock/ux/lib/UserCard/types';
|
|
5
6
|
|
|
6
7
|
export default function CustomerLink({
|
|
7
8
|
customer,
|
|
8
9
|
linked,
|
|
9
10
|
linkTo,
|
|
10
11
|
size,
|
|
12
|
+
cardProps,
|
|
11
13
|
}: {
|
|
12
14
|
customer: TCustomer;
|
|
13
15
|
linked?: boolean;
|
|
14
16
|
linkTo?: string;
|
|
15
17
|
size?: 'default' | 'small';
|
|
16
18
|
tooltip?: boolean;
|
|
19
|
+
cardProps?: UserCardProps;
|
|
17
20
|
}) {
|
|
18
21
|
if (!customer) {
|
|
19
22
|
return null;
|
|
@@ -31,6 +34,7 @@ export default function CustomerLink({
|
|
|
31
34
|
avatarProps={{
|
|
32
35
|
size: size === 'small' ? 24 : 40,
|
|
33
36
|
}}
|
|
37
|
+
popupInfoType={InfoType.Minimal}
|
|
34
38
|
showDid={size !== 'small'}
|
|
35
39
|
{...(customer.metadata.anonymous === true
|
|
36
40
|
? {
|
|
@@ -45,6 +49,7 @@ export default function CustomerLink({
|
|
|
45
49
|
},
|
|
46
50
|
}
|
|
47
51
|
: {})}
|
|
52
|
+
{...cardProps}
|
|
48
53
|
/>
|
|
49
54
|
);
|
|
50
55
|
if (linked) {
|
|
@@ -59,4 +64,5 @@ CustomerLink.defaultProps = {
|
|
|
59
64
|
linkTo: '',
|
|
60
65
|
size: 'default',
|
|
61
66
|
tooltip: true,
|
|
67
|
+
cardProps: {},
|
|
62
68
|
};
|
|
@@ -41,11 +41,11 @@ export default function InfoCard(props: Props) {
|
|
|
41
41
|
wordBreak: getWordBreakStyle(props.name),
|
|
42
42
|
minWidth: 140,
|
|
43
43
|
}}>
|
|
44
|
-
<Typography variant="
|
|
44
|
+
<Typography variant="body2" color="text.primary" component="div">
|
|
45
45
|
{props.name}
|
|
46
46
|
</Typography>
|
|
47
47
|
{props.description && (
|
|
48
|
-
<Typography variant="
|
|
48
|
+
<Typography variant="subtitle2" color="text.secondary">
|
|
49
49
|
{props.description}
|
|
50
50
|
</Typography>
|
|
51
51
|
)}
|
|
@@ -16,7 +16,7 @@ export default function InfoMetric(props: Props) {
|
|
|
16
16
|
return (
|
|
17
17
|
<>
|
|
18
18
|
<Stack direction="column" alignItems="flex-start">
|
|
19
|
-
<Typography component="div" variant="
|
|
19
|
+
<Typography component="div" variant="subtitle2" mb={1} color="text.primary">
|
|
20
20
|
{props.label}
|
|
21
21
|
{!!props.tip && (
|
|
22
22
|
<Tooltip title={props.tip}>
|
|
@@ -18,10 +18,12 @@ export default function MetadataList({
|
|
|
18
18
|
if (isEmpty(data)) {
|
|
19
19
|
return (
|
|
20
20
|
<Box sx={{ textAlign: 'center' }}>
|
|
21
|
-
<Typography color="text.primary"
|
|
21
|
+
<Typography variant="subtitle1" color="text.primary">
|
|
22
22
|
{t('common.metadata.empty')}
|
|
23
23
|
</Typography>
|
|
24
|
-
<Typography color="text.lighter">
|
|
24
|
+
<Typography variant="body2" color="text.lighter">
|
|
25
|
+
{t('common.metadata.emptyTip')}
|
|
26
|
+
</Typography>
|
|
25
27
|
<Button sx={{ color: 'text.link' }} onClick={handleEditMetadata}>
|
|
26
28
|
{t('common.add')}
|
|
27
29
|
</Button>
|
|
@@ -5,17 +5,18 @@ import { Stack, Tooltip, Typography } from '@mui/material';
|
|
|
5
5
|
|
|
6
6
|
type Props = {
|
|
7
7
|
subscription: TSubscriptionExpanded;
|
|
8
|
-
variant?: 'body1' | 'h5' | 'h4' | 'h3' | 'h2' | 'h1';
|
|
8
|
+
variant?: 'subtitle1' | 'subtitle2' | 'body1' | 'body2' | 'h5' | 'h4' | 'h3' | 'h2' | 'h1';
|
|
9
9
|
hideSubscription?: boolean;
|
|
10
|
+
maxLength?: number;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
|
-
export default function SubscriptionDescription({ subscription, variant, hideSubscription }: Props) {
|
|
13
|
+
export default function SubscriptionDescription({ subscription, variant, hideSubscription, maxLength = 80 }: Props) {
|
|
13
14
|
const { isMobile } = useMobile();
|
|
14
15
|
if (subscription.description) {
|
|
15
16
|
return (
|
|
16
17
|
<Stack direction="row" alignItems="center" spacing={1}>
|
|
17
|
-
<Typography variant={variant}
|
|
18
|
-
<TruncatedText text={subscription.description} maxLength={
|
|
18
|
+
<Typography variant={variant} className="subscription-description">
|
|
19
|
+
<TruncatedText text={subscription.description} maxLength={maxLength} useWidth />
|
|
19
20
|
</Typography>
|
|
20
21
|
{!hideSubscription && !isMobile && (
|
|
21
22
|
<Tooltip title={formatSubscriptionProduct(subscription.items)}>
|
|
@@ -27,8 +28,8 @@ export default function SubscriptionDescription({ subscription, variant, hideSub
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
return (
|
|
30
|
-
<Typography variant={variant}
|
|
31
|
-
{formatSubscriptionProduct(subscription.items)}
|
|
31
|
+
<Typography variant={variant}>
|
|
32
|
+
<TruncatedText text={formatSubscriptionProduct(subscription.items)} maxLength={maxLength} useWidth />
|
|
32
33
|
</Typography>
|
|
33
34
|
);
|
|
34
35
|
}
|
|
@@ -36,4 +37,5 @@ export default function SubscriptionDescription({ subscription, variant, hideSub
|
|
|
36
37
|
SubscriptionDescription.defaultProps = {
|
|
37
38
|
variant: 'body1',
|
|
38
39
|
hideSubscription: false,
|
|
40
|
+
maxLength: 80,
|
|
39
41
|
};
|
|
@@ -10,15 +10,19 @@ import {
|
|
|
10
10
|
getSubscriptionAction,
|
|
11
11
|
usePaymentContext,
|
|
12
12
|
OverdueInvoicePayment,
|
|
13
|
+
formatBNStr,
|
|
13
14
|
} from '@blocklet/payment-react';
|
|
14
15
|
import type { TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
15
|
-
import { Button, Link, Stack, Tooltip } from '@mui/material';
|
|
16
|
+
import { Button, Link, Stack, Tooltip, Typography, Box, Alert } from '@mui/material';
|
|
16
17
|
import { useRequest, useSetState } from 'ahooks';
|
|
17
18
|
import isEmpty from 'lodash/isEmpty';
|
|
18
|
-
import { useEffect, useState } from 'react';
|
|
19
|
+
import { useEffect, useState, ReactNode } from 'react';
|
|
19
20
|
import { FormProvider, useForm, useFormContext } from 'react-hook-form';
|
|
20
21
|
import { useNavigate } from 'react-router-dom';
|
|
21
22
|
import { joinURL } from 'ufo';
|
|
23
|
+
import { BN } from '@ocap/util';
|
|
24
|
+
import DID from '@arcblock/ux/lib/DID';
|
|
25
|
+
import { AddOutlined } from '@mui/icons-material';
|
|
22
26
|
import CustomerCancelForm from './cancel';
|
|
23
27
|
import OverdraftProtectionDialog from '../../customer/overdraft-protection';
|
|
24
28
|
import Actions from '../../actions';
|
|
@@ -28,11 +32,12 @@ import { isWillCanceled } from '../../../libs/util';
|
|
|
28
32
|
interface ActionConfig {
|
|
29
33
|
key: string;
|
|
30
34
|
show: boolean;
|
|
31
|
-
label: string;
|
|
35
|
+
label: string | ReactNode | (() => ReactNode);
|
|
36
|
+
labelText?: string;
|
|
32
37
|
tooltip?: string;
|
|
33
38
|
onClick: (e?: React.MouseEvent) => void;
|
|
34
39
|
variant?: 'text' | 'outlined' | 'contained';
|
|
35
|
-
color?: 'inherit' | 'primary' | 'secondary' | 'error';
|
|
40
|
+
color?: 'inherit' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error';
|
|
36
41
|
sx?: any;
|
|
37
42
|
primary?: boolean;
|
|
38
43
|
component?: any;
|
|
@@ -43,8 +48,8 @@ interface ActionConfig {
|
|
|
43
48
|
|
|
44
49
|
type ActionProps = {
|
|
45
50
|
[key: string]: {
|
|
46
|
-
color?:
|
|
47
|
-
variant?:
|
|
51
|
+
color?: 'inherit' | 'primary' | 'secondary' | 'info' | 'success' | 'warning' | 'error';
|
|
52
|
+
variant?: 'text' | 'outlined' | 'contained';
|
|
48
53
|
sx?: {
|
|
49
54
|
[key: string]: any;
|
|
50
55
|
};
|
|
@@ -65,6 +70,7 @@ type Props = {
|
|
|
65
70
|
subscription: TSubscriptionExpanded;
|
|
66
71
|
showExtra?: boolean;
|
|
67
72
|
showRecharge?: boolean;
|
|
73
|
+
showBalanceInfo?: boolean;
|
|
68
74
|
showOverdraftProtection?:
|
|
69
75
|
| boolean
|
|
70
76
|
| {
|
|
@@ -77,11 +83,16 @@ type Props = {
|
|
|
77
83
|
actionProps?: ActionProps;
|
|
78
84
|
mode?: ActionDisplayMode;
|
|
79
85
|
setUp?: (methods: ActionMethods) => void;
|
|
86
|
+
includeActions?: string[] | null;
|
|
87
|
+
excludeActions?: string[] | null;
|
|
88
|
+
forceShowDetailAction?: boolean;
|
|
89
|
+
buttonSize?: 'small' | 'medium' | 'large';
|
|
80
90
|
};
|
|
81
91
|
|
|
82
92
|
SubscriptionActions.defaultProps = {
|
|
83
93
|
showExtra: false,
|
|
84
94
|
showRecharge: false,
|
|
95
|
+
showBalanceInfo: false,
|
|
85
96
|
showOverdraftProtection: false,
|
|
86
97
|
showDelegation: false,
|
|
87
98
|
onChange: null,
|
|
@@ -89,6 +100,10 @@ SubscriptionActions.defaultProps = {
|
|
|
89
100
|
mode: 'all-buttons',
|
|
90
101
|
setUp: null,
|
|
91
102
|
showUnsubscribe: true,
|
|
103
|
+
includeActions: null,
|
|
104
|
+
excludeActions: null,
|
|
105
|
+
forceShowDetailAction: false,
|
|
106
|
+
buttonSize: 'small',
|
|
92
107
|
};
|
|
93
108
|
const fetchExtraActions = async ({
|
|
94
109
|
id,
|
|
@@ -138,6 +153,11 @@ export function SubscriptionActionsInner({
|
|
|
138
153
|
actionProps,
|
|
139
154
|
mode,
|
|
140
155
|
setUp,
|
|
156
|
+
showBalanceInfo,
|
|
157
|
+
includeActions,
|
|
158
|
+
excludeActions,
|
|
159
|
+
forceShowDetailAction,
|
|
160
|
+
buttonSize,
|
|
141
161
|
}: Props) {
|
|
142
162
|
const { t, locale } = useLocaleContext();
|
|
143
163
|
const { reset, getValues } = useFormContext();
|
|
@@ -147,8 +167,35 @@ export function SubscriptionActionsInner({
|
|
|
147
167
|
const { checkUnpaidInvoices } = useUnpaidInvoicesCheckForSubscription(subscription.id, true);
|
|
148
168
|
const action = getSubscriptionAction(subs, actionProps ?? {});
|
|
149
169
|
|
|
170
|
+
const showAction = (actionName: string) => {
|
|
171
|
+
if (excludeActions && excludeActions.length > 0) {
|
|
172
|
+
return !excludeActions.includes(actionName);
|
|
173
|
+
}
|
|
174
|
+
if (includeActions && includeActions.length > 0) {
|
|
175
|
+
return includeActions.includes(actionName);
|
|
176
|
+
}
|
|
177
|
+
return true;
|
|
178
|
+
};
|
|
179
|
+
|
|
150
180
|
const { data: extraActions } = useRequest(() => fetchExtraActions({ id: subscription.id, showExtra: !!showExtra }));
|
|
151
181
|
|
|
182
|
+
const { data: upcoming = {} } = useRequest(
|
|
183
|
+
() => api.get(`/api/subscriptions/${subscription.id}/upcoming`).then((res) => res.data),
|
|
184
|
+
{
|
|
185
|
+
ready: showAction('recharge') && !!(showRecharge && supportRecharge(subscription)) && showBalanceInfo,
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const { data: payerValue = {} } = useRequest(
|
|
190
|
+
() => api.get(`/api/subscriptions/${subscription.id}/payer-token`).then((res) => res.data),
|
|
191
|
+
{
|
|
192
|
+
ready: showAction('recharge') && !!(showRecharge && supportRecharge(subscription)) && showBalanceInfo,
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const isInsufficientBalance =
|
|
197
|
+
upcoming?.amount && payerValue?.token && new BN(payerValue.token || '0').lt(new BN(upcoming.amount || '0'));
|
|
198
|
+
|
|
152
199
|
const [state, setState] = useSetState({
|
|
153
200
|
action: '',
|
|
154
201
|
subscription: '',
|
|
@@ -164,12 +211,13 @@ export function SubscriptionActionsInner({
|
|
|
164
211
|
((typeof showOverdraftProtection === 'boolean' && showOverdraftProtection) ||
|
|
165
212
|
(typeof showOverdraftProtection === 'object' && showOverdraftProtection.show)) &&
|
|
166
213
|
subscription?.paymentMethod?.type === 'arcblock' &&
|
|
167
|
-
['active', 'trialing', 'past_due'].includes(subscription?.status)
|
|
214
|
+
['active', 'trialing', 'past_due'].includes(subscription?.status) &&
|
|
215
|
+
showAction('protection');
|
|
168
216
|
|
|
169
217
|
const { data: delegation = { sufficient: true }, refresh: refreshDelegation } = useRequest(
|
|
170
218
|
() => api.get(`/api/subscriptions/${subscription.id}/delegation`).then((res) => res.data),
|
|
171
219
|
{
|
|
172
|
-
ready: shouldFetchDelegation,
|
|
220
|
+
ready: showAction('delegation') && shouldFetchDelegation,
|
|
173
221
|
refreshDeps: [subscription.id, shouldFetchDelegation],
|
|
174
222
|
}
|
|
175
223
|
);
|
|
@@ -336,7 +384,17 @@ export function SubscriptionActionsInner({
|
|
|
336
384
|
const supportUnsubscribe = action?.action === 'cancel' && showUnsubscribe;
|
|
337
385
|
const supportAction = action && (action?.action !== 'cancel' || supportUnsubscribe);
|
|
338
386
|
const supportResume = isWillCanceled(subscription) && action?.action === 'recover';
|
|
339
|
-
const serviceActions = subscription.service_actions?.filter((x: any) => x?.type !== 'notification') || [
|
|
387
|
+
const serviceActions = subscription.service_actions?.filter((x: any) => x?.type !== 'notification') || [
|
|
388
|
+
{
|
|
389
|
+
name: 'notification',
|
|
390
|
+
text: {
|
|
391
|
+
en: 'Application Details',
|
|
392
|
+
zh: '应用详情',
|
|
393
|
+
},
|
|
394
|
+
link: '/customer/notification',
|
|
395
|
+
color: 'primary',
|
|
396
|
+
},
|
|
397
|
+
];
|
|
340
398
|
const actionConfigs: ActionConfig[] = [
|
|
341
399
|
{
|
|
342
400
|
key: 'delegation',
|
|
@@ -344,8 +402,8 @@ export function SubscriptionActionsInner({
|
|
|
344
402
|
label: t('customer.delegation.btn'),
|
|
345
403
|
tooltip: t('customer.delegation.title'),
|
|
346
404
|
onClick: handleDelegate,
|
|
347
|
-
variant: 'outlined',
|
|
348
|
-
color: 'primary',
|
|
405
|
+
variant: actionProps?.delegation?.variant || 'outlined',
|
|
406
|
+
color: actionProps?.delegation?.color || 'primary',
|
|
349
407
|
primary: true,
|
|
350
408
|
},
|
|
351
409
|
{
|
|
@@ -353,20 +411,105 @@ export function SubscriptionActionsInner({
|
|
|
353
411
|
show: shouldFetchOverdraftProtection,
|
|
354
412
|
label: t('customer.overdraftProtection.setting'),
|
|
355
413
|
onClick: () => setState({ openProtection: true, protectionInitValues: null }),
|
|
356
|
-
variant: 'outlined',
|
|
357
|
-
color: 'primary',
|
|
414
|
+
variant: actionProps?.protection?.variant || 'outlined',
|
|
415
|
+
color: actionProps?.protection?.color || 'primary',
|
|
358
416
|
},
|
|
359
417
|
{
|
|
360
418
|
key: 'recharge',
|
|
361
419
|
show: !!(showRecharge && supportRecharge(subscription)),
|
|
362
|
-
label:
|
|
420
|
+
label: () => {
|
|
421
|
+
const balanceDisplay = (
|
|
422
|
+
<Stack direction="row" spacing={0.5} alignItems="center">
|
|
423
|
+
<AddOutlined fontSize="small" />
|
|
424
|
+
{t('customer.recharge.title')}
|
|
425
|
+
</Stack>
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
if (showBalanceInfo && subscription.paymentCurrency) {
|
|
429
|
+
const formattedBalance = formatBNStr(payerValue?.token || '0', subscription.paymentCurrency.decimal);
|
|
430
|
+
const formattedUpcoming = formatBNStr(upcoming?.amount || '0', subscription.paymentCurrency.decimal);
|
|
431
|
+
|
|
432
|
+
return (
|
|
433
|
+
<Tooltip
|
|
434
|
+
componentsProps={{
|
|
435
|
+
tooltip: {
|
|
436
|
+
sx: {
|
|
437
|
+
bgcolor: 'background.paper',
|
|
438
|
+
color: 'text.primary',
|
|
439
|
+
boxShadow: 2,
|
|
440
|
+
padding: '10px 16px',
|
|
441
|
+
maxWidth: 480,
|
|
442
|
+
minWidth: 350,
|
|
443
|
+
wordBreak: 'break-word',
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
}}
|
|
447
|
+
title={
|
|
448
|
+
<Box>
|
|
449
|
+
{isInsufficientBalance && (
|
|
450
|
+
<Alert severity="error" sx={{ py: 0, mb: 1 }}>
|
|
451
|
+
{t('admin.subscription.insufficientBalance')}
|
|
452
|
+
</Alert>
|
|
453
|
+
)}
|
|
454
|
+
<Stack spacing={0.5}>
|
|
455
|
+
<Box display="flex" justifyContent="space-between">
|
|
456
|
+
<Typography sx={{ color: 'text.secondary' }} variant="body2">
|
|
457
|
+
{t('admin.subscription.currentBalance')}
|
|
458
|
+
</Typography>
|
|
459
|
+
<Typography variant="body2">
|
|
460
|
+
{formattedBalance} {subscription.paymentCurrency.symbol}
|
|
461
|
+
</Typography>
|
|
462
|
+
</Box>
|
|
463
|
+
<Box display="flex" justifyContent="space-between">
|
|
464
|
+
<Typography sx={{ color: 'text.secondary' }} variant="body2">
|
|
465
|
+
{t('admin.subscription.nextInvoiceAmount')}
|
|
466
|
+
</Typography>
|
|
467
|
+
<Typography variant="body2">
|
|
468
|
+
{formattedUpcoming} {subscription.paymentCurrency.symbol}
|
|
469
|
+
</Typography>
|
|
470
|
+
</Box>
|
|
471
|
+
<Box display="flex" justifyContent="space-between">
|
|
472
|
+
<Typography sx={{ color: 'text.secondary' }} variant="body2">
|
|
473
|
+
{t('admin.subscription.paymentAddress')}
|
|
474
|
+
</Typography>
|
|
475
|
+
<DID did={payerValue?.paymentAddress} responsive={false} compact showAvatar={false} />
|
|
476
|
+
</Box>
|
|
477
|
+
</Stack>
|
|
478
|
+
</Box>
|
|
479
|
+
}
|
|
480
|
+
placement="top">
|
|
481
|
+
{balanceDisplay}
|
|
482
|
+
</Tooltip>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return balanceDisplay;
|
|
487
|
+
},
|
|
488
|
+
labelText: t('customer.recharge.title'),
|
|
363
489
|
onClick: (e) => {
|
|
364
490
|
e?.stopPropagation();
|
|
365
491
|
navigate(`/customer/subscription/${subscription.id}/recharge`);
|
|
366
492
|
},
|
|
367
|
-
variant: 'outlined',
|
|
368
|
-
color: 'primary',
|
|
493
|
+
variant: actionProps?.recharge?.variant || 'outlined',
|
|
494
|
+
color: actionProps?.recharge?.color || 'primary',
|
|
369
495
|
primary: !isWillCanceled(subscription),
|
|
496
|
+
sx:
|
|
497
|
+
isInsufficientBalance || payerValue.token === '0'
|
|
498
|
+
? {
|
|
499
|
+
'&::before': {
|
|
500
|
+
content: '""',
|
|
501
|
+
backgroundColor: 'error.main',
|
|
502
|
+
borderRadius: '50%',
|
|
503
|
+
position: 'absolute',
|
|
504
|
+
top: '3px',
|
|
505
|
+
right: '-3px',
|
|
506
|
+
width: '6px',
|
|
507
|
+
height: '6px',
|
|
508
|
+
zIndex: 1,
|
|
509
|
+
},
|
|
510
|
+
...(actionProps?.recharge?.sx || {}),
|
|
511
|
+
}
|
|
512
|
+
: actionProps?.recharge?.sx || {},
|
|
370
513
|
},
|
|
371
514
|
{
|
|
372
515
|
key: 'changePlan',
|
|
@@ -376,9 +519,9 @@ export function SubscriptionActionsInner({
|
|
|
376
519
|
e?.stopPropagation();
|
|
377
520
|
navigate(`/customer/subscription/${subscription.id}/change-plan`);
|
|
378
521
|
},
|
|
379
|
-
variant: 'contained',
|
|
380
|
-
color: 'primary',
|
|
381
|
-
sx:
|
|
522
|
+
variant: actionProps?.changePlan?.variant || 'contained',
|
|
523
|
+
color: actionProps?.changePlan?.color || 'primary',
|
|
524
|
+
sx: actionProps?.changePlan?.sx,
|
|
382
525
|
},
|
|
383
526
|
{
|
|
384
527
|
key: 'batchPay',
|
|
@@ -390,9 +533,9 @@ export function SubscriptionActionsInner({
|
|
|
390
533
|
batchPay: true,
|
|
391
534
|
});
|
|
392
535
|
},
|
|
393
|
-
variant: 'outlined',
|
|
394
|
-
color: 'error',
|
|
395
|
-
sx:
|
|
536
|
+
variant: actionProps?.batchPay?.variant || 'outlined',
|
|
537
|
+
color: actionProps?.batchPay?.color || 'error',
|
|
538
|
+
sx: actionProps?.batchPay?.sx,
|
|
396
539
|
primary: true,
|
|
397
540
|
},
|
|
398
541
|
{
|
|
@@ -432,38 +575,61 @@ export function SubscriptionActionsInner({
|
|
|
432
575
|
];
|
|
433
576
|
|
|
434
577
|
// 过滤出要显示的操作
|
|
435
|
-
const visibleActions = actionConfigs.filter((a) => a.show);
|
|
578
|
+
const visibleActions = actionConfigs.filter((a) => a.show && showAction(a.key));
|
|
436
579
|
|
|
437
580
|
// 转换为菜单项
|
|
438
581
|
const toMenuItem = (item: any) => ({
|
|
439
|
-
label: item.label,
|
|
582
|
+
label: item.labelText || (typeof item.label === 'function' ? item.label() : item.label),
|
|
440
583
|
handler: item.onClick,
|
|
441
584
|
color: item.color,
|
|
442
585
|
divider: item.divider,
|
|
443
586
|
});
|
|
444
|
-
|
|
445
|
-
const toButton = (item: ActionConfig) => (
|
|
587
|
+
const detailAction = forceShowDetailAction && (
|
|
446
588
|
<Button
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
sx={item.sx}
|
|
455
|
-
size="small">
|
|
456
|
-
{item.tooltip ? (
|
|
457
|
-
<Tooltip title={item.tooltip}>
|
|
458
|
-
<span>{item.label}</span>
|
|
459
|
-
</Tooltip>
|
|
460
|
-
) : (
|
|
461
|
-
item.label
|
|
462
|
-
)}
|
|
589
|
+
className="action-button"
|
|
590
|
+
key="detail"
|
|
591
|
+
sx={actionProps?.detail?.sx || {}}
|
|
592
|
+
variant={actionProps?.detail?.variant || 'outlined'}
|
|
593
|
+
color={actionProps?.detail?.color || 'primary'}
|
|
594
|
+
onClick={() => navigate(`/customer/subscription/${subscription.id}`)}>
|
|
595
|
+
{t('customer.subscription.manage')}
|
|
463
596
|
</Button>
|
|
464
597
|
);
|
|
598
|
+
|
|
599
|
+
const toButton = (item: ActionConfig) => {
|
|
600
|
+
const labelContent = typeof item.label === 'function' ? item.label() : item.label;
|
|
601
|
+
|
|
602
|
+
return (
|
|
603
|
+
<Button
|
|
604
|
+
key={item.key}
|
|
605
|
+
variant={item.variant}
|
|
606
|
+
color={item.color}
|
|
607
|
+
onClick={item.onClick}
|
|
608
|
+
component={item.component}
|
|
609
|
+
href={item.href}
|
|
610
|
+
target={item.target}
|
|
611
|
+
sx={item.sx}
|
|
612
|
+
className="action-button"
|
|
613
|
+
size={buttonSize}>
|
|
614
|
+
{item.tooltip ? (
|
|
615
|
+
<Tooltip title={item.tooltip}>
|
|
616
|
+
<span>{labelContent}</span>
|
|
617
|
+
</Tooltip>
|
|
618
|
+
) : (
|
|
619
|
+
labelContent
|
|
620
|
+
)}
|
|
621
|
+
</Button>
|
|
622
|
+
);
|
|
623
|
+
};
|
|
465
624
|
if (mode === 'menu-only') {
|
|
466
|
-
return
|
|
625
|
+
return (
|
|
626
|
+
<>
|
|
627
|
+
{detailAction}
|
|
628
|
+
{visibleActions.length > 0 && (
|
|
629
|
+
<Actions actions={visibleActions.map(toMenuItem)} variant="outlined" sx={actionProps?.menu?.sx || {}} />
|
|
630
|
+
)}
|
|
631
|
+
</>
|
|
632
|
+
);
|
|
467
633
|
}
|
|
468
634
|
|
|
469
635
|
if (mode === 'primary-buttons') {
|
|
@@ -471,13 +637,21 @@ export function SubscriptionActionsInner({
|
|
|
471
637
|
const menuItems = visibleActions.filter((a) => !a.primary);
|
|
472
638
|
return (
|
|
473
639
|
<>
|
|
640
|
+
{detailAction}
|
|
474
641
|
{primaryButtons.map(toButton)}
|
|
475
|
-
{menuItems.length > 0 &&
|
|
642
|
+
{menuItems.length > 0 && (
|
|
643
|
+
<Actions actions={menuItems.map(toMenuItem)} variant="outlined" sx={actionProps?.menu?.sx || {}} />
|
|
644
|
+
)}
|
|
476
645
|
</>
|
|
477
646
|
);
|
|
478
647
|
}
|
|
479
648
|
|
|
480
|
-
return
|
|
649
|
+
return (
|
|
650
|
+
<>
|
|
651
|
+
{detailAction}
|
|
652
|
+
{visibleActions.map(toButton)}
|
|
653
|
+
</>
|
|
654
|
+
);
|
|
481
655
|
};
|
|
482
656
|
|
|
483
657
|
return (
|
|
@@ -566,10 +740,15 @@ SubscriptionActionsInner.defaultProps = {
|
|
|
566
740
|
showExtra: false,
|
|
567
741
|
showRecharge: false,
|
|
568
742
|
showOverdraftProtection: false,
|
|
743
|
+
showBalanceInfo: false,
|
|
569
744
|
showDelegation: false,
|
|
570
745
|
showUnsubscribe: true,
|
|
571
746
|
onChange: null,
|
|
572
747
|
actionProps: {},
|
|
573
748
|
mode: 'all-buttons',
|
|
574
749
|
setUp: null,
|
|
750
|
+
includeActions: null,
|
|
751
|
+
excludeActions: null,
|
|
752
|
+
forceShowDetailAction: false,
|
|
753
|
+
buttonSize: 'small',
|
|
575
754
|
};
|