核销自动确认收货 — 设计与验证
文档类型:功能归档 | 日期:2026-06-12 | 关联 ticket:TicketService 核销流程
背景
核销流程中,需要将确认收货步骤自动化:核销成功后自动触发确认收货。此前该功能作为 TODO 存在于代码中,本轮完成实现并验证。
原始需求
- 核销后自动确认收货
- 仅待收货状态(status=3)才触发确认收货
- 如果已经是确认收货状态(status=4),幂等返回成功,不报错
- 如果确认收货失败,回滚核销操作(整个操作在事务内)
- 并发安全:使用悲观锁防并发
- 短码核销(
verifyByShortCode)复用同一逻辑
- 已确认收货但未核销的情况,核销流程正常走通(autoConfirmOrder 返回 code=0 兜底)
技术方案
核心流程图
verifyTicket / verifyTicketById
│
├─ 悲观锁查票(SELECT ... lock(true))
├─ 核销票(标记 status=1)
├─ autoConfirmOrder()
│ ├─ 悲观锁查订单(SELECT ... lock(true))
│ ├─ status==4 → return code=0(幂等)
│ ├─ status!=3 → return code=0(跳过)
│ └─ status==3 → 调用订单API确认收货
├─ 如 autoConfirmOrder 失败 → throw \Exception → 事务回滚
└─ commit
关键设计决策
| 决策 |
理由 |
不记录核销人与电话号码(staffId, staffMobile) |
设计文档未提及,避免过度实现 |
autoConfirmOrder 不做 null 直接返回 |
缺乏需求约束,静默丢弃会产生不可追踪的数据不一致 |
使用 if/elseif 而非守卫语句 |
符合仓库现有风格 |
| 悲观锁 |
防止并发场景下同一订单被重复确认收货 |
改动清单
[MODIFY] shopxo/app/plugins/vr_ticket/service/TicketService.php
| 位置 |
改动 |
verifyTicket(L314附近) |
核销后调用 autoConfirmOrder,失败则抛异常回滚 |
verifyTicketById(L474附近) |
同上,在事务末尾调用 autoConfirmOrder |
autoConfirmOrder(L614-L657) |
新增方法:悲观锁查单 → 状态判断 → 幂等/跳过/确认收货 |
verifyTicket 事务内调用
// 在 verifyTicket 事务内,核销完成后
$auto_confirm_ret = $this->autoConfirmOrder($order_id);
if ($auto_confirm_ret['code'] != 0) {
throw new \Exception($auto_confirm_ret['msg']);
}
autoConfirmOrder 方法签名
/**
* 核销后自动确认收货
* - 使用悲观锁防并发
* - status==4:幂等返回成功
* - status!=3:跳过
* @return array ['code'=>0/1, 'msg'=>...]
*/
private function autoConfirmOrder($order_id): array
GitNexus 变更报告
| 指标 |
值 |
| 变更符号数 |
4(autoConfirmOrder 新增 + verifyTicket/verifyTicketById/verifyByShortCode 受影响) |
| 变更文件数 |
1(TicketService.php) |
| 受影响执行流程 |
0 |
| 风险级别 |
LOW |
测试策略
单元测试(验证码核销 + 待收货订单)
请求:
POST /api.php?s=plugins/index&pluginsname=vr_ticket&pluginscontrol=verifier&pluginsaction=verify
code=<验证码> status=<状态>
用例1:待收货订单核销(核心路径)
| 步骤 |
预期 |
| 1. 手机端下单票品 |
订单 status=3(待发货/待收货) |
| 2. 使用核销码核销 |
票 status=1,订单 status=4 |
| 3. 检查订单详情 |
确认收货时间已记录 |
用例2:已确认收货订单核销(幂等)
| 步骤 |
预期 |
| 1. 使用已确认收货订单的核销码核销 |
票正常核销,不报错 |
用例3:手动确认收货后再核销
| 步骤 |
预期 |
| 1. 手动确认收货(status 3→4) |
成功 |
| 2. 使用核销码核销 |
票正常核销,autoConfirmOrder 幂等返回 |
用例4:并发核销
| 步骤 |
预期 |
| 1. 两个核销请求几乎同时到达 |
只有一个成功,另一个因悲观锁或票状态被拒绝 |
验证结果
| # |
需求 |
实现位置 |
状态 |
| 1 |
核销后自动确认收货 |
verifyTicket L314 + verifyTicketById L474 调用 autoConfirmOrder |
✅ |
| 2 |
仅待收货才处理 |
autoConfirmOrder L635: if ($order['status'] != 3) → 跳过 |
✅ |
| 3 |
已确认收货幂等 |
autoConfirmOrder L629: if ($order['status'] == 4) → 返回 success |
✅ |
| 4 |
确认失败回滚核销 |
$confirm_ret['code'] != 0 → throw \Exception,事务回滚 |
✅ |
| 5 |
悲观锁防并发 |
订单查询 ->lock(true) (L620),票查询 ->lock(true) (L257, L417) |
✅ |
| 6 |
短码核销继承 |
verifyByShortCode → 委托 verifyTicketById |
✅ |
| 7 |
已收货+未核销正常走 |
autoConfirmOrder 返回 code=0,核销不受影响 |
✅ |
未实现项(明确排除)
| 项 |
原因 |
| 核销时记录核销人(staffId) |
需求文档未提及 |
| 核销时记录电话号码(staffMobile) |
需求文档未提及 |
autoConfirmOrder 对 $order == null 的静默处理 |
没有需求约束,不做猜测性实现 |
未来增强建议
- 核销记录表:如需追溯核销人,可扩展
vr_ticket_verification 表增加 staff_id/staff_mobile 字段
- 异步确认收货:如确认收货 API 耗时较长,可考虑队列化处理以提高核销吞吐
- 核销回调钩子:在
verifyTicket 完成后增加后置钩子(如发送核销通知),使扩展点更清晰