Skip to content

Commit c0ce509

Browse files
authored
Add virtual-delivery docs and assets (#565)
1 parent 7bbcc48 commit c0ce509

18 files changed

+543
-17
lines changed

docs/user-guide/shop/orders.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ description: 商城订单管理相关使用文档。
2323

2424
:::info 提示
2525
1. 目前仅支持填写物流单号等信息,目前没有对接物流查询平台,所以需要用户手动根据物流单号查询物流信息。
26-
2. 目前仅支持实体物流发货,虚拟产品暂不支持,后续会提供虚拟物品池、付费后下载文件、Webhook、自动发货等功能
26+
2. 虚拟产品发货可参考:[虚拟交付](./virtual-delivery.mdx)
2727
:::

docs/user-guide/shop/products.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ import TabItem from '@theme/TabItem';
3131
2. 虚拟:不需要实体配送,使用线上发货的产品类型
3232
3. 链接:直接跳转到第三方链接的产品类型
3333

34+
:::info 提示
35+
虚拟产品支持文件资源下载、卡密发货,可参考 [虚拟交付](./virtual-delivery.mdx) 进行发货配置。
36+
:::
37+
3438
#### 属性配置
3539

3640
属性的作用是为产品添加一些额外的信息,比如颜色、尺寸、型号等,同时也可以用于生成产品规格。
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
---
2+
title: 虚拟交付
3+
description: 商城虚拟交付相关使用文档。
4+
---
5+
6+
在 Halo 2.23 中,我们完善了虚拟产品的发货功能,目前支持文件资源下载、卡密发货。
7+
8+
## 创建产品
9+
10+
完整的创建产品流程可参考 [产品管理](./products.mdx) 文档,需要注意的是,如果产品要支持虚拟交付,需要在产品类型中选择 **虚拟** 产品类型。
11+
12+
![](/img/user-guide/shop/products-create-virtual.png)
13+
14+
## 设置文件资源下载
15+
16+
创建完产品之后,我们需要进入 **产品 -> 虚拟交付** 页面,然后选择刚刚创建的产品。
17+
18+
![](/img/user-guide/shop/virtual-delivery-products.png)
19+
20+
进入设置页面之后,就可以看到 **资源下载** 的设置页面,我们可以点击右上方的 **添加** 按钮来添加文件。
21+
22+
![](/img/user-guide/shop/virtual-delivery-resource-add.png)
23+
24+
- 资源类型:支持附件和静态 URL
25+
- 资源名称:为客户显示的文件名称
26+
- 资源描述:可以填写文件的描述信息
27+
- 附件(当类型为附件时):可以点击 `+` 按钮选择已上传的附件或者上传新的附件
28+
- 资源地址(当类型为静态 URL 时):可以填写文件的 URL 地址
29+
30+
填写完成之后,点击 **保存** 按钮即可。
31+
32+
当文件添加完成之后,我们还需要手动启用自动发货,点击右上角的 **发货配置** 按钮,勾选启用即可。
33+
34+
![](/img/user-guide/shop/virtual-delivery-resource-config.png)
35+
36+
## 设置卡密发货
37+
38+
进入虚拟交付设置页面之后,选择 **卡密资产** 选项卡,就可以看到 **卡密资产** 的设置页面。
39+
40+
![](/img/user-guide/shop/virtual-delivery-cdks.png)
41+
42+
我们可以点击右上方的 **添加** 按钮来添加卡密。
43+
44+
![](/img/user-guide/shop/virtual-delivery-cdk-add.png)
45+
46+
- 卡密:卡密内容,支持批量添加,一行一个
47+
- 过期时间:卡密过期时间
48+
- 备注:可以填写卡密的备注信息
49+
50+
填写完成之后点击 **添加** 按钮即可。
51+
52+
当卡密添加完成之后,我们还需要手动启用自动发货,点击右上角的 **发货配置** 按钮,勾选启用即可。
53+
54+
![](/img/user-guide/shop/virtual-delivery-cdk-config.png)
55+
56+
此外,在卡密发货配置界面中,还支持配置 **远程调用配置**,支持通过三方 API 来补货或者动态生成卡密并发货。
57+
58+
定义:
59+
60+
- 预获取:优先从卡密列表中选择并发货,在库存不足时才调用 API 进行补货
61+
- 动态获取:每次发货时都调用 API 获取卡密并发货
62+
63+
配置说明:
64+
65+
- 类型:支持 **预获取****动态获取**
66+
- 补货配置(当类型为预获取时)
67+
- API 地址:补货 API 地址
68+
- 认证 Token:补货 API 认证 Token,用于对 API 请求进行认证
69+
- 阈值:可用卡密数量低于此值时触发补货
70+
- 每次获取的卡密数量:每次调用 API 获取的卡密数量
71+
- 服务器配置(当类型为动态获取时)
72+
- API 地址:远程生成 API 地址
73+
- 认证 Token:API 认证 Token,用于对 API 请求进行认证
74+
75+
需要注意的是,API 需要自行实现并适配 Halo 的接口标准,可以参考下方的 [API 规范](#api-规范)
76+
77+
## 订单相关
78+
79+
为产品配置好虚拟交付之后,当客户下单并支付时,系统会自动为订单进行发货流程,可以在控制台的订单详情中看到发货状态。
80+
81+
![](/img/user-guide/shop/virtual-delivery-console-order.png)
82+
83+
客户也可以在订单页面看到已发货的虚拟物品。
84+
85+
![](/img/user-guide/shop/virtual-delivery-uc-order-1.png)
86+
87+
![](/img/user-guide/shop/virtual-delivery-uc-order-2.png)
88+
89+
## API 规范
90+
91+
### 卡密远程生成 API 规范
92+
93+
用户下单后,Halo 会向配置的远程地址发起请求,由你的服务实时生成或分配卡密。
94+
95+
#### 请求
96+
97+
```
98+
POST <你配置的远程生成地址>
99+
Content-Type: application/json
100+
Authorization: Bearer <token> // 配置了 token 时附带
101+
```
102+
103+
**请求体**
104+
105+
```json
106+
{
107+
"orderItem": {
108+
"id": 10001,
109+
"orderId": 90001,
110+
"productId": 80001,
111+
"quantity": 2,
112+
"itemTitle": "年度会员兑换码",
113+
"productVariantSnapshot": {
114+
"id": 123,
115+
"name": "默认规格"
116+
}
117+
},
118+
"dryRun": false
119+
}
120+
```
121+
122+
| 字段 | 类型 | 必填 | 说明 |
123+
| ---------------------------------- | ------- | ---- | ------------------------- |
124+
| `orderItem.id` | long || 订单项 ID(可用作幂等键) |
125+
| `orderItem.orderId` | long || 订单 ID |
126+
| `orderItem.productId` | long || 商品 ID |
127+
| `orderItem.quantity` | int || 需要发放的卡密数量 |
128+
| `orderItem.itemTitle` | string || 订单项标题 |
129+
| `orderItem.productVariantSnapshot` | object || 规格快照 |
130+
| `dryRun` | boolean || 当前固定为 `false` |
131+
132+
#### 响应
133+
134+
返回卡密列表,所有卡密的 `capacity` 之和须不小于 `orderItem.quantity`
135+
136+
```json
137+
[
138+
{
139+
"code": "ABCD-EFGH-IJKL",
140+
"secret": "S3cr3t-1",
141+
"expireAt": "2026-12-31T23:59:59Z",
142+
"capacity": 1
143+
}
144+
]
145+
```
146+
147+
| 字段 | 类型 | 必填 | 说明 |
148+
| ---------- | ------ | ---- | ------------------------- |
149+
| `code` | string || 卡密(同一响应内唯一) |
150+
| `secret` | string || 附加验证信息 |
151+
| `expireAt` | string || 过期时间(UTC,ISO-8601) |
152+
| `capacity` | int || 可用次数,默认为 `1` |
153+
154+
:::warning 注意
155+
响应不可为空列表,否则触发卡密发放失败。建议以 `orderItem.id` 作为幂等键,避免重复请求导致重复发放。
156+
:::
157+
158+
#### 错误码
159+
160+
建议使用非 `2xx` 状态码表达失败,并返回结构化错误:
161+
162+
```json
163+
{
164+
"code": "CDK_OUT_OF_STOCK",
165+
"message": "No enough CDKs available",
166+
"requestId": "f38db1c2-2f7a-4dc3-b5a8-2d0012345678"
167+
}
168+
```
169+
170+
推荐错误码:
171+
172+
| HTTP 状态码 | 错误码 | 含义 | 是否可重试 |
173+
| ----------- | --------------------- | ---------------------- | ---------- |
174+
| `400` | `INVALID_REQUEST` | 请求字段不合法 ||
175+
| `401` | `UNAUTHORIZED` | 鉴权失败 ||
176+
| `403` | `FORBIDDEN` | 调用被拒绝 ||
177+
| `404` | `NOT_FOUND` | 路由不存在 ||
178+
| `409` | `IDEMPOTENT_CONFLICT` | 幂等键冲突且参数不一致 ||
179+
| `422` | `CDK_OUT_OF_STOCK` | 库存不足 | 视业务而定 |
180+
| `429` | `RATE_LIMITED` | 触发限流 ||
181+
| `500` | `INTERNAL_ERROR` | 远程服务内部错误 ||
182+
| `503` | `SERVICE_UNAVAILABLE` | 服务不可用 ||
183+
184+
### 卡密自动补货 API 规范
185+
186+
当密钥库存低于阈值时,Halo 会自动向配置的补货地址发起补货请求。
187+
188+
#### 请求
189+
190+
```
191+
POST <你配置的补货地址>
192+
Content-Type: application/json
193+
Authorization: Bearer <token> // 配置了 token 时附带
194+
```
195+
196+
**请求体**
197+
198+
```json
199+
{
200+
"productVariantId": 123,
201+
"quantity": 50
202+
}
203+
```
204+
205+
| 字段 | 类型 | 必填 | 说明 |
206+
| ------------------ | ---- | ---- | --------------------- |
207+
| `productVariantId` | long || 需要补货的商品规格 ID |
208+
| `quantity` | int || 请求补充的卡密数量 |
209+
210+
#### 响应
211+
212+
返回卡密列表,实际返回数量可与请求数量不一致。
213+
214+
```json
215+
[
216+
{
217+
"code": "ABCD-EFGH-IJKL",
218+
"secret": "S3cr3t-1",
219+
"expireAt": "2026-12-31T23:59:59Z",
220+
"capacity": 1
221+
}
222+
]
223+
```
224+
225+
| 字段 | 类型 | 必填 | 说明 |
226+
| ---------- | ------ | ---- | ------------------------- |
227+
| `code` | string || 卡密(同一响应内唯一) |
228+
| `secret` | string || 附加验证信息 |
229+
| `expireAt` | string || 过期时间(UTC,ISO-8601) |
230+
| `capacity` | int || 可用次数,默认为 `1` |
231+
232+
:::warning 注意
233+
响应不可为空列表,否则视为补货失败。补货失败不影响当前订单的卡密发放。
234+
:::
235+
236+
#### 错误码
237+
238+
建议使用非 `2xx` 状态码表达失败,并返回结构化错误:
239+
240+
```json
241+
{
242+
"code": "CDK_OUT_OF_STOCK",
243+
"message": "No enough CDKs available",
244+
"requestId": "f38db1c2-2f7a-4dc3-b5a8-2d0012345678"
245+
}
246+
```
247+
248+
推荐错误码:
249+
250+
| HTTP 状态码 | 错误码 | 含义 | 是否可重试 |
251+
| ----------- | --------------------- | ---------------------- | ---------- |
252+
| `400` | `INVALID_REQUEST` | 请求字段不合法 ||
253+
| `401` | `UNAUTHORIZED` | 鉴权失败 ||
254+
| `403` | `FORBIDDEN` | 调用被拒绝 ||
255+
| `404` | `NOT_FOUND` | 路由不存在 ||
256+
| `409` | `IDEMPOTENT_CONFLICT` | 幂等键冲突且参数不一致 ||
257+
| `422` | `CDK_OUT_OF_STOCK` | 库存不足 | 视业务而定 |
258+
| `429` | `RATE_LIMITED` | 触发限流 ||
259+
| `500` | `INTERNAL_ERROR` | 远程服务内部错误 ||
260+
| `503` | `SERVICE_UNAVAILABLE` | 服务不可用 ||

i18n/zh-Hans/code.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
"message": "页面已崩溃。",
1010
"description": "The title of the fallback page when the page crashed"
1111
},
12+
"theme.BackToTopButton.buttonAriaLabel": {
13+
"message": "回到顶部",
14+
"description": "The ARIA label for the back to top button"
15+
},
1216
"theme.blog.archive.title": {
1317
"message": "历史博文",
1418
"description": "The page & hero title of the blog archive page"
@@ -17,10 +21,6 @@
1721
"message": "历史博文",
1822
"description": "The page & hero description of the blog archive page"
1923
},
20-
"theme.BackToTopButton.buttonAriaLabel": {
21-
"message": "回到顶部",
22-
"description": "The ARIA label for the back to top button"
23-
},
2424
"theme.blog.paginator.navAriaLabel": {
2525
"message": "博文列表分页导航",
2626
"description": "The ARIA label for the blog pagination"
@@ -144,10 +144,6 @@
144144
"message": "标签:",
145145
"description": "The label alongside a tag list"
146146
},
147-
"theme.AnnouncementBar.closeButtonAriaLabel": {
148-
"message": "关闭",
149-
"description": "The ARIA label for close button of announcement bar"
150-
},
151147
"theme.admonition.caution": {
152148
"message": "警告",
153149
"description": "The default label used for the Caution admonition (:::caution)"
@@ -176,6 +172,10 @@
176172
"message": "最近博文导航",
177173
"description": "The ARIA label for recent posts in the blog sidebar"
178174
},
175+
"theme.AnnouncementBar.closeButtonAriaLabel": {
176+
"message": "关闭",
177+
"description": "The ARIA label for close button of announcement bar"
178+
},
179179
"theme.DocSidebarItem.expandCategoryAriaLabel": {
180180
"message": "展开侧边栏分类 '{label}'",
181181
"description": "The ARIA label to expand the sidebar category"
@@ -204,6 +204,10 @@
204204
"message": "本页总览",
205205
"description": "The label used by the button on the collapsible TOC component"
206206
},
207+
"theme.navbar.mobileLanguageDropdown.label": {
208+
"message": "选择语言",
209+
"description": "The label for the mobile language switcher dropdown"
210+
},
207211
"theme.blog.post.readMore": {
208212
"message": "阅读更多",
209213
"description": "The label used in blog post item excerpts to link to full blog posts"
@@ -212,10 +216,6 @@
212216
"message": "阅读 {title} 的全文",
213217
"description": "The ARIA label for the link to full blog posts from excerpts"
214218
},
215-
"theme.navbar.mobileLanguageDropdown.label": {
216-
"message": "选择语言",
217-
"description": "The label for the mobile language switcher dropdown"
218-
},
219219
"theme.blog.post.readingTime.plurals": {
220220
"message": "阅读需 {readingTime} 分钟",
221221
"description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)"
@@ -228,6 +228,10 @@
228228
"message": "文档侧边栏",
229229
"description": "The ARIA label for the sidebar navigation"
230230
},
231+
"theme.docs.breadcrumbs.home": {
232+
"message": "主页面",
233+
"description": "The ARIA label for the home page in the breadcrumbs"
234+
},
231235
"theme.docs.sidebar.collapseButtonTitle": {
232236
"message": "收起侧边栏",
233237
"description": "The title attribute for collapse button of doc sidebar"
@@ -236,10 +240,6 @@
236240
"message": "收起侧边栏",
237241
"description": "The title attribute for collapse button of doc sidebar"
238242
},
239-
"theme.docs.breadcrumbs.home": {
240-
"message": "主页面",
241-
"description": "The ARIA label for the home page in the breadcrumbs"
242-
},
243243
"theme.CodeBlock.copy": {
244244
"message": "复制",
245245
"description": "The copy button label on code blocks"

sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ module.exports = {
119119
"user-guide/shop/sales-channels",
120120
"user-guide/shop/products",
121121
"user-guide/shop/orders",
122+
"user-guide/shop/virtual-delivery",
122123
"user-guide/shop/theme-dev",
123124
],
124125
},
118 KB
Loading
110 KB
Loading
123 KB
Loading
137 KB
Loading
110 KB
Loading

0 commit comments

Comments
 (0)