payment-kit 1.18.13 → 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.
Files changed (39) hide show
  1. package/api/src/index.ts +2 -0
  2. package/api/src/integrations/stripe/resource.ts +53 -11
  3. package/api/src/libs/auth.ts +14 -0
  4. package/api/src/libs/payment.ts +77 -2
  5. package/api/src/libs/util.ts +8 -0
  6. package/api/src/queues/payment.ts +50 -1
  7. package/api/src/queues/payout.ts +297 -0
  8. package/api/src/routes/checkout-sessions.ts +2 -7
  9. package/api/src/routes/payment-currencies.ts +117 -1
  10. package/api/src/routes/payment-methods.ts +19 -9
  11. package/api/src/routes/subscriptions.ts +2 -8
  12. package/api/src/store/migrations/20250305-vault-config.ts +21 -0
  13. package/api/src/store/models/payment-currency.ts +14 -0
  14. package/api/src/store/models/payout.ts +21 -0
  15. package/api/src/store/models/types.ts +6 -0
  16. package/blocklet.yml +1 -1
  17. package/package.json +18 -18
  18. package/src/app.tsx +116 -120
  19. package/src/components/customer/overdraft-protection.tsx +1 -0
  20. package/src/components/layout/admin.tsx +6 -0
  21. package/src/components/layout/user.tsx +1 -0
  22. package/src/components/metadata/editor.tsx +7 -1
  23. package/src/components/metadata/list.tsx +3 -0
  24. package/src/components/passport/assign.tsx +3 -0
  25. package/src/components/payment-link/rename.tsx +1 -0
  26. package/src/components/pricing-table/rename.tsx +1 -0
  27. package/src/components/product/add-price.tsx +1 -0
  28. package/src/components/product/edit-price.tsx +1 -0
  29. package/src/components/product/edit.tsx +1 -0
  30. package/src/components/subscription/actions/index.tsx +1 -0
  31. package/src/components/subscription/portal/actions.tsx +1 -0
  32. package/src/locales/en.tsx +42 -0
  33. package/src/locales/zh.tsx +37 -0
  34. package/src/pages/admin/payments/payouts/detail.tsx +47 -43
  35. package/src/pages/admin/settings/index.tsx +3 -3
  36. package/src/pages/admin/settings/payment-methods/index.tsx +33 -1
  37. package/src/pages/admin/settings/vault-config/edit-form.tsx +253 -0
  38. package/src/pages/admin/settings/vault-config/index.tsx +352 -0
  39. package/src/pages/integrations/donations/edit-form.tsx +0 -1
package/src/app.tsx CHANGED
@@ -43,119 +43,113 @@ const IntegrationsPage = React.lazy(() => import('./pages/integrations'));
43
43
 
