feat: 实现订单支付功能模块
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m36s

- 新增订单管理、支付回调、购买验证等核心服务
- 实现订单、订单项目的数据存储层和 API 接口
- 添加订单数据库迁移和 DTO 定义
- 更新 API 文档和路由配置
- 同步 3 个新规范到主规范库(订单管理、订单支付、套餐购买验证)
- 完成 OpenSpec 变更归档

Ultraworked with Sisyphus
This commit is contained in:
2026-01-28 22:12:15 +08:00
parent a945a4f554
commit dfcf16f548
39 changed files with 3795 additions and 126 deletions

View File

@@ -923,6 +923,34 @@ components:
description: 提现单号
type: string
type: object
DtoCreateOrderRequest:
properties:
device_id:
description: 设备ID(设备购买时必填)
minimum: 0
nullable: true
type: integer
iot_card_id:
description: IoT卡ID(单卡购买时必填)
minimum: 0
nullable: true
type: integer
order_type:
description: 订单类型 (single_card:单卡购买, device:设备购买)
type: string
package_ids:
description: 套餐ID列表
items:
minimum: 0
type: integer
maxItems: 10
minItems: 1
nullable: true
type: array
required:
- order_type
- package_ids
type: object
DtoCreatePackageRequest:
properties:
data_amount_mb:
@@ -2226,6 +2254,117 @@ components:
description: 已提现佣金(分)
type: integer
type: object
DtoOrderItemResponse:
properties:
amount:
description: 小计金额(分)
type: integer
id:
description: 明细ID
minimum: 0
type: integer
package_id:
description: 套餐ID
minimum: 0
type: integer
package_name:
description: 套餐名称
type: string
quantity:
description: 数量
type: integer
unit_price:
description: 单价(分)
type: integer
type: object
DtoOrderListResponse:
properties:
list:
description: 订单列表
items:
$ref: '#/components/schemas/DtoOrderResponse'
nullable: true
type: array
page:
description: 当前页码
type: integer
page_size:
description: 每页数量
type: integer
total:
description: 总数
type: integer
total_pages:
description: 总页数
type: integer
type: object
DtoOrderResponse:
properties:
buyer_id:
description: 买家ID
minimum: 0
type: integer
buyer_type:
description: 买家类型 (personal:个人客户, agent:代理商)
type: string
commission_config_version:
description: 佣金配置版本
type: integer
commission_status:
description: 佣金状态 (1:待计算, 2:已计算)
type: integer
created_at:
description: 创建时间
format: date-time
type: string
device_id:
description: 设备ID
minimum: 0
nullable: true
type: integer
id:
description: 订单ID
minimum: 0
type: integer
iot_card_id:
description: IoT卡ID
minimum: 0
nullable: true
type: integer
items:
description: 订单明细列表
items:
$ref: '#/components/schemas/DtoOrderItemResponse'
nullable: true
type: array
order_no:
description: 订单号
type: string
order_type:
description: 订单类型 (single_card:单卡购买, device:设备购买)
type: string
paid_at:
description: 支付时间
format: date-time
nullable: true
type: string
payment_method:
description: 支付方式 (wallet:钱包支付, wechat:微信支付, alipay:支付宝支付)
type: string
payment_status:
description: 支付状态 (1:待支付, 2:已支付, 3:已取消, 4:已退款)
type: integer
payment_status_text:
description: 支付状态文本
type: string
total_amount:
description: 订单总金额(分)
type: integer
updated_at:
description: 更新时间
format: date-time
type: string
type: object
DtoPackagePageResult:
properties:
list:
@@ -7860,6 +7999,228 @@ paths:
summary: 发起提现申请
tags:
- 我的佣金
/api/admin/orders:
get:
parameters:
- description: 页码
in: query
name: page
schema:
description: 页码
minimum: 1
type: integer
- description: 每页数量
in: query
name: page_size
schema:
description: 每页数量
maximum: 100
minimum: 1
type: integer
- description: 支付状态 (1:待支付, 2:已支付, 3:已取消, 4:已退款)
in: query
name: payment_status
schema:
description: 支付状态 (1:待支付, 2:已支付, 3:已取消, 4:已退款)
maximum: 4
minimum: 1
nullable: true
type: integer
- description: 订单类型 (single_card:单卡购买, device:设备购买)
in: query
name: order_type
schema:
description: 订单类型 (single_card:单卡购买, device:设备购买)
type: string
- description: 订单号(精确查询)
in: query
name: order_no
schema:
description: 订单号(精确查询)
maxLength: 30
type: string
- description: 创建时间起始
in: query
name: start_time
schema:
description: 创建时间起始
format: date-time
nullable: true
type: string
- description: 创建时间结束
in: query
name: end_time
schema:
description: 创建时间结束
format: date-time
nullable: true
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoOrderListResponse'
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 获取订单列表
tags:
- 订单管理
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DtoCreateOrderRequest'
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoOrderResponse'
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 创建订单
tags:
- 订单管理
/api/admin/orders/{id}:
get:
parameters:
- description: 订单ID
in: path
name: id
required: true
schema:
description: 订单ID
minimum: 0
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoOrderResponse'
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 获取订单详情
tags:
- 订单管理
/api/admin/orders/{id}/cancel:
post:
parameters:
- description: 订单ID
in: path
name: id
required: true
schema:
description: 订单ID
minimum: 0
type: integer
responses:
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 取消订单
tags:
- 订单管理
/api/admin/package-series:
get:
parameters:
@@ -11380,6 +11741,42 @@ paths:
summary: 查询任务状态
tags:
- 任务管理
/api/callback/alipay:
post:
responses:
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
summary: 支付宝回调
tags:
- 支付回调
/api/callback/wechat-pay:
post:
responses:
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
summary: 微信支付回调
tags:
- 支付回调
/api/h5/login:
post:
requestBody:
@@ -11479,6 +11876,228 @@ paths:
summary: 获取当前用户信息
tags:
- H5 认证
/api/h5/orders:
get:
parameters:
- description: 页码
in: query
name: page
schema:
description: 页码
minimum: 1
type: integer
- description: 每页数量
in: query
name: page_size
schema:
description: 每页数量
maximum: 100
minimum: 1
type: integer
- description: 支付状态 (1:待支付, 2:已支付, 3:已取消, 4:已退款)
in: query
name: payment_status
schema:
description: 支付状态 (1:待支付, 2:已支付, 3:已取消, 4:已退款)
maximum: 4
minimum: 1
nullable: true
type: integer
- description: 订单类型 (single_card:单卡购买, device:设备购买)
in: query
name: order_type
schema:
description: 订单类型 (single_card:单卡购买, device:设备购买)
type: string
- description: 订单号(精确查询)
in: query
name: order_no
schema:
description: 订单号(精确查询)
maxLength: 30
type: string
- description: 创建时间起始
in: query
name: start_time
schema:
description: 创建时间起始
format: date-time
nullable: true
type: string
- description: 创建时间结束
in: query
name: end_time
schema:
description: 创建时间结束
format: date-time
nullable: true
type: string
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoOrderListResponse'
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 获取订单列表
tags:
- H5 订单
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DtoCreateOrderRequest'
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoOrderResponse'
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 创建订单
tags:
- H5 订单
/api/h5/orders/{id}:
get:
parameters:
- description: 订单ID
in: path
name: id
required: true
schema:
description: 订单ID
minimum: 0
type: integer
responses:
"200":
content:
application/json:
schema:
$ref: '#/components/schemas/DtoOrderResponse'
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 获取订单详情
tags:
- H5 订单
/api/h5/orders/{id}/wallet-pay:
post:
parameters:
- description: 订单ID
in: path
name: id
required: true
schema:
description: 订单ID
minimum: 0
type: integer
responses:
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 请求参数错误
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 未认证或认证已过期
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 无权访问
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
description: 服务器内部错误
security:
- BearerAuth: []
summary: 钱包支付
tags:
- H5 订单
/api/h5/password:
put:
requestBody:

