payment-kit 1.18.12 → 1.18.14
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/index.ts +2 -0
- package/api/src/integrations/stripe/resource.ts +53 -11
- package/api/src/libs/auth.ts +14 -0
- package/api/src/libs/notification/template/one-time-payment-succeeded.ts +5 -3
- package/api/src/libs/notification/template/subscription-canceled.ts +3 -3
- package/api/src/libs/notification/template/subscription-refund-succeeded.ts +4 -3
- package/api/src/libs/notification/template/subscription-renew-failed.ts +5 -4
- package/api/src/libs/notification/template/subscription-renewed.ts +2 -1
- package/api/src/libs/notification/template/subscription-stake-slash-succeeded.ts +3 -4
- package/api/src/libs/notification/template/subscription-succeeded.ts +2 -1
- package/api/src/libs/notification/template/subscription-upgraded.ts +6 -4
- package/api/src/libs/notification/template/subscription-will-canceled.ts +6 -3
- package/api/src/libs/notification/template/subscription-will-renew.ts +1 -1
- package/api/src/libs/payment.ts +77 -2
- package/api/src/libs/util.ts +8 -0
- package/api/src/queues/payment.ts +50 -1
- package/api/src/queues/payout.ts +297 -0
- package/api/src/routes/checkout-sessions.ts +2 -7
- package/api/src/routes/customers.ts +79 -5
- package/api/src/routes/payment-currencies.ts +117 -1
- package/api/src/routes/payment-methods.ts +19 -9
- package/api/src/routes/subscriptions.ts +15 -9
- package/api/src/store/migrations/20250305-vault-config.ts +21 -0
- package/api/src/store/models/invoice.ts +4 -2
- package/api/src/store/models/payment-currency.ts +14 -0
- package/api/src/store/models/payout.ts +21 -0
- package/api/src/store/models/types.ts +6 -0
- package/blocklet.yml +2 -2
- package/package.json +18 -18
- package/src/app.tsx +117 -121
- package/src/components/actions.tsx +32 -9
- package/src/components/copyable.tsx +2 -2
- package/src/components/customer/overdraft-protection.tsx +1 -0
- package/src/components/layout/admin.tsx +6 -0
- package/src/components/layout/user.tsx +38 -0
- package/src/components/metadata/editor.tsx +7 -1
- package/src/components/metadata/list.tsx +3 -0
- package/src/components/passport/assign.tsx +3 -0
- package/src/components/payment-link/rename.tsx +1 -0
- package/src/components/pricing-table/rename.tsx +1 -0
- package/src/components/product/add-price.tsx +1 -0
- package/src/components/product/edit-price.tsx +1 -0
- package/src/components/product/edit.tsx +1 -0
- package/src/components/subscription/actions/index.tsx +1 -0
- package/src/components/subscription/portal/actions.tsx +27 -5
- package/src/components/subscription/portal/list.tsx +24 -6
- package/src/components/subscription/status.tsx +2 -2
- package/src/libs/util.ts +15 -0
- package/src/locales/en.tsx +42 -0
- package/src/locales/zh.tsx +37 -0
- package/src/pages/admin/payments/payouts/detail.tsx +47 -38
- package/src/pages/admin/settings/index.tsx +3 -3
- package/src/pages/admin/settings/payment-methods/index.tsx +33 -1
- package/src/pages/admin/settings/vault-config/edit-form.tsx +253 -0
- package/src/pages/admin/settings/vault-config/index.tsx +352 -0
- package/src/pages/customer/index.tsx +247 -154
- package/src/pages/customer/invoice/detail.tsx +1 -1
- package/src/pages/customer/payout/detail.tsx +9 -2
- package/src/pages/customer/recharge.tsx +6 -2
- package/src/pages/customer/subscription/change-payment.tsx +1 -1
- package/src/pages/customer/subscription/change-plan.tsx +1 -1
- package/src/pages/customer/subscription/detail.tsx +8 -3
- package/src/pages/customer/subscription/embed.tsx +142 -84
- package/src/pages/integrations/donations/edit-form.tsx +0 -1
|
@@ -39,6 +39,7 @@ export default function AssignPassportDialog(props: { id: string; onCancel: any
|
|
|
39
39
|
message={<Alert severity="error">{error.message}</Alert>}
|
|
40
40
|
onConfirm={onConfirm}
|
|
41
41
|
onCancel={props.onCancel}
|
|
42
|
+
color="primary"
|
|
42
43
|
/>
|
|
43
44
|
);
|
|
44
45
|
}
|
|
@@ -50,6 +51,7 @@ export default function AssignPassportDialog(props: { id: string; onCancel: any
|
|
|
50
51
|
message={<CircularProgress />}
|
|
51
52
|
onConfirm={onConfirm}
|
|
52
53
|
onCancel={props.onCancel}
|
|
54
|
+
color="primary"
|
|
53
55
|
/>
|
|
54
56
|
);
|
|
55
57
|
}
|
|
@@ -75,6 +77,7 @@ export default function AssignPassportDialog(props: { id: string; onCancel: any
|
|
|
75
77
|
}
|
|
76
78
|
onConfirm={onConfirm}
|
|
77
79
|
onCancel={props.onCancel}
|
|
80
|
+
color="primary"
|
|
78
81
|
/>
|
|
79
82
|
);
|
|
80
83
|
}
|
|
@@ -181,6 +181,7 @@ function SubscriptionActionsInner({ data, variant, onChange }: Props) {
|
|
|
181
181
|
title={t('admin.subscription.resume')}
|
|
182
182
|
message={t('admin.subscription.resumeTip')}
|
|
183
183
|
loading={state.loading}
|
|
184
|
+
color="primary"
|
|
184
185
|
/>
|
|
185
186
|
)}
|
|
186
187
|
{state.action === 'slashStake' && (
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getPrefix,
|
|
10
10
|
getSubscriptionAction,
|
|
11
11
|
usePaymentContext,
|
|
12
|
+
OverdueInvoicePayment,
|
|
12
13
|
} from '@blocklet/payment-react';
|
|
13
14
|
import type { TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
14
15
|
import { Button, Link, Stack, Tooltip } from '@mui/material';
|
|
@@ -22,6 +23,7 @@ import CustomerCancelForm from './cancel';
|
|
|
22
23
|
import OverdraftProtectionDialog from '../../customer/overdraft-protection';
|
|
23
24
|
import Actions from '../../actions';
|
|
24
25
|
import { useUnpaidInvoicesCheckForSubscription } from '../../../hooks/subscription';
|
|
26
|
+
import { isWillCanceled } from '../../../libs/util';
|
|
25
27
|
|
|
26
28
|
interface ActionConfig {
|
|
27
29
|
key: string;
|
|
@@ -154,6 +156,7 @@ export function SubscriptionActionsInner({
|
|
|
154
156
|
openProtection: false,
|
|
155
157
|
protectionLoading: false,
|
|
156
158
|
protectionInitValues: null,
|
|
159
|
+
batchPay: false,
|
|
157
160
|
});
|
|
158
161
|
|
|
159
162
|
const shouldFetchDelegation = showDelegation && ['active', 'trialing', 'past_due'].includes(subscription?.status);
|
|
@@ -320,7 +323,7 @@ export function SubscriptionActionsInner({
|
|
|
320
323
|
const renderActions = () => {
|
|
321
324
|
const supportUnsubscribe = action?.action === 'cancel' && showUnsubscribe;
|
|
322
325
|
const supportAction = action && (action?.action !== 'cancel' || supportUnsubscribe);
|
|
323
|
-
|
|
326
|
+
const supportResume = isWillCanceled(subscription) && action?.action === 'recover';
|
|
324
327
|
const serviceActions = subscription.service_actions?.filter((x: any) => x?.type !== 'notification') || [];
|
|
325
328
|
const actionConfigs: ActionConfig[] = [
|
|
326
329
|
{
|
|
@@ -351,7 +354,7 @@ export function SubscriptionActionsInner({
|
|
|
351
354
|
},
|
|
352
355
|
variant: 'outlined',
|
|
353
356
|
color: 'primary',
|
|
354
|
-
primary:
|
|
357
|
+
primary: !isWillCanceled(subscription),
|
|
355
358
|
},
|
|
356
359
|
{
|
|
357
360
|
key: 'changePlan',
|
|
@@ -371,7 +374,9 @@ export function SubscriptionActionsInner({
|
|
|
371
374
|
label: action?.text || t('admin.subscription.batchPay.button'),
|
|
372
375
|
onClick: (e) => {
|
|
373
376
|
e?.stopPropagation();
|
|
374
|
-
|
|
377
|
+
setState({
|
|
378
|
+
batchPay: true,
|
|
379
|
+
});
|
|
375
380
|
},
|
|
376
381
|
variant: 'outlined',
|
|
377
382
|
color: 'error',
|
|
@@ -397,6 +402,7 @@ export function SubscriptionActionsInner({
|
|
|
397
402
|
color: action?.color || 'primary',
|
|
398
403
|
sx: action?.sx,
|
|
399
404
|
divider: serviceActions.length > 0,
|
|
405
|
+
primary: supportResume,
|
|
400
406
|
},
|
|
401
407
|
// @ts-ignore
|
|
402
408
|
...serviceActions.map((x) => ({
|
|
@@ -445,7 +451,7 @@ export function SubscriptionActionsInner({
|
|
|
445
451
|
</Button>
|
|
446
452
|
);
|
|
447
453
|
if (mode === 'menu-only') {
|
|
448
|
-
return <Actions actions={visibleActions.map(toMenuItem)} />;
|
|
454
|
+
return <Actions actions={visibleActions.map(toMenuItem)} variant="outlined" />;
|
|
449
455
|
}
|
|
450
456
|
|
|
451
457
|
if (mode === 'primary-buttons') {
|
|
@@ -454,7 +460,7 @@ export function SubscriptionActionsInner({
|
|
|
454
460
|
return (
|
|
455
461
|
<>
|
|
456
462
|
{primaryButtons.map(toButton)}
|
|
457
|
-
{menuItems.length > 0 && <Actions actions={menuItems.map(toMenuItem)} />}
|
|
463
|
+
{menuItems.length > 0 && <Actions actions={menuItems.map(toMenuItem)} variant="outlined" />}
|
|
458
464
|
</>
|
|
459
465
|
);
|
|
460
466
|
}
|
|
@@ -489,6 +495,7 @@ export function SubscriptionActionsInner({
|
|
|
489
495
|
date: formatToDate(subscription.current_period_end * 1000),
|
|
490
496
|
})}
|
|
491
497
|
loading={state.loading}
|
|
498
|
+
color="primary"
|
|
492
499
|
/>
|
|
493
500
|
)}
|
|
494
501
|
|
|
@@ -506,6 +513,21 @@ export function SubscriptionActionsInner({
|
|
|
506
513
|
initValues={state.protectionInitValues}
|
|
507
514
|
/>
|
|
508
515
|
)}
|
|
516
|
+
|
|
517
|
+
{state.batchPay && (
|
|
518
|
+
<OverdueInvoicePayment
|
|
519
|
+
subscriptionId={subscription.id}
|
|
520
|
+
onPaid={() => {
|
|
521
|
+
setState({ batchPay: false });
|
|
522
|
+
onChange?.('batch-pay');
|
|
523
|
+
}}
|
|
524
|
+
inSubscriptionDetail
|
|
525
|
+
dialogProps={{
|
|
526
|
+
open: state.batchPay,
|
|
527
|
+
onClose: () => setState({ batchPay: false }),
|
|
528
|
+
}}
|
|
529
|
+
/>
|
|
530
|
+
)}
|
|
509
531
|
</Stack>
|
|
510
532
|
);
|
|
511
533
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
|
|
3
3
|
import Empty from '@arcblock/ux/lib/Empty';
|
|
4
4
|
import { api, formatPrice, getSubscriptionTimeSummary, useMobile } from '@blocklet/payment-react';
|
|
5
|
-
import type {
|
|
5
|
+
import type { TSubscriptionExpanded } from '@blocklet/payment-types';
|
|
6
6
|
import { Avatar, AvatarGroup, Box, Button, CircularProgress, Stack, StackProps, Typography } from '@mui/material';
|
|
7
7
|
import { useInfiniteScroll } from 'ahooks';
|
|
8
8
|
|
|
@@ -11,8 +11,16 @@ import SubscriptionDescription from '../description';
|
|
|
11
11
|
import SubscriptionActions from './actions';
|
|
12
12
|
import SubscriptionStatus from '../status';
|
|
13
13
|
import useDelayedLoading from '../../../hooks/loading';
|
|
14
|
+
import { isWillCanceled } from '../../../libs/util';
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
type SubscriptionListResponse = {
|
|
17
|
+
count: number;
|
|
18
|
+
list: TSubscriptionExpanded[];
|
|
19
|
+
paging: { page: number; pageSize: number };
|
|
20
|
+
totalCount: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const fetchData = (params: Record<string, any> = {}): Promise<SubscriptionListResponse> => {
|
|
16
24
|
const search = new URLSearchParams();
|
|
17
25
|
Object.keys(params).forEach((key) => {
|
|
18
26
|
search.set(key, String(params[key]));
|
|
@@ -27,6 +35,7 @@ type Props = {
|
|
|
27
35
|
onClickSubscription: (subscription: TSubscriptionExpanded) => void | Promise<void>;
|
|
28
36
|
onlyActive?: boolean;
|
|
29
37
|
changeActive?: (active: boolean) => void;
|
|
38
|
+
setStatusState?: (state: boolean) => void;
|
|
30
39
|
} & Omit<StackProps, 'onChange'>;
|
|
31
40
|
|
|
32
41
|
const pageSize = 5;
|
|
@@ -38,19 +47,25 @@ export default function CurrentSubscriptions({
|
|
|
38
47
|
onClickSubscription,
|
|
39
48
|
onlyActive,
|
|
40
49
|
changeActive = () => {},
|
|
50
|
+
setStatusState = () => {},
|
|
41
51
|
...rest
|
|
42
52
|
}: Props) {
|
|
43
53
|
const { t } = useLocaleContext();
|
|
44
54
|
const { isMobile } = useMobile();
|
|
45
55
|
const listRef = useRef<HTMLDivElement | null>(null);
|
|
46
56
|
|
|
47
|
-
const { data, loadMore, loadingMore, loading, reload } = useInfiniteScroll<
|
|
57
|
+
const { data, loadMore, loadingMore, loading, reload } = useInfiniteScroll<SubscriptionListResponse>(
|
|
48
58
|
(d) => {
|
|
49
59
|
const page = d ? Math.ceil(d.list.length / pageSize) + 1 : 1;
|
|
50
|
-
return fetchData({ page, pageSize, status, customer_id: id, activeFirst: true });
|
|
60
|
+
return fetchData({ page, pageSize, status, customer_id: id, activeFirst: true, showTotalCount: true });
|
|
51
61
|
},
|
|
52
62
|
{
|
|
53
63
|
reloadDeps: [id, status],
|
|
64
|
+
onSuccess(res) {
|
|
65
|
+
if (res.totalCount > 0 || res.count > 0) {
|
|
66
|
+
setStatusState(true);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
54
69
|
...(isMobile
|
|
55
70
|
? {}
|
|
56
71
|
: {
|
|
@@ -68,6 +83,8 @@ export default function CurrentSubscriptions({
|
|
|
68
83
|
return <CircularProgress />;
|
|
69
84
|
}
|
|
70
85
|
|
|
86
|
+
const hasAnySubscriptions = data.totalCount > 0;
|
|
87
|
+
|
|
71
88
|
const hasMore = data && data.list?.length < data.count;
|
|
72
89
|
const size = { width: 48, height: 48 };
|
|
73
90
|
|
|
@@ -193,7 +210,7 @@ export default function CurrentSubscriptions({
|
|
|
193
210
|
}
|
|
194
211
|
}}
|
|
195
212
|
showUnsubscribe={false}
|
|
196
|
-
showRecharge
|
|
213
|
+
showRecharge={!isWillCanceled(subscription)}
|
|
197
214
|
actionProps={{
|
|
198
215
|
cancel: {
|
|
199
216
|
variant: 'outlined',
|
|
@@ -239,7 +256,7 @@ export default function CurrentSubscriptions({
|
|
|
239
256
|
</>
|
|
240
257
|
) : (
|
|
241
258
|
<Empty>
|
|
242
|
-
{onlyActive ? (
|
|
259
|
+
{onlyActive && hasAnySubscriptions ? (
|
|
243
260
|
<Box sx={{ textAlign: 'center' }}>
|
|
244
261
|
<Typography>{t('admin.subscription.noActiveEmpty')}</Typography>
|
|
245
262
|
{changeActive && (
|
|
@@ -261,4 +278,5 @@ CurrentSubscriptions.defaultProps = {
|
|
|
261
278
|
onChange: null,
|
|
262
279
|
onlyActive: false,
|
|
263
280
|
changeActive: null,
|
|
281
|
+
setStatusState: null,
|
|
264
282
|
};
|
|
@@ -17,7 +17,7 @@ export default function SubscriptionStatus({
|
|
|
17
17
|
<Status
|
|
18
18
|
icon={<AccessTimeOutlined />}
|
|
19
19
|
label={t('admin.subscription.cancel.will', { date: formatToDate(subscription.current_period_end * 1000) })}
|
|
20
|
-
color="
|
|
20
|
+
color="warning"
|
|
21
21
|
{...rest}
|
|
22
22
|
/>
|
|
23
23
|
);
|
|
@@ -28,7 +28,7 @@ export default function SubscriptionStatus({
|
|
|
28
28
|
<Status
|
|
29
29
|
icon={<AccessTimeOutlined />}
|
|
30
30
|
label={t('admin.subscription.cancel.will', { date: formatToDate(subscription.cancel_at * 1000) })}
|
|
31
|
-
color="
|
|
31
|
+
color="warning"
|
|
32
32
|
{...rest}
|
|
33
33
|
/>
|
|
34
34
|
);
|
package/src/libs/util.ts
CHANGED
|
@@ -377,3 +377,18 @@ export function getTokenBalanceLink(method: TPaymentMethod, address: string) {
|
|
|
377
377
|
}
|
|
378
378
|
return '';
|
|
379
379
|
}
|
|
380
|
+
|
|
381
|
+
export function isWillCanceled(subscription: TSubscriptionExpanded) {
|
|
382
|
+
const now = Date.now() / 1000;
|
|
383
|
+
if (
|
|
384
|
+
['active', 'trialing'].includes(subscription.status) &&
|
|
385
|
+
subscription.cancel_at_period_end &&
|
|
386
|
+
subscription.current_period_end > now
|
|
387
|
+
) {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
if (subscription.cancel_at && subscription.cancel_at > now) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
return false;
|
|
394
|
+
}
|
package/src/locales/en.tsx
CHANGED
|
@@ -713,6 +713,48 @@ export default flat({
|
|
|
713
713
|
note: 'Note: mountLocation must be unique, used to identify the donation instance. After configuration, the instance will appear in the list, and you can make further settings.',
|
|
714
714
|
},
|
|
715
715
|
},
|
|
716
|
+
vaultConfig: {
|
|
717
|
+
title: 'Vault',
|
|
718
|
+
learnMore: 'Learn more about vault configuration',
|
|
719
|
+
goToConfig: 'Go to system configuration',
|
|
720
|
+
description:
|
|
721
|
+
'By enabling the vault wallet, you create a secure offline storage solution that automatically transfers excess funds when your hot wallet balance exceeds the threshold. This separation significantly enhances security by keeping the majority of your assets safely offline, protected from potential online threats.',
|
|
722
|
+
notConfigured: 'Vault wallet not configured',
|
|
723
|
+
configureFirst:
|
|
724
|
+
'Please #go to the dashboard# to configure the vault wallet address before setting up individual currencies.',
|
|
725
|
+
ownerOnly: 'Only administrators with owner permissions can modify vault wallet settings.',
|
|
726
|
+
permissionRequired: 'Owner permissions required',
|
|
727
|
+
enabled: 'Status',
|
|
728
|
+
enabledYes: 'Enabled',
|
|
729
|
+
enabledNo: 'Disabled',
|
|
730
|
+
depositThreshold: 'Deposit Threshold',
|
|
731
|
+
withdrawThreshold: 'Withdrawal Threshold',
|
|
732
|
+
edit: 'Configure',
|
|
733
|
+
enable: 'Enable',
|
|
734
|
+
editTitle: 'Configure {currency} Vault Settings',
|
|
735
|
+
enableTitle: 'Enable Vault Wallet for {currency}',
|
|
736
|
+
enableVault: 'Enable Vault Wallet',
|
|
737
|
+
enableVaultHelp:
|
|
738
|
+
'When enabled, excess funds automatically transfer to the vault wallet, and withdrawals exceeding the threshold require admin approval for security and risk control.',
|
|
739
|
+
depositThresholdHelp:
|
|
740
|
+
'When the hot wallet balance exceeds this amount, the excess funds will be automatically transferred to the vault wallet.',
|
|
741
|
+
withdrawThresholdHelp:
|
|
742
|
+
'For withdrawals exceeding this amount, approval from the vault wallet administrator is required.',
|
|
743
|
+
notConfig: 'Not configured',
|
|
744
|
+
noLimit: 'No limit',
|
|
745
|
+
withdrawThresholdNoLimit: '0 means no withdrawal limit',
|
|
746
|
+
depositThresholdRequired: 'Deposit threshold must be greater than 0',
|
|
747
|
+
withdrawThresholdInvalid: 'Withdrawal threshold must be greater or equal to 0',
|
|
748
|
+
enableSuccess: 'Successfully enabled vault wallet for {currency}',
|
|
749
|
+
disableSuccess: 'Successfully disabled vault wallet for {currency}',
|
|
750
|
+
updateSuccess: 'Successfully updated vault wallet settings for {currency}',
|
|
751
|
+
depositConfirmTitle: 'Deposit to Vault',
|
|
752
|
+
depositConfirmMessage:
|
|
753
|
+
'{currency} balance has exceeded the threshold, do you want to deposit to vault immediately?',
|
|
754
|
+
depositQueued: 'Deposit to vault request queued, please check the result later',
|
|
755
|
+
depositFailed: 'Deposit to vault request failed',
|
|
756
|
+
appBalance: 'App Balance',
|
|
757
|
+
},
|
|
716
758
|
},
|
|
717
759
|
empty: {
|
|
718
760
|
image: 'No Image',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -696,6 +696,43 @@ export default flat({
|
|
|
696
696
|
note: '注意:mountLocation 必须是唯一的,用于标识打赏实例。配置完成后,该实例将出现在列表中,您可以进行进一步的设置。',
|
|
697
697
|
},
|
|
698
698
|
},
|
|
699
|
+
vaultConfig: {
|
|
700
|
+
title: '冷钱包配置',
|
|
701
|
+
description:
|
|
702
|
+
'启用冷钱包后,系统会在热钱包余额超过阈值时,自动将多余资金转移至安全的离线存储。这种隔离机制将大部分资产与在线环境分离,有效抵御网络攻击,显著提升资金安全性。',
|
|
703
|
+
learnMore: '了解更多',
|
|
704
|
+
goToConfig: '前往系统配置',
|
|
705
|
+
notConfigured: '冷钱包尚未配置',
|
|
706
|
+
configureFirst: '请先#前往仪表盘#配置冷钱包地址,然后再设置各币种参数。',
|
|
707
|
+
ownerOnly: '仅拥有所有者权限的管理员可修改冷钱包设置。',
|
|
708
|
+
permissionRequired: '需要所有者权限',
|
|
709
|
+
enabled: '状态',
|
|
710
|
+
enabledYes: '已启用',
|
|
711
|
+
enabledNo: '未启用',
|
|
712
|
+
depositThreshold: '存入阈值',
|
|
713
|
+
withdrawThreshold: '提取阈值',
|
|
714
|
+
edit: '配置',
|
|
715
|
+
enable: '启用',
|
|
716
|
+
editTitle: '配置 {currency} 冷钱包设置',
|
|
717
|
+
enableTitle: '为 {currency} 启用冷钱包',
|
|
718
|
+
enableVault: '启用冷钱包',
|
|
719
|
+
enableVaultHelp: '启用冷钱包后,超额资金将自动转入冷钱包,提款超出阈值需管理员审核,确保资产安全与风控。',
|
|
720
|
+
depositThresholdHelp: '当热钱包余额超过此金额时,多余资金将自动转入冷钱包。',
|
|
721
|
+
withdrawThresholdHelp: '当单笔提款超过此金额时,需由冷钱包管理员审核并批准。',
|
|
722
|
+
notConfig: '未配置',
|
|
723
|
+
noLimit: '无限制',
|
|
724
|
+
withdrawThresholdNoLimit: '0 表示无提款限制',
|
|
725
|
+
depositThresholdRequired: '存入阈值必须大于0',
|
|
726
|
+
withdrawThresholdInvalid: '提款阈值不能小于0',
|
|
727
|
+
enableSuccess: '{currency} 冷钱包已启用',
|
|
728
|
+
disableSuccess: '{currency} 冷钱包已关闭',
|
|
729
|
+
updateSuccess: '{currency} 冷钱包设置已更新',
|
|
730
|
+
depositConfirmTitle: '转入冷钱包',
|
|
731
|
+
depositConfirmMessage: '{currency}余额已超过阈值,是否立即转入冷钱包?',
|
|
732
|
+
depositQueued: '申请转入冷钱包成功,请稍后查看结果',
|
|
733
|
+
depositFailed: '申请转入冷钱包失败',
|
|
734
|
+
appBalance: '热钱包余额',
|
|
735
|
+
},
|
|
699
736
|
},
|
|
700
737
|
empty: {
|
|
701
738
|
image: '无图片',
|
|
@@ -194,43 +194,50 @@ export default function PayoutDetail(props: { id: string }) {
|
|
|
194
194
|
value={<Status label={data.status} color={getPayoutStatusColor(data.status)} />}
|
|
195
195
|
divider
|
|
196
196
|
/>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
197
|
+
{paymentIntent?.id && (
|
|
198
|
+
<InfoMetric
|
|
199
|
+
label={t('customer.payout.payer')}
|
|
200
|
+
value={
|
|
201
|
+
<InfoCard
|
|
202
|
+
logo={getCustomerAvatar(
|
|
203
|
+
paymentIntent?.customer?.did,
|
|
204
|
+
paymentIntent?.customer?.updated_at
|
|
205
|
+
? new Date(paymentIntent?.customer?.updated_at).toISOString()
|
|
206
|
+
: '',
|
|
207
|
+
48
|
|
208
|
+
)}
|
|
209
|
+
name={
|
|
210
|
+
<Typography
|
|
211
|
+
variant="subtitle2"
|
|
212
|
+
sx={{
|
|
213
|
+
cursor: 'pointer',
|
|
214
|
+
'&:hover': {
|
|
215
|
+
color: 'text.link',
|
|
216
|
+
},
|
|
217
|
+
}}
|
|
218
|
+
onClick={() => {
|
|
219
|
+
const url = getCustomerProfileUrl({
|
|
220
|
+
userDid: paymentIntent?.customer?.did,
|
|
221
|
+
locale: 'zh',
|
|
222
|
+
});
|
|
223
|
+
window.open(url, '_blank');
|
|
224
|
+
}}>
|
|
225
|
+
{paymentIntent?.customer?.name} ({paymentIntent?.customer?.email})
|
|
226
|
+
</Typography>
|
|
227
|
+
}
|
|
228
|
+
description={
|
|
229
|
+
<DID
|
|
230
|
+
did={paymentIntent?.customer?.did}
|
|
231
|
+
{...(isMobile ? { responsive: false, compact: true } : {})}
|
|
232
|
+
/>
|
|
233
|
+
}
|
|
234
|
+
size={40}
|
|
235
|
+
variant="rounded"
|
|
236
|
+
/>
|
|
237
|
+
}
|
|
238
|
+
divider
|
|
239
|
+
/>
|
|
240
|
+
)}
|
|
234
241
|
{/* <InfoMetric label={t('common.createdAt')} value={formatTime(data.created_at)} divider /> */}
|
|
235
242
|
{/* <InfoMetric label={t('common.updatedAt')} value={formatTime(data.updated_at)} divider /> */}
|
|
236
243
|
</Stack>
|
|
@@ -380,13 +387,15 @@ export default function PayoutDetail(props: { id: string }) {
|
|
|
380
387
|
<Box className="section">
|
|
381
388
|
<SectionHeader title={t('admin.connections')} />
|
|
382
389
|
<Stack>
|
|
383
|
-
{data.payment_intent_id
|
|
390
|
+
{data.payment_intent_id ? (
|
|
384
391
|
<InfoRow
|
|
385
392
|
label={t('admin.paymentIntent.name')}
|
|
386
393
|
value={<Link to={`/admin/payments/${data.paymentIntent.id}`}>{data.paymentIntent.id}</Link>}
|
|
387
394
|
direction={InfoDirection}
|
|
388
395
|
alignItems={InfoAlignItems}
|
|
389
396
|
/>
|
|
397
|
+
) : (
|
|
398
|
+
t('common.none')
|
|
390
399
|
)}
|
|
391
400
|
</Stack>
|
|
392
401
|
</Box>
|
|
@@ -9,8 +9,8 @@ import { useTransitionContext } from '../../../components/progress-bar';
|
|
|
9
9
|
const PaymentMethodCreate = React.lazy(() => import('./payment-methods/create'));
|
|
10
10
|
|
|
11
11
|
const pages = {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
'payment-methods': React.lazy(() => import('./payment-methods')),
|
|
13
|
+
'vault-config': React.lazy(() => import('./vault-config')),
|
|
14
14
|
// business: React.lazy(() => import('./business')),
|
|
15
15
|
};
|
|
16
16
|
|
|
@@ -30,7 +30,7 @@ export default function SettingsIndex() {
|
|
|
30
30
|
const TabComponent = pages[page] || pages.paymentMethods;
|
|
31
31
|
const tabs = [
|
|
32
32
|
{ label: t('admin.paymentMethods'), value: 'payment-methods' },
|
|
33
|
-
|
|
33
|
+
{ label: t('admin.vaultConfig.title'), value: 'vault-config' },
|
|
34
34
|
// { label: t('admin.business'), value: 'business' },
|
|
35
35
|
];
|
|
36
36
|
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
ListItem,
|
|
26
26
|
ListItemAvatar,
|
|
27
27
|
ListItemText,
|
|
28
|
+
Skeleton,
|
|
28
29
|
Stack,
|
|
29
30
|
TextField,
|
|
30
31
|
Tooltip,
|
|
@@ -296,6 +297,37 @@ function Balance({
|
|
|
296
297
|
);
|
|
297
298
|
}
|
|
298
299
|
|
|
300
|
+
function PaymentMethodSkeleton() {
|
|
301
|
+
return (
|
|
302
|
+
<>
|
|
303
|
+
{[1].map((group) => (
|
|
304
|
+
<Box key={group} mt={3}>
|
|
305
|
+
<Stack direction="row" alignItems="center" mb={1} flexWrap="wrap" gap={1}>
|
|
306
|
+
<Skeleton variant="text" width={120} height={32} />
|
|
307
|
+
</Stack>
|
|
308
|
+
<Box
|
|
309
|
+
sx={{
|
|
310
|
+
py: 1,
|
|
311
|
+
borderTop: '1px solid #eee',
|
|
312
|
+
borderBottom: '1px solid #eee',
|
|
313
|
+
mb: 1,
|
|
314
|
+
}}>
|
|
315
|
+
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
|
316
|
+
<Stack direction="row" spacing={2} alignItems="center" sx={{ flex: 1 }}>
|
|
317
|
+
<Skeleton variant="rectangular" width={40} height={40} />
|
|
318
|
+
<Box sx={{ flex: 1 }}>
|
|
319
|
+
<Skeleton variant="text" width="20%" height={24} />
|
|
320
|
+
<Skeleton variant="text" width="40%" height={20} />
|
|
321
|
+
</Box>
|
|
322
|
+
</Stack>
|
|
323
|
+
</Stack>
|
|
324
|
+
</Box>
|
|
325
|
+
</Box>
|
|
326
|
+
))}
|
|
327
|
+
</>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
299
331
|
export default function PaymentMethods() {
|
|
300
332
|
const { t } = useLocaleContext();
|
|
301
333
|
const [expandedId, setExpandedId] = useSessionStorageState('payment-method-expanded-id', {
|
|
@@ -362,7 +394,7 @@ export default function PaymentMethods() {
|
|
|
362
394
|
}
|
|
363
395
|
|
|
364
396
|
if (loading || !data || methods?.length === 0) {
|
|
365
|
-
return <
|
|
397
|
+
return <PaymentMethodSkeleton />;
|
|
366
398
|
}
|
|
367
399
|
|
|
368
400
|
const groups = groupByType(methods);
|