payment-kit 1.20.7 → 1.20.9

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 (32) hide show
  1. package/api/src/crons/index.ts +1 -1
  2. package/api/src/index.ts +2 -2
  3. package/api/src/libs/{vendor → vendor-util}/adapters/factory.ts +9 -5
  4. package/api/src/libs/{vendor → vendor-util}/adapters/launcher-adapter.ts +3 -8
  5. package/api/src/libs/{vendor → vendor-util}/adapters/types.ts +1 -4
  6. package/api/src/libs/{vendor → vendor-util}/fulfillment.ts +11 -8
  7. package/api/src/queues/{vendor → vendors}/commission.ts +4 -5
  8. package/api/src/queues/{vendor → vendors}/fulfillment-coordinator.ts +33 -4
  9. package/api/src/queues/{vendor → vendors}/fulfillment.ts +2 -2
  10. package/api/src/queues/{vendor → vendors}/status-check.ts +2 -2
  11. package/api/src/routes/payment-links.ts +2 -1
  12. package/api/src/routes/products.ts +1 -0
  13. package/api/src/routes/vendor.ts +135 -213
  14. package/api/src/store/migrations/20250911-add-vendor-type.ts +26 -0
  15. package/api/src/store/models/product-vendor.ts +6 -24
  16. package/api/src/store/models/product.ts +1 -0
  17. package/blocklet.yml +1 -1
  18. package/doc/vendor_fulfillment_system.md +1 -1
  19. package/package.json +23 -22
  20. package/src/components/metadata/form.tsx +12 -19
  21. package/src/components/payment-link/before-pay.tsx +40 -0
  22. package/src/components/product/vendor-config.tsx +4 -11
  23. package/src/components/subscription/description.tsx +1 -6
  24. package/src/components/subscription/portal/list.tsx +82 -6
  25. package/src/components/subscription/vendor-service-list.tsx +128 -0
  26. package/src/components/vendor/actions.tsx +1 -33
  27. package/src/locales/en.tsx +13 -3
  28. package/src/locales/zh.tsx +13 -3
  29. package/src/pages/admin/products/links/create.tsx +2 -0
  30. package/src/pages/admin/products/vendors/create.tsx +108 -194
  31. package/src/pages/admin/products/vendors/index.tsx +14 -22
  32. package/src/pages/customer/subscription/detail.tsx +26 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payment-kit",
3
- "version": "1.20.7",
3
+ "version": "1.20.9",
4
4
  "scripts": {
5
5
  "dev": "blocklet dev --open",
6
6
  "lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
@@ -10,6 +10,7 @@
10
10
  "coverage": "pnpm run test --coverage",
11
11
  "start": "tsx watch api/dev.ts",
12
12
  "clean": "node scripts/build-clean.js",
13
+ "prebundle": "npm run types",
13
14
  "bundle": "tsc --noEmit && pnpm run bundle:client && pnpm run bundle:api",
14
15
  "bundle:client": "vite build",
15
16
  "bundle:analyze": "cross-env ANALYZE=true vite build",
@@ -43,33 +44,33 @@
43
44
  ]
44
45
  },
