398 lines
10 KiB
Markdown
398 lines
10 KiB
Markdown
|
|
# VR票务插件 C端 API 文档
|
|||
|
|
|
|||
|
|
> **版本**: 1.1.0
|
|||
|
|
> **最后更新**: 2026-05-28
|
|||
|
|
|
|||
|
|
本文档描述了 VR 票务插件(`vr_ticket`)的 C 端 UniApp API,涵盖票夹、核销、核销记录三类接口,适用于移动端用户及授权核销员使用。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、概述
|
|||
|
|
|
|||
|
|
### 1.1 核心机制
|
|||
|
|
|
|||
|
|
| 机制 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| **动态 QR Payload** | QR 内容含 HMAC-SHA256 签名,默认 **30 分钟** 有效期,前端应动态刷新 |
|
|||
|
|
| **悲观锁防并发** | 核销接口使用 `FOR UPDATE` 事务锁,杜绝重复核销 |
|
|||
|
|
| **双重码格式** | 长码(UUID v4)与短码(Base36 + Feistel 混淆),API 自动识别 |
|
|||
|
|
| **核销员白名单** | C 端用户必须存在于 `vr_verifiers` 表且 `status=1` 才可执行核销操作 |
|
|||
|
|
|
|||
|
|
### 1.2 响应标准格式
|
|||
|
|
|
|||
|
|
所有 API 均返回以下 JSON 结构:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 0,
|
|||
|
|
"msg": "success",
|
|||
|
|
"data": { ... }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、认证机制
|
|||
|
|
|
|||
|
|
### 2.1 请求头认证
|
|||
|
|
|
|||
|
|
所有 C 端 API 均需携带用户身份令牌。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Header Key: X-Token
|
|||
|
|
Header Value: {user_token}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
兜底方案(按优先级):
|
|||
|
|
1. `X-Token` / `Authorization: Bearer {token}`(App 登录)
|
|||
|
|
2. `user_info` Cookie 解码(Web 登录)
|
|||
|
|
3. Session(传统页面)
|
|||
|
|
|
|||
|
|
若均未找到,返回:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 401,
|
|||
|
|
"msg": "请先登录",
|
|||
|
|
"data": []
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.2 核销员身份鉴权
|
|||
|
|
|
|||
|
|
以下 API 除了要求 C 端登录外,还需验证用户是否为授权核销员:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
// 未授权(非核销员)
|
|||
|
|
{
|
|||
|
|
"code": -403,
|
|||
|
|
"msg": "你不是授权核销员,无权核销",
|
|||
|
|
"data": []
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
核销员身份由 `vr_verifiers` 表决定,管理员可在 ShopXO 后台添加。普通用户(非核销员)可正常使用票夹 API,但不能执行核销。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、通用错误码
|
|||
|
|
|
|||
|
|
| code | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| `0` | 成功 |
|
|||
|
|
| `-1` | 通用失败 / 参数错误 |
|
|||
|
|
| `-2` | 该票已核销 |
|
|||
|
|
| `-3` | 该票已退款 |
|
|||
|
|
| `-4` | QR 数据异常 |
|
|||
|
|
| `-403` | 无核销权限(不是授权核销员) |
|
|||
|
|
| `-404` | 票不存在或无权访问 |
|
|||
|
|
| `-999` | 系统异常(事务失败) |
|
|||
|
|
| `401` | 未登录 / 未授权 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、票夹 API
|
|||
|
|
|
|||
|
|
> **基础路由**: `/api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction={action}`
|
|||
|
|
> **认证**: C 端登录态(无需核销员身份)
|
|||
|
|
|
|||
|
|
### 4.1 获取用户票列表
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET ...&pluginsaction=list
|
|||
|
|
GET ...&pluginsaction=tickets (别名)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**成功响应 `data`**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"tickets": [
|
|||
|
|
{
|
|||
|
|
"id": 123,
|
|||
|
|
"goods_id": 456,
|
|||
|
|
"goods_title": "周杰伦演唱会-北京站",
|
|||
|
|
"goods_image": "https://...jpg",
|
|||
|
|
"seat_info": "2026-06-01 20:00|国家体育馆|主厅|A区|A1",
|
|||
|
|
"seat_number": "A1",
|
|||
|
|
"session_time": "2026-06-01 20:00",
|
|||
|
|
"venue_name": "国家体育馆",
|
|||
|
|
"real_name": "张三",
|
|||
|
|
"phone": "138****5678",
|
|||
|
|
"verify_status": 0,
|
|||
|
|
"issued_at": 1716307200,
|
|||
|
|
"short_code": "000ca1b2"
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"count": 1
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `verify_status` | int | `0`=未核销 `1`=已核销 `2`=已退款 |
|
|||
|
|
| `short_code` | string | 9位短码,可供核销员扫码 |
|
|||
|
|
| `seat_info` | string | 完整 5 维坐席信息,`场次\|场馆\|演播室\|分区\|座位号` |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.2 获取票详情(含 QR Payload)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET ...&pluginsaction=detail&id={ticket_id}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**参数**:
|
|||
|
|
|
|||
|
|
| 参数 | 类型 | 必填 | 说明 |
|
|||
|
|
|------|------|------|------|
|
|||
|
|
| `id` | int | ✅ | 票 ID |
|
|||
|
|
|
|||
|
|
**成功响应 `data`**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"ticket": {
|
|||
|
|
"id": 123,
|
|||
|
|
"goods_id": 456,
|
|||
|
|
"goods_title": "周杰伦演唱会-北京站",
|
|||
|
|
"goods_image": "https://...jpg",
|
|||
|
|
"seat_info": "2026-06-01 20:00|国家体育馆|主厅|A区|A1",
|
|||
|
|
"session_time": "2026-06-01 20:00",
|
|||
|
|
"venue_name": "国家体育馆",
|
|||
|
|
"seat_number": "A1",
|
|||
|
|
"real_name": "张三",
|
|||
|
|
"phone": "138****5678",
|
|||
|
|
"verify_status": 0,
|
|||
|
|
"verify_time": 0,
|
|||
|
|
"issued_at": 1716307200,
|
|||
|
|
"short_code": "000ca1b2",
|
|||
|
|
"qr_payload": "eyJpZCI6MTIzLCJnIjo0NTYsImNvZGUiOiI4OTM1...",
|
|||
|
|
"qr_expires_at": 1716309000,
|
|||
|
|
"qr_expires_in": 1800
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `qr_payload` | string | 二维码内容,含 HMAC-SHA256 签名,有效期 30 分钟 |
|
|||
|
|
| `qr_expires_at` | int | 过期时间戳(秒) |
|
|||
|
|
| `qr_expires_in` | int | 剩余有效期(秒),固定 1800 |
|
|||
|
|
|
|||
|
|
**失败响应**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
// 票不存在或无权访问
|
|||
|
|
{ "code": -404, "msg": "票不存在或无权访问", "data": [] }
|
|||
|
|
|
|||
|
|
// 票已核销(不返回 QR)
|
|||
|
|
{ "code": -2, "msg": "该票已核销", "data": [] }
|
|||
|
|
|
|||
|
|
// 票已退款
|
|||
|
|
{ "code": -3, "msg": "该票已退款", "data": [] }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.3 强制刷新二维码
|
|||
|
|
|
|||
|
|
重新生成 QR Payload,重置 30 分钟有效期。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET ...&pluginsaction=refreshQr&id={ticket_id}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**参数**: 同 `detail`
|
|||
|
|
|
|||
|
|
**成功响应**: 与 `detail` 完全一致(`qr_payload` 为新生成)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4.4 检测核销员身份
|
|||
|
|
|
|||
|
|
轻量接口,用于前端快速判断是否展示核销员入口。
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET ...&pluginsaction=checkVerifier
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**成功响应 `data`**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"is_verifier": true,
|
|||
|
|
"verifier_id": 3,
|
|||
|
|
"verifier_name": "张三"
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `is_verifier` | bool | 是否为授权核销员 |
|
|||
|
|
| `verifier_id` | int | 核销员 ID,未授权时为 `0` |
|
|||
|
|
| `verifier_name` | string | 核销员名称,未授权时为空字符串 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、核销 API(UniApp 授权核销员专用)
|
|||
|
|
|
|||
|
|
> **基础路由**: `/api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=verify`
|
|||
|
|
> **认证**: C 端登录态 **+** 必须是 `vr_verifiers` 表中 `status=1` 的授权核销员
|
|||
|
|
|
|||
|
|
### 5.1 扫码核销
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST ...&pluginsaction=verify
|
|||
|
|
Content-Type: application/x-www-form-urlencoded
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**请求参数**:
|
|||
|
|
|
|||
|
|
| 参数 | 类型 | 必填 | 说明 |
|
|||
|
|
|------|------|------|------|
|
|||
|
|
| `ticket_code` | string | ✅ | 扫码得到的票码(短码或 UUID) |
|
|||
|
|
|
|||
|
|
**自动识别规则**:
|
|||
|
|
- 长度 < 20 且不含连字符 `-` → 短码(Base36 + Feistel 混淆)
|
|||
|
|
- 包含连字符 `-` → UUID v4 长码
|
|||
|
|
|
|||
|
|
**成功响应** (`code: 0`):
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"code": 0,
|
|||
|
|
"msg": "核销成功",
|
|||
|
|
"data": {
|
|||
|
|
"seat_info": "2026-06-01 20:00|国家体育馆|主厅|A区|A1",
|
|||
|
|
"real_name": "张三",
|
|||
|
|
"goods_name": "周杰伦演唱会-北京站"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**失败响应**:
|
|||
|
|
|
|||
|
|
| code | msg | 场景 |
|
|||
|
|
|------|-----|------|
|
|||
|
|
| `-1` | `票码不存在` | 短码解码失败或票不存在 |
|
|||
|
|
| `-2` | `该票已核销` | 重复核销 |
|
|||
|
|
| `-3` | `该票已退款` | 票已退款 |
|
|||
|
|
| `-403` | `你不是授权核销员,无权核销` | C 端用户不在核销员白名单 |
|
|||
|
|
| `401` | `请先登录` | 未登录 |
|
|||
|
|
| `-999` | `核销失败,请重试` | 数据库事务异常 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、核销记录 API(UniApp 授权核销员专用)
|
|||
|
|
|
|||
|
|
> **基础路由**: `/api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=ticket&pluginsaction=myVerifications`
|
|||
|
|
> **认证**: C 端登录态 **+** 必须是 `vr_verifiers` 表中 `status=1` 的授权核销员
|
|||
|
|
|
|||
|
|
### 6.1 我的核销记录
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
GET ...&pluginsaction=myVerifications
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**请求参数**:
|
|||
|
|
|
|||
|
|
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
|||
|
|
|------|------|------|--------|------|
|
|||
|
|
| `page` | int | ❌ | `1` | 页码 |
|
|||
|
|
| `page_size` | int | ❌ | `20` | 每页条数(最大 100) |
|
|||
|
|
|
|||
|
|
**成功响应 `data`**:
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"list": [
|
|||
|
|
{
|
|||
|
|
"id": 1,
|
|||
|
|
"ticket_id": 123,
|
|||
|
|
"ticket_code": "8935b3a3-a7b4-4e3d-8c1f-9b7e2a6f5d4c",
|
|||
|
|
"seat_info": "2026-06-01 20:00|国家体育馆|主厅|A区|A1",
|
|||
|
|
"real_name": "张三",
|
|||
|
|
"goods_title": "周杰伦演唱会-北京站",
|
|||
|
|
"created_at": 1716307200
|
|||
|
|
}
|
|||
|
|
],
|
|||
|
|
"total": 50,
|
|||
|
|
"page": 1,
|
|||
|
|
"page_size": 20,
|
|||
|
|
"pages": 3
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**失败响应**:
|
|||
|
|
|
|||
|
|
| code | msg | 场景 |
|
|||
|
|
|------|-----|------|
|
|||
|
|
| `-403` | `你不是授权核销员` | 非授权核销员 |
|
|||
|
|
| `401` | `请先登录` | 未登录 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、QR 二维码前端最佳实践
|
|||
|
|
|
|||
|
|
### 7.1 动态刷新策略
|
|||
|
|
|
|||
|
|
1. **倒计时渲染**:使用 `qr_expires_in` 启动本地倒计时器
|
|||
|
|
2. **过期遮罩**:剩余时间 ≤ 0 时显示模糊/遮罩 + "点击刷新"按钮
|
|||
|
|
3. **静默刷新**:过期前 30 秒自动调用 `refreshQr` 重新获取并渲染
|
|||
|
|
|
|||
|
|
### 7.2 短码编码规则
|
|||
|
|
|
|||
|
|
短码结构:`【4位 goods_id 明文 base36】【变长 ticket_id 混淆 base36】`
|
|||
|
|
|
|||
|
|
- 解码 O(1):前 4 位直接取 goods_id,剩余部分用 Feistel 解密得到 ticket_id
|
|||
|
|
- 示例:`000ca1b2` → `goods_id=0`, `ticket_id=12345`
|
|||
|
|
- 核销员 App 扫描时应获取完整短码字符串进行提交
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 附录:数据字典
|
|||
|
|
|
|||
|
|
### vr_tickets 电子票表
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `id` | int | 票 ID(自增) |
|
|||
|
|
| `goods_id` | int | 关联商品 ID |
|
|||
|
|
| `order_id` | int | 关联订单 ID |
|
|||
|
|
| `user_id` | int | 购票用户 ID |
|
|||
|
|
| `ticket_code` | string | UUID v4 长码 |
|
|||
|
|
| `qr_data` | string | 格式 `短码\|payload`,内部缓存 |
|
|||
|
|
| `seat_info` | string | `场次\|场馆\|演播室\|分区\|座位号` |
|
|||
|
|
| `verify_status` | int | `0`=未核销 `1`=已核销 `2`=已退款 |
|
|||
|
|
| `verify_time` | int | 核销时间戳 |
|
|||
|
|
| `verifier_id` | int | 执行核销的核销员 ID |
|
|||
|
|
| `real_name` | string | 观演人姓名 |
|
|||
|
|
| `phone` | string | 观演人手机 |
|
|||
|
|
| `issued_at` | int | 票发放时间戳 |
|
|||
|
|
| `created_at` | int | 创建时间戳 |
|
|||
|
|
|
|||
|
|
### vr_verifiers 核销员表
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `id` | int | 核销员 ID(自增) |
|
|||
|
|
| `user_id` | int | 关联的 C 端用户 ID(ShopXO 会员 ID) |
|
|||
|
|
| `name` | string | 核销员名称(后台显示用) |
|
|||
|
|
| `status` | int | `1`=启用 `0`=禁用 |
|
|||
|
|
| `created_at` | int | 创建时间戳 |
|
|||
|
|
|
|||
|
|
> **注意**: `user_id` 关联的是 C 端用户 ID,而非后台管理员 ID。C 端用户只需在 `vr_verifiers` 表中存在且 `status=1` 即可使用 UniApp 核销功能。
|
|||
|
|
|
|||
|
|
### vr_verifications 核销记录表
|
|||
|
|
|
|||
|
|
| 字段 | 类型 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `id` | int | 记录 ID(自增) |
|
|||
|
|
| `ticket_id` | int | 票 ID |
|
|||
|
|
| `ticket_code` | string | 票长码快照 |
|
|||
|
|
| `verifier_id` | int | 执行核销的核销员 ID |
|
|||
|
|
| `verifier_name` | string | 核销员名称快照 |
|
|||
|
|
| `goods_id` | int | 商品 ID |
|
|||
|
|
| `created_at` | int | 核销时间戳 |
|