feat: implement third-party order sync

This commit is contained in:
2026-06-07 02:05:48 +08:00
commit aa593449bb
398 changed files with 51460 additions and 0 deletions
+415
View File
@@ -0,0 +1,415 @@
## 通用说明
- **请求方式**:所有接口(除 `/api/streamLogs` 外)均为 **POST**
- **请求头**:必须添加
`Content-Type: application/x-www-form-urlencoded`
- **基础 URL**`https://biedawo.org/api.php`
- **响应格式**JSON`/api/streamLogs` 除外,为 Stream 格式)
## 1. 查课接口
**URL**`https://biedawo.org/api.php?act=get`
| 参数 | 解释 | 必传 |
| -------- | -------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| platform | 项目 ID | ✅ |
| user | 下单账号 | ✅ |
| pass | 下单密码 | ✅ |
| school | 用户学校 | ✅ |
---
## 2. 下单接口
**URL**`https://biedawo.org/api.php?act=add`
| 参数 | 解释 | 必传 |
| -------- | ------------------------------------------------------------------------------ | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| platform | 项目 ID | ✅ |
| user | 下单账号 | ✅ |
| pass | 下单密码 | ✅ |
| school | 用户学校 | ✅ |
| kcname | 课程名称 | ✅ |
| kcid | 课程 ID | ❌ |
| expand | 扩展参数(恐龙项目需使用 `expand.konglong` 传递 remark、city、tag、config 等) | ❌ |
---
## 3. 查单接口
**URL**`https://biedawo.org/api.php?act=chadan`
| 参数 | 解释 | 必传 |
| -------- | ----------------------------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID(与 username 二选一) | ✅ |
| username | 下单账号(与 id 二选一) | ✅ |
---
## 4. 补单接口
**URL**`https://biedawo.org/api.php?act=budan`
| 参数 | 解释 | 必传 |
| ---- | -------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
---
## 5. 改密接口
**URL**`https://biedawo.org/api.php?act=gaimi`
| 参数 | 解释 | 必传 |
| --------- | ------------------------------------------ | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
| newPwd | 新密码 | ❌ |
| remark | 恐龙项目备注(支持数组或英文逗号分隔) | ❌ |
| city | 恐龙项目代理 IP 归属地 | ❌ |
| tag | 恐龙项目归属标记 | ❌ |
| config | 恐龙项目动态配置(支持数组或 JSON 字符串) | ❌ |
| autoReset | 是否自动补单(传 `1` 开启) | ❌ |
> 支持的恐龙项目包括:稳(奶昔)系、坤坤、龙猫、10u、10u 单视频/考试、图图 qg、66 冷门、少系、pup、叶族、spacex、黑白、至强、继续教育 1/2 号、3Y 继续教育、欲梦、优优、皇族、红杉、神奇等。
**示例(恐龙项目)**
```json
{
"uid": 1023,
"key": "JISADHG783J",
"id": "202604200001",
"password": "new_password",
"remark": ["urgent", "code_ready"],
"city": "beijing",
"tag": "vip",
"config": {
"useTime": 60,
"code": "888888"
},
"autoReset": 1
}
```
---
## 6. 暂停接口
**URL**`https://biedawo.org/api.php?act=stop`
| 参数 | 解释 | 必传 |
| ---- | -------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
> 支持:坤坤、奶昔、3y、继续教育 2 号、八九、课代表、恐龙、神奇项目
---
## 7. 优先学习接口
**URL**`https://biedawo.org/api.php?act=priority`
| 参数 | 解释 | 必传 |
| ---- | -------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
> 支持:坤坤项目
---
## 8. 课程转换接口
**URL**`https://biedawo.org/api.php?act=convert`
| 参数 | 解释 | 必传 |
| ---------------- | ----------------------------------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
| convertToClassId | 目标课程 ID182:慢刷,183:秒刷) | ✅ |
> 支持:奶昔项目
---
## 9. 修改时长接口
**URL**`https://biedawo.org/api.php?act=update_time`
| 参数 | 解释 | 必传 |
| ---- | ------------ | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
| time | 时长(小时) | ✅ |
> 支持:pup 项目
---
## 10. 修改周期接口
**URL**`https://biedawo.org/api.php?act=update_cycle`
| 参数 | 解释 | 必传 |
| ----- | ---------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
| cycle | 周期(天) | ✅ |
> 支持:pup 项目
---
## 11. 恐龙日志接口(Stream
**URL**`https://biedawo.org/api/streamLogs`
**请求方式**GET
| 参数 | 解释 | 必传 |
| ---- | ------- | ---- |
| id | 订单 ID | ✅ |
> 响应为 Stream 格式,参考 [CSDN 文档](https://blog.csdn.net/qq_42978535/article/details/142670351)
---
## 12. 日志接口
**URL**`https://biedawo.org/api.php?act=cha_logwk`
| 参数 | 解释 | 必传 |
| ---- | -------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
> 支持:坤系列、PUP 系列、至强系列、课代表系列、优优项目
---
## 13. zhs 明细接口
**URL**`https://biedawo.org/api.php?act=cha_log`
| 参数 | 解释 | 必传 |
| ---- | -------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
> 支持:坤系列
---
## 14. 获取易教育学习记录接口
**URL**`https://biedawo.org/api.php?act=get_yjy_study_log`
| 参数 | 解释 | 必传 |
| ---- | -------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
---
## 15. 获取订单接口
**URL**`https://biedawo.org/api.php?act=orders`
| 参数 | 解释 | 必传 |
| ------ | -------------------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| page | 页数(默认 1 | ❌ |
| limit | 每页条数(默认 100 | ❌ |
| recent | 近几天订单(默认 5 | ❌ |
> 节流说明:每天 7 点后仅返回近 5 天订单数据
---
## 16. 获取分类接口
**URL**`https://biedawo.org/api.php?act=getcate`
| 参数 | 解释 | 必传 |
| ---- | -------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
---
## 17. 获取课程接口
**URL**`https://biedawo.org/api.php?act=getclass`
| 参数 | 解释 | 必传 |
| ------ | ------------------------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| fenlei | 分类 ID(为空则返回所有) | ❌ |
---
## 18. 上传工单图片接口
**URL**`https://biedawo.org/api.php?act=uploadTicketImage`
| 参数 | 解释 | 必传 |
| ---- | ------------------------------------------------------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| file | 图片文件(jpg/png/gif/webp,≤5MBmultipart/form-data | ✅ |
**成功返回示例**
```json
{
"code": 1,
"msg": "上传成功",
"data": {
"file_name": "abc123.jpg",
"file_path": "https://pan.pptvt.com/xxx/abc123.jpg",
"file_size": 12345,
"file_type": "jpg",
"mime_type": "image/jpeg",
"hash": "xxx",
"downurl": "https://...",
"viewurl": "https://..."
}
}
```
---
## 19. 提交工单接口
**URL**`https://biedawo.org/api.php?act=submitWorkOrder`
| 参数 | 解释 | 必传 |
| ----------- | -------------------------------------------------------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| id | 订单 ID | ✅ |
| type | 订单类型(100:网课,200:闪电闪动,300:运动世界,...) | ❌ |
| title | 工单标题(默认“订单问题反馈”) | ❌ |
| content | 工单内容 | ✅ |
| attachments | 附件 JSON 数组(含 file_name、file_path 等) | ❌ |
**成功返回示例**
```json
{
"code": 1,
"msg": "添加成功",
"data": {
"workId": 123,
"ticket_no": "TK24010112001234"
}
}
```
> 限制:用户总充值需 ≥100 元,同一订单只能创建一个工单。
---
## 20. 查询工单接口
**URL**`https://biedawo.org/api.php?act=queryWorkOrder`
| 参数 | 解释 | 必传 |
| ------ | -------- | ---- |
| uid | 您的 UID | ✅ |
| key | 您的 KEY | ✅ |
| workId | 工单 ID | ✅ |
**状态说明**
| 状态值 | 状态文本 | 说明 |
| ------ | -------- | ---------------- |
| 1 | 待处理 | 已创建,等待处理 |
| 2 | 处理中 | 管理员处理中 |
| 3 | 已回复 | 等待用户确认 |
| 4 | 已解决 | 工单已解决 |
| 5 | 已关闭 | 工单关闭 |
| 6 | 已取消 | 工单取消 |
---
## 附录:expand 参数说明(下单接口)
### 通用字段(扁平结构)
| 参数 | 解释 |
| --------- | -------------------------------------------------------------------------------------------------------- |
| score | 分数(如 95 |
| duration | 时长 |
| period | 周期 |
| staticIds | 订单标识数组:1-班测不提交 2-秒刷 3-覆盖已作答 4-跳过已作答 5-不提交 6-强制提交 7-慢刷 8-不满分 9-仅必修 |
### 恐龙项目专用(expand.konglong
| 参数 | 解释 |
| --------------- | ----------------------------------- |
| konglong.remark | 备注(数组,提交时转逗号拼接) |
| konglong.city | 代理 IP 归属地 |
| konglong.tag | 自定义标签 |
| konglong.config | 动态配置对象(如 `{"useTime":60}` |
**通用示例**
```json
{
"uid": 1023,
"key": "JISADHG783J",
"expand": {
"score": 95,
"duration": 35,
"period": 7,
"staticIds": [1, 2]
}
}
```
**恐龙项目示例**
```json
{
"uid": 1023,
"key": "JISADHG783J",
"platform": 12345,
"school": "demo_university",
"user": "student001",
"pass": "pwd123456",
"kcname": "course_name",
"kcid": "hash_value",
"expand": {
"konglong": {
"remark": ["urgent", "code_ready"],
"city": "beijing",
"tag": "vip",
"config": {
"useTime": 60,
"code": "888888"
}
}
}
}
```
+52
View File
@@ -0,0 +1,52 @@
# Database Draft
第一阶段使用 TypeORM 描述 MySQL 数据模型。当前只保留管理后台的最小底座,并把第三方接口账号改为环境变量配置。
后续数据库设计必须按商城模型扩展:第三方分类/商品和自营分类/商品使用统一业务主干,通过 `source_type``provider``external_id` 等字段区分来源。支付功能先按订单、支付单、支付事件和退款单预留结构,详细说明见 `docs/payment-integration.md`
## Initial Entities
- `users`: 管理后台用户。
- `roles`: 角色与权限集合。
- `user_roles`: 用户角色关联表。
- `api_call_logs`: 所有第三方接口调用日志。
- `categories`: 商品分类。当前迁移草案偏第三方项目分类缓存,后续需要支持 `third_party``self_owned`
- `courses`: 第三方商品/课程缓存。后续可改造为统一 `products`,或保留表名但补齐商品来源、价格、库存、上下架和履约类型等字段。
## Unified Commerce Model
建议后续补齐或新建以下核心表:
- `products`: 商品主表,统一承载第三方课程商品和自营商品。
- `product_skus`: 商品规格/套餐表,后续存在周期、套餐、规格时使用。
- `orders`: 订单主表,保存订单号、用户、金额、支付状态、履约状态。
- `order_items`: 订单明细表,保存商品快照和下单扩展信息。
- `order_fulfillments`: 履约记录表,区分第三方 API 提交、本地自动交付和人工处理。
- `payments`: 支付单表,记录支付渠道、金额、渠道流水号和支付状态。
- `payment_events`: 支付回调、主动查询、退款回调等事件日志。
- `refunds`: 退款单表。
- `audit_logs`: 后台操作审计日志。
关键来源字段:
- `source_type`: `third_party``self_owned`
- `provider`: 第三方供应商标识,当前第三方默认可用 `biedawo`,自营可为空。
- `external_id`: 第三方分类、商品或订单 ID,自营数据为空。
- `fulfillment_type`: `third_party_api``local_only``manual`
## Third-Party API Config
当前不再维护渠道管理或多套接口账号配置。服务端统一读取:
- `WK_BASE_URL`
- `WK_APP_UID`
- `WK_APP_KEY`
`WK_APP_KEY` 原样作为第三方 `key` 使用,前端和日志展示时只允许脱敏显示。
## Migration Policy
- 不在运行时开启 `synchronize`
- 本地开发可以通过 TypeORM migration 生成 SQL。
- 生产环境必须使用 migration 执行结构变更。
- `.env` 不提交到仓库,参考 `.env.example` 创建本地配置。
@@ -0,0 +1,33 @@
CREATE TABLE IF NOT EXISTS `categories` (
`id` varchar(36) NOT NULL,
`api_account_id` varchar(36) NOT NULL,
`remote_category_id` varchar(120) NOT NULL,
`name` varchar(160) NOT NULL,
`sort_order` int NOT NULL DEFAULT 0,
`raw_payload` json NULL,
`last_synced_at` datetime NOT NULL,
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (`id`),
UNIQUE KEY `IDX_categories_account_remote` (`api_account_id`, `remote_category_id`),
KEY `IDX_categories_account` (`api_account_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `courses` (
`id` varchar(36) NOT NULL,
`api_account_id` varchar(36) NOT NULL,
`category_id` varchar(36) NULL,
`remote_category_id` varchar(120) NOT NULL DEFAULT '',
`remote_course_id` varchar(160) NOT NULL,
`name` varchar(255) NOT NULL,
`is_favorite` tinyint NOT NULL DEFAULT 0,
`enabled` tinyint NOT NULL DEFAULT 1,
`raw_payload` json NULL,
`last_synced_at` datetime NOT NULL,
`created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
`updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
PRIMARY KEY (`id`),
UNIQUE KEY `IDX_courses_account_remote_category` (`api_account_id`, `remote_course_id`, `remote_category_id`),
KEY `IDX_courses_account` (`api_account_id`),
KEY `IDX_courses_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,7 @@
ALTER TABLE `courses`
ADD COLUMN `content` text NULL AFTER `name`;
UPDATE `courses`
SET `content` = JSON_UNQUOTE(JSON_EXTRACT(`raw_payload`, '$.content'))
WHERE `content` IS NULL
AND JSON_EXTRACT(`raw_payload`, '$.content') IS NOT NULL;
@@ -0,0 +1,5 @@
CREATE INDEX `IDX_courses_account_favorite_updated`
ON `courses` (`api_account_id`, `is_favorite`, `updated_at`);
CREATE INDEX `IDX_courses_account_category_favorite_updated`
ON `courses` (`api_account_id`, `category_id`, `is_favorite`, `updated_at`);
@@ -0,0 +1,5 @@
CREATE INDEX `IDX_api_call_logs_created_at`
ON `api_call_logs` (`created_at`);
CREATE INDEX `IDX_api_call_logs_act_created_at`
ON `api_call_logs` (`act`, `created_at`);
@@ -0,0 +1,10 @@
ALTER TABLE `courses`
ADD COLUMN `price` decimal(10,2) NOT NULL DEFAULT 0.00 AFTER `name`;
UPDATE `courses`
SET `price` = CAST(JSON_UNQUOTE(JSON_EXTRACT(`raw_payload`, '$.price')) AS DECIMAL(10,2))
WHERE JSON_EXTRACT(`raw_payload`, '$.price') IS NOT NULL
AND JSON_UNQUOTE(JSON_EXTRACT(`raw_payload`, '$.price')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$';
ALTER TABLE `courses`
DROP COLUMN `raw_payload`;
+348
View File
@@ -0,0 +1,348 @@
# 支付功能接入文档
本文用于指导后续接入微信、支付宝或其他聚合支付渠道。当前项目尚未实现支付功能,开发时应先把订单、支付单、回调日志和履约流程的边界预留好,避免后续重构订单主链路。
## 接入目标
- 订单创建后先进入待支付状态。
- 用户或管理员发起支付,后端创建支付单。
- 支付渠道异步回调后,后端验签并更新支付状态。
- 支付成功后触发订单履约:
- 第三方商品:服务端调用第三方下单接口。
- 自营商品:只写入本地数据库,生成本地交付记录。
- 支持后续退款、支付状态主动查询、回调补偿和支付审计。
## 推荐订单状态
订单主状态建议拆成支付状态和履约状态,不要只用一个 `status` 混合表达全部含义。
支付状态 `payment_status`
| 状态 | 说明 |
| --- | --- |
| `unpaid` | 未支付,订单刚创建 |
| `paying` | 已创建支付单,等待渠道结果 |
| `paid` | 已支付成功 |
| `pay_failed` | 支付失败 |
| `closed` | 支付超时或订单关闭 |
| `refunding` | 退款中 |
| `refunded` | 已退款 |
| `partial_refunded` | 部分退款 |
履约状态 `fulfillment_status`
| 状态 | 说明 |
| --- | --- |
| `pending` | 等待支付或等待履约 |
| `submitting` | 正在提交第三方或生成本地交付 |
| `submitted` | 已提交第三方或已生成交付 |
| `processing` | 第三方或本地处理中 |
| `completed` | 已完成 |
| `failed` | 履约失败 |
| `canceled` | 已取消 |
## 推荐数据表
### `orders`
订单主表,保存业务订单。
建议字段:
| 字段 | 说明 |
| --- | --- |
| `id` | 本地订单 ID |
| `order_no` | 本地订单号,展示和支付使用 |
| `user_id` | 下单用户 |
| `source_type` | `third_party``self_owned` |
| `provider` | 第三方供应商标识,自营可为空 |
| `total_amount` | 订单金额 |
| `payable_amount` | 应付金额 |
| `paid_amount` | 实付金额 |
| `payment_status` | 支付状态 |
| `fulfillment_status` | 履约状态 |
| `remark` | 备注 |
| `created_at` | 创建时间 |
| `updated_at` | 更新时间 |
### `order_items`
订单明细表,保存商品快照,避免商品改价影响历史订单。
建议字段:
| 字段 | 说明 |
| --- | --- |
| `id` | 明细 ID |
| `order_id` | 订单 ID |
| `product_id` | 商品 ID |
| `product_name` | 商品名称快照 |
| `source_type` | 商品来源 |
| `external_product_id` | 第三方商品 ID,自营为空 |
| `unit_price` | 单价 |
| `quantity` | 数量 |
| `total_amount` | 明细金额 |
| `payload` | 下单扩展信息,敏感字段入库前加密或脱敏 |
### `payments`
支付单表。一个订单可以有多次支付尝试,但同一时间只允许一个有效待支付支付单。
建议字段:
| 字段 | 说明 |
| --- | --- |
| `id` | 支付单 ID |
| `payment_no` | 本地支付单号 |
| `order_id` | 订单 ID |
| `order_no` | 本地订单号冗余 |
| `channel` | 支付渠道,如 `wechat``alipay``manual` |
| `amount` | 支付金额 |
| `currency` | 默认 `CNY` |
| `status` | `created``paying``paid``failed``closed``refunded` |
| `channel_trade_no` | 渠道交易号 |
| `channel_payload` | 渠道创建支付返回,敏感字段脱敏 |
| `paid_at` | 支付成功时间 |
| `expired_at` | 支付过期时间 |
| `created_at` | 创建时间 |
| `updated_at` | 更新时间 |
### `payment_events`
支付事件表,保存支付回调、主动查询、退款回调等原始事件。
建议字段:
| 字段 | 说明 |
| --- | --- |
| `id` | 事件 ID |
| `payment_id` | 支付单 ID |
| `order_id` | 订单 ID |
| `channel` | 支付渠道 |
| `event_type` | `notify``query``refund_notify` |
| `event_no` | 渠道事件号或渠道交易号,用于幂等 |
| `status` | 处理状态 |
| `raw_payload` | 原始回调内容,注意脱敏或加密 |
| `verify_result` | 验签结果 |
| `error_message` | 处理失败原因 |
| `created_at` | 创建时间 |
### `refunds`
退款单表,后续退款功能使用。
建议字段:
| 字段 | 说明 |
| --- | --- |
| `id` | 退款单 ID |
| `refund_no` | 本地退款单号 |
| `payment_id` | 支付单 ID |
| `order_id` | 订单 ID |
| `amount` | 退款金额 |
| `reason` | 退款原因 |
| `status` | `created``processing``succeeded``failed` |
| `channel_refund_no` | 渠道退款单号 |
| `refunded_at` | 退款完成时间 |
## 后端接口设计
### 创建订单
`POST /api/orders`
职责:
- 校验商品、价格、库存、上下架状态。
- 创建 `orders``order_items`
- 设置 `payment_status=unpaid`
- 设置 `fulfillment_status=pending`
- 返回订单号和应付金额。
注意:
- 第三方商品在订单创建阶段不要把 `uid``key` 返回给前端。
- 学生账号和密码等敏感信息必须由服务端保存,并在展示时脱敏。
### 创建支付单
`POST /api/payments`
请求示例:
```json
{
"orderNo": "ORD202606060001",
"channel": "wechat",
"returnUrl": "https://example.com/orders/ORD202606060001"
}
```
职责:
- 校验订单存在且未支付。
- 校验订单金额和当前商品金额是否允许支付。
- 创建 `payments`
- 调用支付渠道统一下单接口。
- 返回前端支付参数,例如二维码、跳转链接或小程序支付参数。
### 支付回调
`POST /api/payments/notify/:channel`
职责:
- 获取原始请求体。
- 按渠道验签。
- 写入 `payment_events`
- 校验本地支付单号、金额、币种、渠道交易号。
- 幂等更新 `payments.status=paid`
- 幂等更新 `orders.payment_status=paid`
- 触发履约流程。
- 返回渠道要求的成功响应。
必须保证:
- 重复回调不会重复提交第三方订单。
- 金额不一致时不能标记为已支付。
- 验签失败只记录事件,不更新支付成功。
### 主动查询支付状态
`GET /api/payments/:paymentNo/query`
职责:
- 调用渠道查询接口。
- 写入 `payment_events`
- 如果渠道显示已支付,走同一套支付成功处理逻辑。
### 申请退款
`POST /api/refunds`
职责:
- 校验订单已支付且允许退款。
- 创建 `refunds`
- 调用渠道退款接口。
- 更新订单为 `refunding`
### 退款回调
`POST /api/payments/refund-notify/:channel`
职责:
- 验签。
- 写入 `payment_events`
- 幂等更新退款单和订单支付状态。
## 支付成功后的履约流程
支付成功处理函数建议抽成后端服务方法,例如:
```ts
async function handlePaymentSucceeded(paymentNo: string) {
// 1. 开启事务
// 2. 锁定 payment 和 order
// 3. 如果 payment/order 已处理过,直接返回
// 4. 标记 payment=paid、order.payment_status=paid
// 5. 根据 order.source_type 触发履约
// 6. 提交事务
}
```
第三方商品:
- 将订单履约状态改为 `submitting`
- 调用第三方 `act=add`
- 成功后保存第三方订单 ID 和响应摘要。
- 将履约状态改为 `submitted``processing`
- 失败时改为 `failed`,保留失败原因,后台可手动重试。
自营商品:
- 将订单履约状态改为 `submitting`
- 生成本地交付记录或待处理任务。
- 自动交付商品可直接改为 `completed`
- 人工处理商品改为 `processing`
## 幂等与事务
必须使用幂等键:
- 本地订单号 `order_no` 唯一。
- 本地支付单号 `payment_no` 唯一。
- 渠道交易号 `channel_trade_no` 唯一。
- 支付事件 `event_no` 或原始回调哈希唯一。
建议处理顺序:
1. 回调进入后先验签。
2. 写入 `payment_events`,重复事件直接返回成功。
3. 开启数据库事务。
4. 锁定支付单和订单。
5. 校验金额和状态。
6. 更新支付单和订单。
7. 创建履约任务或直接履约。
8. 提交事务。
第三方下单如果耗时较长,建议支付事务内只创建 `order_fulfillments` 任务,事务外由 BullMQ 异步提交第三方,避免支付回调超时。
## 环境变量建议
```env
PAYMENT_DEFAULT_CHANNEL=wechat
WECHAT_PAY_MCH_ID=
WECHAT_PAY_APP_ID=
WECHAT_PAY_API_V3_KEY=
WECHAT_PAY_PRIVATE_KEY=
WECHAT_PAY_CERT_SERIAL_NO=
WECHAT_PAY_NOTIFY_URL=
ALIPAY_APP_ID=
ALIPAY_PRIVATE_KEY=
ALIPAY_PUBLIC_KEY=
ALIPAY_NOTIFY_URL=
ALIPAY_RETURN_URL=
```
生产环境要求:
- 支付私钥和商户密钥不得提交仓库。
- `.env` 不提交。
- 私钥建议使用部署平台 Secret 或 KMS。
- 回调地址必须是 HTTPS。
## 前端页面建议
- 订单确认页:展示商品、来源、价格、账号信息摘要、应付金额。
- 支付页:展示支付方式、二维码/跳转按钮、倒计时、支付状态轮询。
- 订单详情页:展示支付状态、履约状态、第三方提交状态或自营交付状态。
- 后台支付管理:支付单列表、回调日志、主动查询、异常标记。
- 后台退款管理:退款申请、退款结果、退款回调日志。
## 测试清单
- 创建订单后状态为 `unpaid``pending`
- 创建支付单后状态为 `paying`
- 正常回调可把支付单和订单标记为已支付。
- 重复回调不会重复履约。
- 金额不一致不会标记成功。
- 验签失败不会更新订单。
- 第三方商品支付成功后只提交一次第三方订单。
- 自营商品支付成功后不调用第三方接口。
- 支付超时后订单可关闭。
- 退款成功后订单状态正确更新。
## 与第三方商品的关系
支付渠道和第三方课程接口是两套外部系统,不要混在一个服务里:
- 支付服务只负责收钱、验签、退款和支付事件。
- 第三方课程服务只负责 `getcate``getclass``get``add``orders` 等接口。
- 订单服务负责协调支付成功后的履约分流。
这样后续新增支付渠道或新增自营商品时,不需要改动第三方接口调用核心逻辑。