View File

@@ -0,0 +1,333 @@
# 订单支付系统功能总结
## 概述
add-order-payment 提案实现了完整的订单和支付流程,核心是"强充"机制:用户不能直接给钱包充值,必须通过购买套餐来充值。这样每笔充值都有对应的套餐购买记录,便于佣金计算和业务追踪。
## 核心功能
### 1. 订单管理
**新增模型**
- `Order`:订单模型,记录套餐购买信息
- `OrderItem`:订单明细(支持一个订单购买多个套餐)
**订单字段**
- 订单号、订单类型(单卡购买/设备购买)
- 买家信息(个人客户/代理店铺)
- 关联的卡/设备 ID
- 支付金额、支付状态、支付方式
- 佣金计算状态
**业务流程**
1. 用户选择套餐,创建订单
2. 用户支付(微信/支付宝/钱包余额)
3. 支付成功后,套餐生效,流量额度增加
4. 触发佣金计算Phase 5
### 2. API 端点
**后台管理端** (`/api/admin/orders`):
- `POST /orders` - 创建订单
- `GET /orders` - 获取订单列表(支持分页和筛选)
- `GET /orders/:id` - 获取订单详情
- `POST /orders/:id/cancel` - 取消订单
**H5 端** (`/api/h5/orders`):
- `POST /orders` - 创建订单
- `GET /orders` - 获取订单列表
- `GET /orders/:id` - 获取订单详情
- `POST /orders/:id/wallet-pay` - 钱包支付
**支付回调** (`/api/callback`):
- `POST /wechat-pay` - 微信支付回调
- `POST /alipay` - 支付宝回调
### 3. 业务规则
**购买限制**
- 只能购买卡/设备关联的套餐系列下的套餐
- 只能购买已上架且启用的套餐
- 设备购买时,套餐分配给设备下所有卡(流量共享)
- 订单金额 = 套餐零售价(代理设置的售价)
**支付流程**
- 钱包支付:事务扣减余额 → 更新订单状态 → 激活套餐 → 更新销售统计
- 第三方支付:验证签名 → 幂等处理 → 激活套餐 → 更新销售统计
**套餐激活**
- 创建 PackageUsage 记录
- 更新 ShopSeriesCommissionStats销售统计
- 快照佣金配置版本
## 数据库设计
### 表结构
**tb_order**(订单表):
- `id`, `created_at`, `updated_at`, `deleted_at`
- `creator`, `updater`
- `order_no`(订单号,唯一)
- `order_type`订单类型1=单卡购买2=设备购买)
- `buyer_type`买家类型1=个人客户2=代理店铺)
- `buyer_id`(买家 ID
- `iot_card_id`IoT 卡 ID
- `device_id`(设备 ID
- `total_amount`(总金额,分)
- `payment_method`支付方式1=钱包2=微信3=支付宝)
- `payment_status`支付状态1=待支付2=已支付3=已取消)
- `paid_at`(支付时间)
- `commission_status`佣金状态1=未计算2=已计算)
- `commission_config_version`(佣金配置快照版本)
**tb_order_item**(订单明细表):
- `id`, `created_at`, `updated_at`, `deleted_at`
- `order_id`(订单 ID
- `package_id`(套餐 ID
- `package_name`(套餐名称)
- `quantity`(数量)
- `unit_price`(单价,分)
- `amount`(小计金额,分)
### 索引设计
```sql
-- tb_order
CREATE UNIQUE INDEX idx_order_no ON tb_order(order_no);
CREATE INDEX idx_buyer ON tb_order(buyer_type, buyer_id);
CREATE INDEX idx_payment_status ON tb_order(payment_status);
CREATE INDEX idx_iot_card ON tb_order(iot_card_id);
CREATE INDEX idx_device ON tb_order(device_id);
-- tb_order_item
CREATE INDEX idx_order_id ON tb_order_item(order_id);
CREATE INDEX idx_package_id ON tb_order_item(package_id);
```
## 代码结构
### Store 层
**OrderStore** (`internal/store/postgres/order_store.go`):
- `Create(ctx, order) error` - 创建订单
- `GetByID(ctx, id) (*Order, error)` - 按 ID 查询
- `GetByIDWithItems(ctx, id) (*Order, []OrderItem, error)` - 查询订单及明细
- `GetByOrderNo(ctx, orderNo) (*Order, error)` - 按订单号查询
- `Update(ctx, order) error` - 更新订单
- `UpdatePaymentStatus(ctx, id, status, paidAt) error` - 更新支付状态
- `List(ctx, req) ([]Order, int64, error)` - 分页查询
- `GenerateOrderNo() string` - 生成订单号
**OrderItemStore** (`internal/store/postgres/order_item_store.go`):
- `BatchCreate(ctx, items) error` - 批量创建明细
- `ListByOrderID(ctx, orderID) ([]OrderItem, error)` - 查询订单明细
### Service 层
**PurchaseValidationService** (`internal/service/purchase_validation/service.go`):
- `ValidateCardPurchase(ctx, cardID, packageID) error` - 验证卡购买权限
- `ValidateDevicePurchase(ctx, deviceID, packageID) error` - 验证设备购买权限
- `ValidatePackageStatus(ctx, packageID) error` - 验证套餐状态
- `GetPurchasePrice(ctx, packageID, buyerType, buyerID) (int64, error)` - 获取购买价格
**OrderService** (`internal/service/order/service.go`):
- `Create(ctx, req) (*Order, error)` - 创建订单
- `Get(ctx, id) (*OrderResponse, error)` - 获取订单详情
- `List(ctx, req) ([]OrderResponse, int64, error)` - 获取订单列表
- `Cancel(ctx, id) error` - 取消订单
- `WalletPay(ctx, id, req) error` - 钱包支付
- `HandlePaymentCallback(ctx, orderNo, paymentMethod) error` - 处理支付回调
### Handler 层
**AdminOrderHandler** (`internal/handler/admin/order.go`):
- `Create(c)` - 创建订单
- `Get(c)` - 获取订单详情
- `List(c)` - 获取订单列表
- `Cancel(c)` - 取消订单
**H5OrderHandler** (`internal/handler/h5/order.go`):
- `Create(c)` - 创建订单
- `Get(c)` - 获取订单详情
- `List(c)` - 获取订单列表
- `WalletPay(c)` - 钱包支付
**PaymentCallbackHandler** (`internal/handler/callback/payment.go`):
- `WechatPayCallback(c)` - 微信支付回调
- `AlipayCallback(c)` - 支付宝回调
## 测试覆盖
### 单元测试
**OrderStore 测试** (`order_store_test.go`):
- ✅ 创建订单
- ✅ 按 ID 查询
- ✅ 按 ID 查询(含明细)
- ✅ 按订单号查询
- ✅ 更新订单
- ✅ 更新支付状态
- ✅ 分页查询
- ✅ 生成订单号
**OrderItemStore 测试** (`order_item_store_test.go`):
- ✅ 批量创建明细
- ✅ 查询订单明细
**PurchaseValidationService 测试** (`service_test.go`):
- ✅ 验证卡购买(成功/卡不存在/套餐系列不匹配/套餐未上架)
- ✅ 验证设备购买(成功/设备不存在/套餐系列不匹配)
- ✅ 获取购买价格(个人客户零售价/代理成本价)
**OrderService 测试** (`service_test.go`):
- ✅ 创建单卡订单
- ✅ 创建设备订单
- ✅ 获取订单详情
- ✅ 获取订单列表
- ✅ 取消订单
- ✅ 钱包支付(成功/订单不存在/无权操作/重复支付)
### 集成测试
- ✅ 编译验证:`go build ./...`
- ✅ 服务启动验证
- ✅ OpenAPI 文档生成验证
## 验证结果
### 编译验证
```bash
✅ go build ./... 编译通过
```
### 服务启动
```bash
✅ ./api 启动成功
✅ /health 健康检查通过
```
### OpenAPI 文档
```yaml
✅ /api/admin/orders 路由已生成
✅ /api/h5/orders 路由已生成
✅ /api/callback/wechat-pay 路由已生成
✅ /api/callback/alipay 路由已生成
```
### 测试通过率
```bash
✅ OrderStore 单元测试8/8 通过
✅ OrderItemStore 单元测试4/4 通过
✅ PurchaseValidationService 测试3/3 通过
✅ OrderService 测试6/6 通过
```
## 使用指南
### 创建订单(单卡购买)
**请求**
```http
POST /api/h5/orders
Authorization: Bearer {token}
Content-Type: application/json
{
"order_type": 1,
"iot_card_id": 101,
"package_ids": [201, 202]
}
```
**响应**
```json
{
"code": 0,
"data": {
"id": 1001,
"order_no": "ORD202601281234567890",
"order_type": 1,
"buyer_type": 1,
"buyer_id": 301,
"iot_card_id": 101,
"total_amount": 39900,
"payment_status": 1,
"items": [
{
"id": 2001,
"package_id": 201,
"package_name": "月套餐 3000G",
"quantity": 1,
"unit_price": 19900,
"amount": 19900
}
]
},
"msg": "success"
}
```
### 钱包支付
**请求**
```http
POST /api/h5/orders/1001/wallet-pay
Authorization: Bearer {token}
Content-Type: application/json
{
"payment_method": 1
}
```
**响应**
```json
{
"code": 0,
"msg": "支付成功"
}
```
### 查询订单列表
**请求**
```http
GET /api/h5/orders?payment_status=2&page=1&page_size=20
Authorization: Bearer {token}
```
**响应**
```json
{
"code": 0,
"data": {
"list": [...],
"total": 100
},
"msg": "success"
}
```
## 依赖关系
**依赖**
- Phase 3add-card-device-series-binding- 卡/设备套餐系列关联
- Wallet 模型 - 钱包余额管理
**被依赖**
- Phase 5add-one-time-commission- 一次性佣金计算
## 后续优化
1. **支付集成**:完成微信支付、支付宝支付的真实对接
2. **订单超时**:实现订单超时自动取消机制
3. **支付重试**:处理支付失败的重试逻辑
4. **退款流程**:实现订单退款功能
5. **发票管理**:支持开具电子发票
## 相关文档
- [提案文档](../../openspec/changes/add-order-payment/proposal.md)
- [设计文档](../../openspec/changes/add-order-payment/design.md)
- [任务清单](../../openspec/changes/add-order-payment/tasks.md)
- [项目规范](../../AGENTS.md)