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.
- package/api/src/crons/index.ts +1 -1
- package/api/src/index.ts +2 -2
- package/api/src/libs/{vendor → vendor-util}/adapters/factory.ts +9 -5
- package/api/src/libs/{vendor → vendor-util}/adapters/launcher-adapter.ts +3 -8
- package/api/src/libs/{vendor → vendor-util}/adapters/types.ts +1 -4
- package/api/src/libs/{vendor → vendor-util}/fulfillment.ts +11 -8
- package/api/src/queues/{vendor → vendors}/commission.ts +4 -5
- package/api/src/queues/{vendor → vendors}/fulfillment-coordinator.ts +33 -4
- package/api/src/queues/{vendor → vendors}/fulfillment.ts +2 -2
- package/api/src/queues/{vendor → vendors}/status-check.ts +2 -2
- package/api/src/routes/payment-links.ts +2 -1
- package/api/src/routes/products.ts +1 -0
- package/api/src/routes/vendor.ts +135 -213
- package/api/src/store/migrations/20250911-add-vendor-type.ts +26 -0
- package/api/src/store/models/product-vendor.ts +6 -24
- package/api/src/store/models/product.ts +1 -0
- package/blocklet.yml +1 -1
- package/doc/vendor_fulfillment_system.md +1 -1
- package/package.json +23 -22
- package/src/components/metadata/form.tsx +12 -19
- package/src/components/payment-link/before-pay.tsx +40 -0
- package/src/components/product/vendor-config.tsx +4 -11
- package/src/components/subscription/description.tsx +1 -6
- package/src/components/subscription/portal/list.tsx +82 -6
- package/src/components/subscription/vendor-service-list.tsx +128 -0
- package/src/components/vendor/actions.tsx +1 -33
- package/src/locales/en.tsx +13 -3
- package/src/locales/zh.tsx +13 -3
- package/src/pages/admin/products/links/create.tsx +2 -0
- package/src/pages/admin/products/vendors/create.tsx +108 -194
- package/src/pages/admin/products/vendors/index.tsx +14 -22
- 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.
|
|
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-
|
|
47
|
-
"@arcblock/did": "^1.24.
|
|
48
|
-
"@arcblock/did-connect-react": "^3.1.
|
|
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.
|
|
51
|
-
"@arcblock/jwt": "^1.24.
|
|
52
|
-
"@arcblock/ux": "^3.1.
|
|
53
|
-
"@arcblock/validator": "^1.24.
|
|
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-
|
|
57
|
-
"@blocklet/logger": "^1.16.52-beta-
|
|
58
|
-
"@blocklet/payment-react": "1.20.
|
|
59
|
-
"@blocklet/payment-vendor": "1.20.
|
|
60
|
-
"@blocklet/sdk": "^1.16.52-beta-
|
|
61
|
-
"@blocklet/ui-react": "^3.1.
|
|
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.
|
|
69
|
-
"@ocap/client": "^1.24.
|
|
70
|
-
"@ocap/mcrypto": "^1.24.
|
|
71
|
-
"@ocap/util": "^1.24.
|
|
72
|
-
"@ocap/wallet": "^1.24.
|
|
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-
|
|
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.
|
|
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": "
|
|
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',
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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'),
|
package/src/locales/en.tsx
CHANGED
|
@@ -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
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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',
|
package/src/locales/zh.tsx
CHANGED
|
@@ -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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
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: '计费',
|