45
46
  "dependencies": {
46
- "@abtnode/cron": "^1.16.52-beta-20250908-085420-224a58fa",
47
- "@arcblock/did": "^1.24.8",
48
- "@arcblock/did-connect-react": "^3.1.37",
47
+ "@abtnode/cron": "^1.16.52-beta-20250912-112002-e3499e9c",
48
+ "@arcblock/did": "^1.24.9",
49
+ "@arcblock/did-connect-react": "^3.1.40",
49
50
  "@arcblock/did-connect-storage-nedb": "^1.8.0",
50
- "@arcblock/did-util": "^1.24.8",
51
- "@arcblock/jwt": "^1.24.8",
52
- "@arcblock/ux": "^3.1.37",
53
- "@arcblock/validator": "^1.24.8",
51
+ "@arcblock/did-util": "^1.24.9",
52
+ "@arcblock/jwt": "^1.24.9",
53
+ "@arcblock/ux": "^3.1.40",
54
+ "@arcblock/validator": "^1.24.9",
54
55
  "@blocklet/did-space-js": "^1.1.23",
55
56
  "@blocklet/error": "^0.2.5",
56
- "@blocklet/js-sdk": "^1.16.52-beta-20250908-085420-224a58fa",
57
- "@blocklet/logger": "^1.16.52-beta-20250908-085420-224a58fa",
58
- "@blocklet/payment-react": "1.20.7",
59
- "@blocklet/payment-vendor": "1.20.7",
60
- "@blocklet/sdk": "^1.16.52-beta-20250908-085420-224a58fa",
61
- "@blocklet/ui-react": "^3.1.37",
57
+ "@blocklet/js-sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
58
+ "@blocklet/logger": "^1.16.52-beta-20250912-112002-e3499e9c",
59
+ "@blocklet/payment-react": "1.20.9",
60
+ "@blocklet/payment-vendor": "1.20.9",
61
+ "@blocklet/sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
62
+ "@blocklet/ui-react": "^3.1.40",
62
63
  "@blocklet/uploader": "^0.2.10",
63
64
  "@blocklet/xss": "^0.2.7",
64
65
  "@mui/icons-material": "^7.1.2",
65
66
  "@mui/lab": "7.0.0-beta.14",
66
67
  "@mui/material": "^7.1.2",
67
68
  "@mui/system": "^7.1.1",
68
- "@ocap/asset": "^1.24.8",
69
- "@ocap/client": "^1.24.8",
70
- "@ocap/mcrypto": "^1.24.8",
71
- "@ocap/util": "^1.24.8",
72
- "@ocap/wallet": "^1.24.8",
69
+ "@ocap/asset": "^1.24.9",
70
+ "@ocap/client": "^1.24.9",
71
+ "@ocap/mcrypto": "^1.24.9",
72
+ "@ocap/util": "^1.24.9",
73
+ "@ocap/wallet": "^1.24.9",
73
74
  "@stripe/react-stripe-js": "^2.9.0",
74
75
  "@stripe/stripe-js": "^2.4.0",
75
76
  "ahooks": "^3.8.5",
@@ -123,9 +124,9 @@
123
124
  "web3": "^4.16.0"
124
125
  },
125
126
  "devDependencies": {
126
- "@abtnode/types": "^1.16.52-beta-20250908-085420-224a58fa",
127
+ "@abtnode/types": "^1.16.52-beta-20250912-112002-e3499e9c",
127
128
  "@arcblock/eslint-config-ts": "^0.3.3",
128
- "@blocklet/payment-types": "1.20.7",
129
+ "@blocklet/payment-types": "1.20.9",
129
130
  "@types/cookie-parser": "^1.4.9",
130
131
  "@types/cors": "^2.8.19",
131
132
  "@types/debug": "^4.1.12",
@@ -172,5 +173,5 @@
172
173
  "parser": "typescript"
173
174
  }
174
175
  },
175
- "gitHead": "4ceda9d8cf6413d3c28514d774eb653ba5145f1b"
176
+ "gitHead": "e89c0a9b6a3f3b7f7cb3cc0845b1775c1f587f6e"
176
177
  }
