payment-kit 1.20.8 → 1.20.10
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/integrations/stripe/handlers/index.ts +10 -2
- package/api/src/libs/{vendor → vendor-util}/adapters/factory.ts +9 -5
- package/api/src/libs/{vendor → vendor-util}/adapters/launcher-adapter.ts +20 -18
- package/api/src/libs/{vendor → vendor-util}/adapters/types.ts +1 -4
- package/api/src/libs/{vendor → vendor-util}/fulfillment.ts +24 -127
- package/api/src/queues/payment.ts +1 -5
- package/api/src/queues/{vendor → vendors}/commission.ts +19 -18
- package/api/src/queues/{vendor → vendors}/fulfillment-coordinator.ts +35 -6
- package/api/src/queues/{vendor → vendors}/fulfillment.ts +2 -2
- package/api/src/queues/{vendor → vendors}/status-check.ts +13 -8
- package/api/src/routes/payment-links.ts +2 -1
- package/api/src/routes/products.ts +1 -0
- package/api/src/routes/vendor.ts +157 -216
- package/api/src/store/migrations/20250911-add-vendor-type.ts +26 -0
- package/api/src/store/migrations/20250916-add-vendor-did.ts +20 -0
- package/api/src/store/models/payout.ts +2 -2
- package/api/src/store/models/product-vendor.ts +11 -24
- package/api/src/store/models/product.ts +2 -0
- package/blocklet.yml +1 -1
- package/doc/vendor_fulfillment_system.md +1 -1
- package/package.json +5 -5
- 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 +16 -3
- package/src/locales/zh.tsx +18 -5
- package/src/pages/admin/products/links/create.tsx +2 -0
- package/src/pages/admin/products/vendors/create.tsx +140 -190
- package/src/pages/admin/products/vendors/index.tsx +14 -22
- package/src/pages/customer/subscription/detail.tsx +26 -11
package/blocklet.yml
CHANGED
|
@@ -790,7 +790,7 @@ async function requestReturnFromSingleVendor(
|
|
|
790
790
|
): Promise<void> {
|
|
791
791
|
try {
|
|
792
792
|
// 1. 获取供应商适配器
|
|
793
|
-
const vendorAdapter = VendorFulfillmentService.getVendorAdapter(vendor.vendor_key);
|
|
793
|
+
const vendorAdapter = await VendorFulfillmentService.getVendorAdapter(vendor.vendor_key);
|
|
794
794
|
|
|
795
795
|
// 2. 调用供应商的退货请求方法
|
|
796
796
|
const returnResult = await vendorAdapter.requestReturn({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "payment-kit",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.10",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev": "blocklet dev --open",
|
|
6
6
|
"lint": "tsc --noEmit && eslint src api/src --ext .mjs,.js,.jsx,.ts,.tsx",
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"@blocklet/error": "^0.2.5",
|
|
57
57
|
"@blocklet/js-sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
|
|
58
58
|
"@blocklet/logger": "^1.16.52-beta-20250912-112002-e3499e9c",
|
|
59
|
-
"@blocklet/payment-react": "1.20.
|
|
60
|
-
"@blocklet/payment-vendor": "1.20.
|
|
59
|
+
"@blocklet/payment-react": "1.20.10",
|
|
60
|
+
"@blocklet/payment-vendor": "1.20.10",
|
|
61
61
|
"@blocklet/sdk": "^1.16.52-beta-20250912-112002-e3499e9c",
|
|
62
62
|
"@blocklet/ui-react": "^3.1.40",
|
|
63
63
|
"@blocklet/uploader": "^0.2.10",
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"devDependencies": {
|
|
127
127
|
"@abtnode/types": "^1.16.52-beta-20250912-112002-e3499e9c",
|
|
128
128
|
"@arcblock/eslint-config-ts": "^0.3.3",
|
|
129
|
-
"@blocklet/payment-types": "1.20.
|
|
129
|
+
"@blocklet/payment-types": "1.20.10",
|
|
130
130
|
"@types/cookie-parser": "^1.4.9",
|
|
131
131
|
"@types/cors": "^2.8.19",
|
|
132
132
|
"@types/debug": "^4.1.12",
|
|
@@ -173,5 +173,5 @@
|
|
|
173
173
|
"parser": "typescript"
|
|
174
174
|
}
|
|
175
175
|
},
|
|
176
|
-
"gitHead": "
|
|
176
|
+
"gitHead": "1659d63120ced92167ec8681e51db96801b910ec"
|
|
177
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,14 +892,22 @@ 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://',
|
|
900
907
|
appUrlHelp: 'The base URL of the vendor application',
|
|
908
|
+
vendorDid: 'Vendor DID',
|
|
909
|
+
vendorDidInvalid: 'Please enter a valid DID',
|
|
910
|
+
vendorDidHelp: 'Optional DID address for the vendor',
|
|
901
911
|
webhookPath: 'Webhook Path',
|
|
902
912
|
webhookPathInvalid: 'Please enter a valid path starting with /',
|
|
903
913
|
webhookPathHelp: 'Optional webhook callback path (e.g., /webhooks/status)',
|
|
@@ -930,6 +940,9 @@ export default flat({
|
|
|
930
940
|
empty: 'No subscriptions',
|
|
931
941
|
viewAll: 'View all subscriptions',
|
|
932
942
|
noActiveEmpty: 'You currently have no active subscriptions. You can choose to view your subscription history.',
|
|
943
|
+
includedServices: 'Included Services',
|
|
944
|
+
serviceHome: 'Visit Service Homepage',
|
|
945
|
+
serviceDashboard: 'Access Service Dashboard',
|
|
933
946
|
attention: 'Past due subscriptions',
|
|
934
947
|
product: 'Product',
|
|
935
948
|
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,14 +870,22 @@ 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
|
-
appUrlInvalid: '
|
|
878
|
-
appUrlHelp: '供应商应用的基础URL',
|
|
884
|
+
appUrlInvalid: '请输入有效的 URL,以 http:// 或 https:// 开头',
|
|
885
|
+
appUrlHelp: '供应商应用的基础 URL 地址',
|
|
886
|
+
vendorDid: '供应商 DID',
|
|
887
|
+
vendorDidInvalid: '请输入有效的 DID',
|
|
888
|
+
vendorDidHelp: '供应商的可选 DID 地址',
|
|
879
889
|
webhookPath: 'Webhook路径',
|
|
880
890
|
webhookPathInvalid: '请输入以/开头的有效路径',
|
|
881
891
|
webhookPathHelp: '可选的webhook回调路径(如:/webhooks/status)',
|
|
@@ -908,6 +918,9 @@ export default flat({
|
|
|
908
918
|
empty: '没有订阅',
|
|
909
919
|
viewAll: '查看历史订阅',
|
|
910
920
|
noActiveEmpty: '您当前没有服务中的订阅,您可以选择查看历史订阅',
|
|
921
|
+
includedServices: '包含的服务',
|
|
922
|
+
serviceHome: '访问服务主页',
|
|
923
|
+
serviceDashboard: '访问服务管理后台',
|
|
911
924
|
product: '产品',
|
|
912
925
|
attention: '将过期的订阅',
|
|
913
926
|
collectionMethod: '计费',
|
|
@@ -66,6 +66,7 @@ export default function CreatePaymentLink() {
|
|
|
66
66
|
subscription_data: {
|
|
67
67
|
description: '',
|
|
68
68
|
trial_period_days: 0,
|
|
69
|
+
no_stake: false,
|
|
69
70
|
},
|
|
70
71
|
nft_mint_settings: {
|
|
71
72
|
enabled: false,
|
|
@@ -86,6 +87,7 @@ export default function CreatePaymentLink() {
|
|
|
86
87
|
'billing_address_collection',
|
|
87
88
|
'phone_number_collection',
|
|
88
89
|
'currency_id',
|
|
90
|
+
'metadata',
|
|
89
91
|
]);
|
|
90
92
|
|
|
91
93
|
useEffect(() => {
|