44
44
  function App() {
45
45
  return (
46
- <PaymentThemeProvider>
47
- <TransitionProvider>
48
- <LocaleProvider translations={translations} fallbackLocale="en">
49
- <ErrorBoundary FallbackComponent={ErrorFallback} onReset={window.location.reload}>
50
- <Suspense
51
- fallback={
52
- <Center>
53
- <CircularProgress />
54
- </Center>
55
- }>
56
- <Routes>
57
- <Route path="/" element={<HomePage />} />
58
- <Route path="/checkout/:action/:id" element={<CheckoutPage />} />
59
- <Route key="admin-index" path="/admin" element={<AdminPage />} />,
60
- <Route key="admin-tabs" path="/admin/:group" element={<AdminPage />} />,
61
- <Route key="admin-sub" path="/admin/:group/:page" element={<AdminPage />} />,
62
- <Route key="admin-fallback" path="/admin/*" element={<AdminPage />} />,
63
- <Route key="integrations-index" path="/integrations" element={<IntegrationsPage />} />
64
- <Route key="integrations-tabs" path="/integrations/:group" element={<IntegrationsPage />} />
65
- <Route key="integrations-sub" path="/integrations/:group/:page" element={<IntegrationsPage />} />
66
- <Route key="integrations-fallback" path="/integrations/*" element={<IntegrationsPage />} />
67
- <Route
68
- key="customer-home"
69
- path="/customer"
70
- element={
71
- <UserLayout>
72
- <CustomerHome />
73
- </UserLayout>
74
- }
75
- />
76
- <Route
77
- key="customer-subscription"
78
- path="/customer/subscription/:id"
79
- element={
80
- <UserLayout>
81
- <CustomerSubscriptionDetail />
82
- </UserLayout>
83
- }
84
- />
85
- <Route
86
- key="customer-subscription-change-plan"
87
- path="/customer/subscription/:id/change-plan"
88
- element={
89
- <UserLayout>
90
- <CustomerSubscriptionChangePlan />
91
- </UserLayout>
92
- }
93
- />
94
- <Route
95
- key="customer-subscription-change-payment"
96
- path="/customer/subscription/:id/change-payment"
97
- element={
98
- <UserLayout>
99
- <CustomerSubscriptionChangePayment />
100
- </UserLayout>
101
- }
102
- />
103
- <Route
104
- key="customer-recharge"
105
- path="/customer/subscription/:id/recharge"
106
- element={
107
- <UserLayout>
108
- <CustomerRecharge />
109
- </UserLayout>
110
- }
111
- />
112
- <Route
113
- key="customer-embed"
114
- path="/customer/embed/subscription"
115
- element={<CustomerSubscriptionEmbed />}
116
- />
117
- ,
118
- <Route
119
- key="subscription-embed"
120
- path="/embed/customer/subscription"
121
- element={<CustomerSubscriptionEmbed />}
122
- />
123
- ,
124
- <Route
125
- key="customer-due"
126
- path="/customer/invoice/past-due"
127
- element={
128
- <UserLayout>
129
- <CustomerInvoicePastDue />
130
- </UserLayout>
131
- }
132
- />
133
- <Route
134
- key="customer-invoice"
135
- path="/customer/invoice/:id"
136
- element={
137
- <UserLayout>
138
- <CustomerInvoiceDetail />
139
- </UserLayout>
140
- }
141
- />
142
- <Route
143
- key="customer-payout"
144
- path="/customer/payout/:id"
145
- element={
146
- <UserLayout>
147
- <CustomerPayoutDetail />
148
- </UserLayout>
149
- }
150
- />
151
- <Route key="customer-fallback" path="/customer/*" element={<Navigate to="/customer" />} />,
152
- <Route path="*" element={<Navigate to="/" />} />
153
- </Routes>
154
- </Suspense>
155
- </ErrorBoundary>
156
- </LocaleProvider>
157
- </TransitionProvider>
158
- </PaymentThemeProvider>
46
+ <TransitionProvider>
47
+ <LocaleProvider translations={translations} fallbackLocale="en">
48
+ <ErrorBoundary FallbackComponent={ErrorFallback} onReset={window.location.reload}>
49
+ <Suspense
50
+ fallback={
51
+ <Center>
52
+ <CircularProgress />
53
+ </Center>
54
+ }>
55
+ <Routes>
56
+ <Route path="/" element={<HomePage />} />
57
+ <Route path="/checkout/:action/:id" element={<CheckoutPage />} />
58
+ <Route key="admin-index" path="/admin" element={<AdminPage />} />,
59
+ <Route key="admin-tabs" path="/admin/:group" element={<AdminPage />} />,
60
+ <Route key="admin-sub" path="/admin/:group/:page" element={<AdminPage />} />,
61
+ <Route key="admin-fallback" path="/admin/*" element={<AdminPage />} />,
62
+ <Route key="integrations-index" path="/integrations" element={<IntegrationsPage />} />
63
+ <Route key="integrations-tabs" path="/integrations/:group" element={<IntegrationsPage />} />
64
+ <Route key="integrations-sub" path="/integrations/:group/:page" element={<IntegrationsPage />} />
65
+ <Route key="integrations-fallback" path="/integrations/*" element={<IntegrationsPage />} />
66
+ <Route
67
+ key="customer-home"
68
+ path="/customer"
69
+ element={
70
+ <UserLayout>
71
+ <CustomerHome />
72
+ </UserLayout>
73
+ }
74
+ />
75
+ <Route
76
+ key="customer-subscription"
77
+ path="/customer/subscription/:id"
78
+ element={
79
+ <UserLayout>
80
+ <CustomerSubscriptionDetail />
81
+ </UserLayout>
82
+ }
83
+ />
84
+ <Route
85
+ key="customer-subscription-change-plan"
86
+ path="/customer/subscription/:id/change-plan"
87
+ element={
88
+ <UserLayout>
89
+ <CustomerSubscriptionChangePlan />
90
+ </UserLayout>
91
+ }
92
+ />
93
+ <Route
94
+ key="customer-subscription-change-payment"
95
+ path="/customer/subscription/:id/change-payment"
96
+ element={
97
+ <UserLayout>
98
+ <CustomerSubscriptionChangePayment />
99
+ </UserLayout>
100
+ }
101
+ />
102
+ <Route
103
+ key="customer-recharge"
104
+ path="/customer/subscription/:id/recharge"
105
+ element={
106
+ <UserLayout>
107
+ <CustomerRecharge />
108
+ </UserLayout>
109
+ }
110
+ />
111
+ <Route key="customer-embed" path="/customer/embed/subscription" element={<CustomerSubscriptionEmbed />} />
112
+ ,
113
+ <Route
114
+ key="subscription-embed"
115
+ path="/embed/customer/subscription"
116
+ element={<CustomerSubscriptionEmbed />}
117
+ />
118
+ ,
119
+ <Route
120
+ key="customer-due"
121
+ path="/customer/invoice/past-due"
122
+ element={
123
+ <UserLayout>
124
+ <CustomerInvoicePastDue />
125
+ </UserLayout>
126
+ }
127
+ />
128
+ <Route
129
+ key="customer-invoice"
130
+ path="/customer/invoice/:id"
131
+ element={
132
+ <UserLayout>
133
+ <CustomerInvoiceDetail />
134
+ </UserLayout>
135
+ }
136
+ />
137
+ <Route
138
+ key="customer-payout"
139
+ path="/customer/payout/:id"
140
+ element={
141
+ <UserLayout>
142
+ <CustomerPayoutDetail />
143
+ </UserLayout>
144
+ }
145
+ />
146
+ <Route key="customer-fallback" path="/customer/*" element={<Navigate to="/customer" />} />,
147
+ <Route path="*" element={<Navigate to="/" />} />
148
+ </Routes>
149
+ </Suspense>
150
+ </ErrorBoundary>
151
+ </LocaleProvider>
152
+ </TransitionProvider>
159
153
  );
160
154
  }