@@ -151,6 +151,7 @@ export default function MetadataForm({
151
151
  color="primary"
152
152
  sx={{
153
153
  color: color === 'inherit' ? 'text.secondary' : 'primary.main',
154
+ minWidth: 'max-content',
154
155
  }}
155
156
  onClick={() => handleModeChange(mode === 'form' ? 'json' : 'form')}
156
157
  startIcon={<Autorenew />}>
@@ -162,7 +163,7 @@ export default function MetadataForm({
162
163
  <Box sx={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column' }}>
163
164
  {title ? (
164
165
  <Stack sx={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
165
- <FormLabel sx={{ color: 'text.primary', mt: 0 }}>{title}</FormLabel>
166
+ <FormLabel sx={{ color: 'text.primary', my: 0, '& label': { mb: 0 } }}>{title}</FormLabel>
166
167
  {toggleBtn}
167
168
  </Stack>
168
169
  ) : (
@@ -189,15 +190,17 @@ export default function MetadataForm({
189
190
  ref={index === metadata.fields.length - 1 ? lastItemRef : null}
190
191
  sx={{
191
192
  mt: 2,
193
+ '&:first-child': { mt: 0 },
192
194
  spacing: 2,
193
-
194
195
  alignItems: 'flex-end',
195
196
  }}
196
197
  spacing={2}
197
198
  direction="row">
198
- <Stack direction="row" spacing={2} sx={{ flex: 1 }}>
199
+ <Stack
200
+ direction="row"
201
+ spacing={2}
202
+ sx={{ flex: 1, '& > div:first-child': { flex: 1 }, '& > div:last-child': { flex: 2 } }}>
199
203
  <FormInput
200
- sx={{ flex: 1 }}
201
204
  errorPosition="right"
202
205
  size="small"
203
206
  name={`metadata.${index}.key`}
@@ -208,19 +211,17 @@ export default function MetadataForm({
208
211
  message: t('common.maxLength', { len: 40 }),
209
212
  },
210
213
  }}
214
+ label="Key *"
211
215
  placeholder="Key"
212
- label="Key"
213
216
  // @ts-ignore
214
217
  ref={errors?.metadata?.[index]?.key ? errorRef : null}
215
- inputProps={{
216
- maxLength: 40,
217
- }}
218
+ inputProps={{ maxLength: 40 }}
218
219
  />
219
220
  <FormInput
220
- sx={{ flex: 2 }}
221
221
  size="small"
222
222
  errorPosition="right"
223
223
  name={`metadata.${index}.value`}
224
+ label="Value *"
224
225
  placeholder="Value"
225
226
  rules={{
226
227
  required: t('payment.checkout.required'),
@@ -229,12 +230,9 @@ export default function MetadataForm({
229
230
  message: t('common.maxLength', { len: 256 }),
230
231
  },
231
232
  }}
232
- label="Value"
233
233
  // @ts-ignore
234
234
  ref={errors?.metadata?.[index]?.value ? errorRef : null}
235
- inputProps={{
236
- maxLength: 256,
237
- }}
235
+ inputProps={{ maxLength: 256 }}
238
236
  />
239
237
  </Stack>
240
238
  <IconButton
@@ -322,12 +320,7 @@ export default function MetadataForm({
322
320
  }}
323
321
  />
324
322
  <Divider />
325
- <Stack
326
- direction="row"
327
- sx={{
328
- mt: 2,
329
- justifyContent: 'flex-end',
330
- }}>
323
+ <Stack direction="row" sx={{ mt: 2, justifyContent: 'flex-end' }}>
331
324
  {actions}
332
325
  </Stack>
333
326
  </>
@@ -239,6 +239,46 @@ export default function BeforePay({
239
239
  />
240
240
  )}
241
241
  />
242
+ <Controller
243
+ name="subscription_data.no_stake"
244
+ control={control}
245
+ render={({ field }) => (
246
+ <FormControlLabel
247
+ control={
248
+ <Checkbox
249
+ checked={getValues().subscription_data?.no_stake || false}
250
+ {...field}
251
+ onChange={(_, checked) => setValue(field.name, checked)}
252
+ />
253
+ }
254
+ label={t('admin.paymentLink.noStakeRequired')}
255
+ />
256
+ )}
257
+ />
258
+ <Controller
259
+ name="metadata"
260
+ control={control}
261
+ render={({ field }) => {
262
+ const metadata = field.value || {};
263
+ const isChecked = metadata.show_product_features === 'true';
264
+
265
+ return (
266
+ <FormControlLabel
267
+ control={
268
+ <Checkbox
269
+ checked={isChecked}
270
+ onChange={(_, checked) => {
271
+ const newMetadata = { ...metadata };
272
+ newMetadata.show_product_features = checked ? 'true' : 'false';
273
+ field.onChange(newMetadata);
274
+ }}
275
+ />
276
+ }
277
+ label={t('admin.paymentLink.showProductFeatures')}
278
+ />
279
+ );
280
+ }}
281
+ />
242
282
  {includeFreeTrial && (
243
283
  <Controller
244
284
  name="subscription_data.trial_period_days"
@@ -10,8 +10,7 @@ interface Vendor {
10
10
  name: string;
11
11
  description: string;
12
12
  vendor_key: string;
13
- default_commission_rate: number;
14
- default_commission_type: 'percentage' | 'fixed_amount';
13
+ vendor_type: string;
15
14
  }
16
15
 
17
16
  export default function VendorConfig() {
@@ -120,15 +119,9 @@ export default function VendorConfig() {
120
119
  const vendorId = e.target.value as string;
121
120
  field.onChange(vendorId);
122
121
 
123
- const selectedVendor = vendors.find((v) => v.id === vendorId);
124
- if (selectedVendor) {
125
- setValue(`vendor_config.${index}.commission_rate`, selectedVendor.default_commission_rate);
126
- const commissionType =
127
- selectedVendor.default_commission_type === 'fixed_amount'
128
- ? 'fixed'
129
- : selectedVendor.default_commission_type;
130
- setValue(`vendor_config.${index}.commission_type`, commissionType);
131
- }
122
+ // Set default values when selecting a vendor
123
+ setValue(`vendor_config.${index}.commission_rate`, 20);
124
+ setValue(`vendor_config.${index}.commission_type`, 'percentage');
132
125
  }}
133
126
  renderValue={(value: string) => {
134
127
  const vendor = vendors.find((v) => v.id === value);
@@ -19,12 +19,7 @@ export default function SubscriptionDescription({
19
19
  const { isMobile } = useMobile();
20
20
  if (subscription.description) {
21
21
  return (
22
- <Stack
23
- direction="row"
24
- spacing={1}
25
- sx={{
26
- alignItems: 'center',
27
- }}>
22
+ <Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
28
23
  <Typography variant={variant} className="subscription-description">
29
24
  <TruncatedText text={subscription.description} maxLength={maxLength} useWidth />
30
25
  </Typography>
@@ -38,6 +38,8 @@ const fetchData = (params: Record<string, any> = {}): Promise<SubscriptionListRe
38
38
  return api.get(`/api/subscriptions?${search.toString()}`).then((res) => res.data);
39
39
  };
40
40
 
41
+ const TAG_COUNT = 2;
42
+
41
43
  type Props = {
42
44
  id: string;
43
45
  status: string;
@@ -146,6 +148,7 @@ export default function CurrentSubscriptions({
146
148
  flexDirection: 'column',
147
149
  justifyContent: 'space-between',
148
150
  gap: 2,
151
+ width: '100%',
149
152
 
150
153
  '&:hover': {
151
154
  backgroundColor: 'grey.50',
@@ -160,6 +163,7 @@ export default function CurrentSubscriptions({
160
163
  sx={[
161
164
  {
162
165
  flex: 1,
166
+ width: '100%',
163
167
  },
164
168
  ...(Array.isArray(rest.sx) ? rest.sx : [rest.sx]),
165
169
  ]}>
@@ -170,12 +174,14 @@ export default function CurrentSubscriptions({
170
174
  sx={{
171
175
  alignItems: 'flex-start',
172
176
  justifyContent: 'space-between',
177
+ width: '100%',
173
178
  }}>
174
179
  <Stack
175
180
  direction="row"
176
181
  spacing={1.5}
177
182
  sx={{
178
183
  alignItems: 'center',
184
+ width: '100%',
179
185
  }}>
180
186
  {subscription.metadata?.appLogo ? (
181
187
  <Avatar
@@ -212,18 +218,88 @@ export default function CurrentSubscriptions({
212
218
  </AvatarGroup>
213
219
  )}
214
220
 
215
- <Stack direction="column" spacing={0.25}>
221
+ <Stack direction="column" spacing={0.25} width="100%">
216
222
  <SubscriptionDescription
217
223
  subscription={subscription}
218
224
  hideSubscription
219
225
  maxLength={isMobile ? 30 : 40}
220
226
  variant={isMobile ? 'subtitle2' : 'subtitle1'}
221
227
  />
222
- <SubscriptionStatus
223
- subscription={subscription}
224
- sx={{ height: 18, width: 'fit-content' }}
225
- size="small"
226
- />
228
+ <Box
229
+ sx={{
230
+ display: 'flex',
231
+ alignItems: 'center',
232
+ gap: 1,
233
+ justifyContent: 'space-between',
234
+ }}>
235
+ <SubscriptionStatus
236
+ subscription={subscription}
237
+ sx={{ height: 18, width: 'fit-content' }}
238
+ size="small"
239
+ />
240
+ {/* Vendor 信息显示 */}
241
+ {(() => {
242
+ const vendorConfig = subscription.items?.[0]?.price?.product?.vendor_config;
243
+ if (!vendorConfig || vendorConfig.length === 0) return null;
244
+
245
+ return (
246
+ <Box
247
+ sx={{
248
+ display: 'flex',
249
+ alignItems: 'center',
250
+ gap: 0.5,
251
+ flexWrap: 'wrap',
252
+ }}>
253
+ {vendorConfig.slice(0, TAG_COUNT).map((vendor, index) => (
254
+ <Box
255
+ key={vendor.vendor_id || index}
256
+ sx={{
257
+ display: 'flex',
258
+ alignItems: 'center',
259
+ gap: 0.25,
260
+ px: 0.5,
261
+ py: 0.25,
262
+ bgcolor: 'action.hover',
263
+ borderRadius: 0.5,
264
+ border: '1px solid',
265
+ borderColor: 'divider',
266
+ }}>
267
+ <Box
268
+ sx={{
269
+ width: 4,
270
+ height: 4,
271
+ borderRadius: '50%',
272
+ bgcolor: 'success.main',
273
+ }}
274
+ />
275
+ <Typography
276
+ variant="caption"
277
+ sx={{
278
+ color: 'text.secondary',
279
+ fontSize: '0.7rem',
280
+ fontWeight: 500,
281
+ whiteSpace: 'nowrap',
282
+ }}>
283
+ {vendor.name || vendor.vendor_key}
284
+ </Typography>
285
+ </Box>
286
+ ))}
287
+ {vendorConfig.length > TAG_COUNT && (
288
+ <Typography
289
+ variant="caption"
290
+ sx={{
291
+ color: 'text.secondary',
292
+ fontSize: '0.7rem',
293
+ fontStyle: 'italic',
294
+ px: 0.5,
295
+ }}>
296
+ +{vendorConfig.length - TAG_COUNT} more
297
+ </Typography>
298
+ )}
299
+ </Box>
300
+ );
301
+ })()}
302
+ </Box>
227
303
  </Stack>
228
304
  </Stack>
229
305
  </Stack>
@@ -0,0 +1,128 @@
1
+ import { Box, Stack, Typography, IconButton, Tooltip } from '@mui/material';
2
+ import { Home, Dashboard } from '@mui/icons-material';
3
+ import { useLocaleContext } from '@arcblock/ux/lib/Locale/context';
4
+ import { joinURL } from 'ufo';
5
+
6
+ interface VendorConfig {
7
+ vendor_id: string;
8
+ vendor_key: string;
9
+ name?: string;
10
+ vendor_type?: string;
11
+ }
12
+
13
+ interface VendorServiceListProps {
14
+ vendorServices: VendorConfig[];
15
+ subscriptionId: string;
16
+ }
17
+
18
+ export default function VendorServiceList({ vendorServices, subscriptionId }: VendorServiceListProps) {
19
+ const { t } = useLocaleContext();
20
+
21
+ if (!vendorServices || vendorServices.length === 0) {
22
+ return null;
23
+ }
24
+
25
+ const prefix = window.blocklet.prefix || '/';
26
+
27
+ return (
28
+ <>
29
+ <Typography variant="h3" className="section-header" sx={{ mb: 3 }}>
30
+ {t('admin.subscription.includedServices')} ({vendorServices.length})
31
+ </Typography>
32
+ <Box className="section-body">
33
+ <Stack
34
+ spacing={2}
35
+ sx={{
36
+ display: 'grid',
37
+ gridTemplateColumns: { xs: '1fr', md: '1fr 1fr', lg: '1fr 1fr 1fr' },
38
+ gap: 2,
39
+ }}>
40
+ {vendorServices.map((vendor, index) => {
41
+ const isLauncher = vendor.vendor_type === 'launcher';
42
+
43
+ return (
44
+ <Box
45
+ key={vendor.vendor_key || index}
46
+ sx={{
47
+ p: 2,
48
+ border: '1px solid',
49
+ borderColor: 'divider',
50
+ borderRadius: 2,
51
+ backgroundColor: 'background.paper',
52
+ '&:hover': {
53
+ backgroundColor: 'action.hover',
54
+ },
55
+ transition: 'background-color 0.2s ease',
56
+ }}>
57
+ <Stack direction="row" justifyContent="space-between" alignItems="flex-start">
58
+ <Stack direction="row" alignItems="center" spacing={1} flex={1}>
59
+ <Box
60
+ sx={{
61
+ width: 8,
62
+ height: 8,
63
+ borderRadius: '50%',
64
+ bgcolor: 'success.main',
65
+ flexShrink: 0,
66
+ }}
67
+ />
68
+ <Typography
69
+ variant="body1"
70
+ sx={{
71
+ fontWeight: 600,
72
+ fontSize: '1rem',
73
+ color: 'text.primary',
74
+ }}>
75
+ {vendor.name || vendor.vendor_key}
76
+ </Typography>
77
+ </Stack>
78
+ {/* Launcher 类型的链接 */}
79
+ {isLauncher && (
80
+ <Stack direction="row" spacing={0.5}>
81
+ <Tooltip title={t('admin.subscription.serviceHome')} placement="top">
82
+ <IconButton
83
+ size="small"
84
+ component="a"
85
+ href={joinURL(prefix, '/api/vendors/open/', subscriptionId, `?vendorId=${vendor.vendor_id}`)}
86
+ target="_blank"
87
+ rel="noopener noreferrer"
88
+ sx={{
89
+ color: 'primary.main',
90
+ '&:hover': {
91
+ backgroundColor: 'primary.lighter',
92
+ },
93
+ }}>
94
+ <Home fontSize="small" />
95
+ </IconButton>
96
+ </Tooltip>
97
+ <Tooltip title={t('admin.subscription.serviceDashboard')} placement="top">
98
+ <IconButton
99
+ size="small"
100
+ component="a"
101
+ href={joinURL(
102
+ prefix,
103
+ '/api/vendors/open/',
104
+ subscriptionId,
105
+ `?vendorId=${vendor.vendor_id}&target=dashboard`
106
+ )}
107
+ target="_blank"
108
+ rel="noopener noreferrer"
109
+ sx={{
110
+ color: 'primary.main',
111
+ '&:hover': {
112
+ backgroundColor: 'primary.lighter',
113
+ },
114
+ }}>
115
+ <Dashboard fontSize="small" />
116
+ </IconButton>
117
+ </Tooltip>
118
+ </Stack>
119
+ )}
120
+ </Stack>
121
+ </Box>
122
+ );
123
+ })}
124
+ </Stack>
125
+ </Box>
126
+ </>
127
+ );
128
+ }
@@ -10,14 +10,11 @@ import ClickBoundary from '../click-boundary';
10
10
  interface Vendor {
11
11
  id: string;
12
12
  vendor_key: string;
13
+ vendor_type: string;
13
14
  name: string;
14
15
  description: string;
15
16
  app_url: string;
16
- webhook_path: string;
17
- default_commission_rate: number;
18
- default_commission_type: 'percentage' | 'fixed_amount';
19
17
  status: 'active' | 'inactive';
20
- order_create_params: Record<string, any>;
21
18
  metadata: Record<string, any>;
22
19
  created_at: string;
23
20
  updated_at: string;
@@ -65,40 +62,11 @@ export default function VendorActions({ data, variant = 'compact', onChange }: V
65
62
  }
66
63
  };
67
64
 
68
- // 测试发货功能
69
- const onTestFulfillment = async () => {
70
- try {
71
- setState({ loading: true });
72
- const result = await api
73
- .post(`/api/vendors/${data.id}/test-fulfillment`, {
74
- productCode: 'test_product',
75
- })
76
- .then((res: any) => res.data);
77
-
78
- Toast.success(
79
- '发货测试成功!\n' +
80
- `订单号: ${result.fulfillmentResult?.orderId || 'N/A'}\n` +
81
- `状态: ${result.fulfillmentResult?.status || 'N/A'}\n` +
82
- `服务地址: ${result.fulfillmentResult?.serviceUrl || 'N/A'}`
83
- );
84
- onChange(state.action);
85
- } catch (err) {
86
- Toast.error(`发货测试失败: ${formatError(err)}`);
87
- } finally {
88
- setState({ loading: false, action: '' });
89
- }
90
- };
91
-
92
65
  return (
93
66
  <ClickBoundary>
94
67
  <Actions
95
68
  variant={variant}
96
69
  actions={[
97
- {
98
- label: '测试发货',
99
- handler: onTestFulfillment,
100
- color: 'success',
101
- },
102
70
  data.status === 'active'
103
71
  ? {
104
72
  label: t('admin.vendor.deactivate'),
@@ -602,6 +602,8 @@ export default flat({
602
602
  allowPromotionCodes: 'Allow promotion codes',
603
603
  requireCrossSell: 'Require cross sell products selected if eligible',
604
604
  includeFreeTrial: 'Include a free trial',
605
+ noStakeRequired: 'No stake required',
606
+ showProductFeatures: 'Show product features',
605
607
  freeTrialDaysPositive: 'Free trial days must be positive',
606
608
  includeCustomFields: 'Add custom fields',
607
609
  confirmPage: 'Confirmation Page',
@@ -890,10 +892,15 @@ export default flat({
890
892
  deactivateTip: 'Are you sure you want to deactivate vendor "{name}"?',
891
893
  name: 'Vendor Name',
892
894
  nameRequired: 'Vendor name is required',
893
- vendorKey: 'Vendor Type',
894
- vendorKeyRequired: 'Vendor type is required',
895
- vendorKeyHelp: 'Unique identifier for the vendor (e.g., launcher, did_names)',
895
+ vendorType: 'Vendor Type',
896
+ vendorTypeRequired: 'Vendor type is required',
897
+ launcher: 'Launcher',
898
+ vendorKey: 'Vendor Key',
899
+ vendorKeyRequired: 'Vendor key is required',
900
+ vendorKeyHelp: 'Unique identifier for the vendor',
896
901
  description: 'Description',
902
+ displayNameRequired: 'Display name is required',
903
+ displayNameHelp: 'This name will be displayed on the installation interface after successful payment',
897
904
  appUrl: 'App URL',
898
905
  appUrlRequired: 'App URL is required',
899
906
  appUrlInvalid: 'Please enter a valid URL starting with http:// or https://',
@@ -930,6 +937,9 @@ export default flat({
930
937
  empty: 'No subscriptions',
931
938
  viewAll: 'View all subscriptions',
932
939
  noActiveEmpty: 'You currently have no active subscriptions. You can choose to view your subscription history.',
940
+ includedServices: 'Included Services',
941
+ serviceHome: 'Visit Service Homepage',
942
+ serviceDashboard: 'Access Service Dashboard',
933
943
  attention: 'Past due subscriptions',
934
944
  product: 'Product',
935
945
  collectionMethod: 'Billing',
@@ -570,6 +570,8 @@ export default flat({
570
570
  requirePhoneNumber: '收集客户的电话号码',
571
571
  allowPromotionCodes: '允许促销代码',
572
572
  includeFreeTrial: '包含免费试用',
573
+ noStakeRequired: '无需质押',
574
+ showProductFeatures: '显示产品特性',
573
575
  freeTrialDaysPositive: '免费试用天数必须是正数',
574
576
  includeCustomFields: '添加自定义字段',
575
577
  requireCrossSell: '用户必须选择交叉销售的商品(如果有的话)',
@@ -868,10 +870,15 @@ export default flat({
868
870
  deactivateTip: '确定要停用供应商 "{name}" 吗?',
869
871
  name: '供应商名称',
870
872
  nameRequired: '供应商名称是必填项',
871
- vendorKey: '供应商类型',
872
- vendorKeyRequired: '供应商类型是必填项',
873
- vendorKeyHelp: '供应商的唯一标识符(如:launcher、did_names)',
873
+ vendorType: '供应商类型',
874
+ vendorTypeRequired: '供应商类型是必填项',
875
+ launcher: '启动器',
876
+ vendorKey: '供应商标识',
877
+ vendorKeyRequired: '供应商标识是必填项',
878
+ vendorKeyHelp: '供应商的唯一标识符',
874
879
  description: '描述',
880
+ displayNameRequired: '前台展示名称是必填项',
881
+ displayNameHelp: '此数据会展示到付款成功后的安装界面',
875
882
  appUrl: '应用地址',
876
883
  appUrlRequired: '应用地址是必填项',
877
884
  appUrlInvalid: '请输入以http://或https://开头的有效URL',
@@ -908,6 +915,9 @@ export default flat({
908
915
  empty: '没有订阅',
909
916
  viewAll: '查看历史订阅',
910
917
  noActiveEmpty: '您当前没有服务中的订阅,您可以选择查看历史订阅',
918
+ includedServices: '包含的服务',
919
+ serviceHome: '访问服务主页',
920
+ serviceDashboard: '访问服务管理后台',
911
921
  product: '产品',
912
922
  attention: '将过期的订阅',
913
923
  collectionMethod: '计费',