161
155
 
@@ -169,13 +163,15 @@ export default function WrappedApp() {
169
163
 
170
164
  return (
171
165
  <ToastProvider>
172
- <SessionProvider
173
- serviceHost={prefix}
174
- protectedRoutes={['/admin/*', '/customer/*', '/integrations/*'].map((item) => joinURL(prefix, item))}>
175
- <Router basename={prefix}>
176
- <AppWithTracker />
177
- </Router>
178
- </SessionProvider>
166
+ <PaymentThemeProvider>
167
+ <SessionProvider
168
+ serviceHost={prefix}
169
+ protectedRoutes={['/admin/*', '/customer/*', '/integrations/*'].map((item) => joinURL(prefix, item))}>
170
+ <Router basename={prefix}>
171
+ <AppWithTracker />
172
+ </Router>
173
+ </SessionProvider>
174
+ </PaymentThemeProvider>
179
175
  </ToastProvider>
180
176
  );
181
177
  }
@@ -227,6 +227,7 @@ export default function OverdraftProtectionDialog({
227
227
  onClose={onCancel}
228
228
  maxWidth="sm"
229
229
  fullWidth
230
+ className="base-dialog"
230
231
  title={t('customer.overdraftProtection.setting')}
231
232
  actions={
232
233
  <Stack direction="row" spacing={2}>
@@ -22,6 +22,12 @@ const Root = styled(Dashboard)<{ padding: string }>`
22
22
  min-width: auto;
23
23
  font-size: 0.875rem;
24
24
  }
25
+ .page-content .MuiTab-root {
26
+ font-size: 0.875rem;
27
+ &.Mui-selected {
28
+ font-size: 1.125rem;
29
+ }
30
+ }
25
31
 
26
32
  .MuiTabs-root {
27
33
  min-height: 32px;
@@ -27,6 +27,7 @@ export default function UserLayout(props: any) {
27
27
  <UserCenter
28
28
  currentTab={`${window.blocklet.prefix}customer`}
29
29
  userDid={session.user.did}
30
+ hideFooter
30
31
  notLoginContent="undefined">
31
32
  {props.children}
32
33
  </UserCenter>
@@ -57,7 +57,13 @@ export default function MetadataEditor({
57
57
  };
58
58
 
59
59
  return (
60
- <Dialog open disableEscapeKeyDown fullWidth onClose={() => onCancel(null)} title={t('common.metadata.edit')}>
60
+ <Dialog
61
+ open
62
+ disableEscapeKeyDown
63
+ fullWidth
64
+ onClose={() => onCancel(null)}
65
+ title={t('common.metadata.edit')}
66
+ className="base-dialog">
61
67
  <FormProvider {...methods}>
62
68
  <MetadataForm
63
69
  actions={
@@ -36,6 +36,9 @@ export default function MetadataList({
36
36
  if (isObject(value)) {
37
37
  return <pre style={{ whiteSpace: 'break-spaces' }}>{JSON.stringify(value, null, 2)}</pre>;
38
38
  }
39
+ if (typeof value === 'boolean') {
40
+ return String(value);
41
+ }
39
42
  return value;
40
43
  };
41
44
 
@@ -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
  }
@@ -39,6 +39,7 @@ export default function RenamePaymentLink({
39
39
  disableEscapeKeyDown
40
40
  fullWidth
41
41
  maxWidth="sm"
42
+ className="base-dialog"
42
43
  onClose={() => onCancel(null)}
43
44
  showCloseButton={false}
44
45
  title={t('admin.paymentLink.rename')}
@@ -39,6 +39,7 @@ export default function RenamePricingTable({
39
39
  disableEscapeKeyDown
40
40
  fullWidth
41
41
  maxWidth="sm"
42
+ className="base-dialog"
42
43
  onClose={() => onCancel(null)}
43
44
  showCloseButton={false}
44
45
  title={t('admin.pricingTable.rename')}
@@ -48,6 +48,7 @@ export default function AddPrice({
48
48
  disableEscapeKeyDown
49
49
  fullWidth
50
50
  maxWidth="sm"
51
+ className="base-dialog"
51
52
  onClose={() => onCancel(null)}
52
53
  showCloseButton={false}
53
54
  title={t('admin.price.add')}
@@ -75,6 +75,7 @@ export default function EditPrice({
75
75
  disableEscapeKeyDown
76
76
  fullWidth
77
77
  maxWidth="sm"
78
+ className="base-dialog"
78
79
  onClose={() => onCancel(null)}
79
80
  showCloseButton={false}
80
81
  title={t('admin.price.edit')}
@@ -50,6 +50,7 @@ export default function EditProduct({
50
50
  maxWidth="sm"
51
51
  onClose={() => onCancel(null)}
52
52
  showCloseButton={false}
53
+ className="base-dialog"
53
54
  title={t('admin.product.edit')}
54
55
  actions={
55
56
  <Stack direction="row">
@@ -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' && (
@@ -495,6 +495,7 @@ export function SubscriptionActionsInner({
495
495
  date: formatToDate(subscription.current_period_end * 1000),
496
496
  })}
497
497
  loading={state.loading}
498
+ color="primary"
498
499
  />
499
500
  )}
500
501
 
@@ -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',
@@ -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,48 +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
- <InfoMetric
198
- label={t('customer.payout.payer')}
199
- value={
200
- <InfoCard
201
- logo={getCustomerAvatar(
202
- paymentIntent?.customer?.did,
203
- paymentIntent?.customer?.updated_at
204
- ? new Date(paymentIntent?.customer?.updated_at).toISOString()
205
- : '',
206
- 48
207
- )}
208
- name={
209
- <Typography
210
- variant="subtitle2"
211
- sx={{
212
- cursor: 'pointer',
213
- '&:hover': {
214
- color: 'text.link',
215
- },
216
- }}
217
- onClick={() => {
218
- const url = getCustomerProfileUrl({
219
- userDid: paymentIntent?.customer?.did,
220
- locale: 'zh',
221
- });
222
- window.open(url, '_blank');
223
- }}>
224
- {paymentIntent?.customer?.name} ({paymentIntent?.customer?.email})
225
- </Typography>
226
- }
227
- description={
228
- <DID
229
- did={paymentIntent?.customer?.did}
230
- {...(isMobile ? { responsive: false, compact: true } : {})}
231
- />
232
- }
233
- size={40}
234
- variant="rounded"
235
- />
236
- }
237
- divider
238
- />
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
+ )}
239
241
  {/* <InfoMetric label={t('common.createdAt')} value={formatTime(data.created_at)} divider /> */}
240
242
  {/* <InfoMetric label={t('common.updatedAt')} value={formatTime(data.updated_at)} divider /> */}
241
243
  </Stack>
@@ -385,13 +387,15 @@ export default function PayoutDetail(props: { id: string }) {
385
387
  <Box className="section">
386
388
  <SectionHeader title={t('admin.connections')} />
387
389
  <Stack>
388
- {data.payment_intent_id && (
390
+ {data.payment_intent_id ? (
389
391
  <InfoRow
390
392
  label={t('admin.paymentIntent.name')}
391
393
  value={<Link to={`/admin/payments/${data.paymentIntent.id}`}>{data.paymentIntent.id}</Link>}
392
394
  direction={InfoDirection}
393
395
  alignItems={InfoAlignItems}
394
396
  />
397
+ ) : (
398
+ t('common.none')
395
399
  )}
396
400
  </Stack>
397
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
- paymentMethods: React.lazy(() => import('./payment-methods')),
13
- // branding: React.lazy(() => import('./branding')),
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
- // { label: t('admin.branding'), value: 'branding' },
33
+ { label: t('admin.vaultConfig.title'), value: 'vault-config' },
34
34
  // { label: t('admin.business'), value: 'business' },
35
35
  ];
36
36