# 第 5 批接口移植:订单事件 + 发货 + 售后 > 时间:2026-10-17 > 范围:订单运营主链路(改价/备注/改地址/取消审核/删除)、发货单(列表/详情/发货/物流轨迹)、售后(列表/详情/审核/收货退款) ## 一、本批完成的接口(13 个) | 接口 | 方法 | 说明 | |---|---|---| | `/store/order.event/updatePrice` | POST | 改价:实付款 = 订单金额 + 运费,记录改价差价 | | `/store/order.event/updateRemark` | POST | 商家备注(空值写空串) | | `/store/order.event/updateAddress` | POST | 改收货地址(name/phone/cascader/detail) | | `/store/order.event/confirmCancel` | POST | 审核用户取消:同意则退款 + 回退库存/优惠券/积分 | | `/store/order.event/delete` | POST | 订单软删除(is_delete=1) | | `/store/order.delivery/list` | GET | 发货记录(paginate(10),排除 eorder_html) | | `/store/order.delivery/detail` | GET | 发货单详情(只主表字段) | | `/store/order.delivery/delivery` | POST | 确认发货(手动录入 / 无需物流 / 整单 / 分包) | | `/store/order.delivery/traces` | GET | 物流轨迹(快递100) | | `/store/order.refund/list` | GET | 售后列表(join 订单号,检索订单号/昵称/会员ID) | | `/store/order.refund/detail` | GET | 售后详情(orderData/images.file/orderGoods.image/express/address/user) | | `/store/order.refund/audit` | POST | 商家审核(同意/拒绝,同意时复制退货地址) | | `/store/order.refund/receipt` | POST | 确认收货并退款(限额校验 + 余额退款 + 流水) | store 模块进度:**151 个接口已实现 104 个,剩余 47 个**。 URL 兼容:控制器对 `updatePrice` 与 `update_price` 两种写法都做了映射;前端实际调用的是驼峰写法(`src/api/order/event.js`)。 ## 二、关键实现细节(对齐 PHP 行为) ### 1. 改价 - 实付款 `pay_price = order_price + express_price`,两者皆 0 时报「订单实付款价格不能为0.00元」; - 改价差价 `update_price = 新 order_price - 原 order_price`,**原值走获取器**(库内为 0 时按 `total_price - coupon_money + update_price` 计算),不是直接读库; - 只有 `pay_status = 10(待支付)` 才能改价,否则「该订单不合法」。 ### 2. 审核取消(confirmCancel) - 前置:`pay_status = 20` 且 `order_status = 21(待取消)`; - `status` 为真:先退款,再走取消事件(回退商品总库存 + SKU 库存、优惠券 `is_use=0`、积分抵扣退回并写积分流水),最后 `order_status = 20`; - `status` 为假:仅把订单打回 `order_status = 10`。 ### 3. 发货 - 判定条件:`pay_status = 20` 且 `delivery_status != 20`,否则「订单号[xxx]不满足发货条件!」; - `isAllPack=true` 时自动取所有未发货商品(数量 = `total_num - delivery_num`); - 发货方式 20(无需物流)时物流单号强制写空; - 写「发货单 + 发货商品 + 订单商品累计发货数」,再按「是否全部发完」把订单置为已发货(20) / 部分发货(30) 并写 `delivery_time`; - 只有 `order_type = 10(实物)` 才写发货单。 ### 4. 售后 - 审核:拒绝必须填 `refuse_desc`,同意必须选 `address_id`;同意换货(type=20)直接置为已完成(20);同意退货退款则从商城收货地址复制一份到 `order_refund_address`; - 收货退款:`refund_money ≤ min(订单实付款, 订单商品实付款)`;余额支付走「加余额 + scene=40 流水(订单退款:{订单号})」;`is_settled=1` 时同步扣减会员 `expend_money`。 ### 5. 出参字段集合的两处坑 - 发货列表里的 `orderData` 只 with 了 `user`、`address`,**没有** `trade` 键,`user` 上也没有 `avatar/avatar_url`。订单实体默认会输出这些键,本批改为服务端用 `ObjectNode` 精确裁剪; - 订单详情的 `user` 未加载 `avatar` 关联,同样需要裁掉 `avatar/avatar_url`(列表接口则保留,两者不一致是 PHP 侧的真实行为)。 ### 6. 关联键名 `orderData`、`orderGoods`、`orderNo` 这类驼峰关联名会被全局 SNAKE_CASE 转换,已用 `@JsonProperty` 固定(`order_data` / `order_goods` 是错的)。 ## 三、验证结果 ### 1. 读接口逐字段对比(`tools/compare_order_batch.py`,**10/10 PASS,0 差异**) `order.delivery/list`(含筛选、空结果)、`order.delivery/detail`、`order/detail`×2、`order/list`、`order.refund/list`、`order.refund/detail`(不存在时)、`order.delivery/traces`(未开启物流查询时的报错文案)。 > 踩坑记录:PHP 侧 URL 是 `index.php?s=/store/xxx/yyy`,追加参数必须用 `&`,写成 `?page=1` 会被当成方法名的一部分,返回 `method not exists:...?page=1()`。 ### 2. 写接口端到端(`tools/test_order_writes.py`,**77 项断言全通过**) 现场造订单/售后单 → 调 Java 接口 → 直接查库断言 → 清理恢复,覆盖: - 改价成功 + 3 个非法分支(已支付、实付款为 0); - 备注写入/清空、改地址 4 个字段; - 软删除后详情不可见、列表不返回,再恢复; - 取消审核:拒绝(不动余额)/ 同意(余额 +31、scene=40 流水、商品库存 +1、SKU 库存 +1); - 发货:分包发货(部分发货 30)→ 整单发货 + 无需物流(已发货 20,单号为空)→ 重复发货被拒 → 未支付订单发货被拒;发货列表/详情字段集合校验; - 售后:拒绝无原因、同意无地址两个校验分支;同意后写退货地址;超额退款拦截;正常退款(余额 +10、流水 scene=40);换货同意即完成;拒绝后状态=10 且不写地址。 ### 3. 回归 `tools/compare_batch.py` 28 个既有接口 **28/28 PASS**。 ## 四、本批的已知限制(切流前需补) 1. **微信小程序发货信息同步**未接入:PHP 在 `platform=MP_WEIXIN && pay_method=wechat` 且订单全部发货时会调微信 API 上报物流;本批跳过该调用,`sync_weixin_shipping` 写 0。 2. **第三方支付退款**(微信/支付宝)未接入:需要商户证书与支付配置,当前对这两类支付方式抛「第三方支付退款暂未接入,请先在商户平台手动退款」;余额支付退款已完整实现(线上当前订单以余额支付为主)。 3. **物流轨迹**只接了快递100,阿里云驱动未实现;轨迹缓存在进程内(PHP 用 Cache 60 分钟),重启即失效,语义一致但非共享缓存。 4. `checkPrivilege`(按角色校验 API 权限)仍未实现,目前只校验登录态。 ## 五、下一步 剩余 47 个接口,建议顺序: 1. **设置类**:支付设置(options/update、支付模板 CRUD)、配送模板、运费模板、快递公司、打印机 CRUD(约 20 个,后台「设置」菜单几乎全空); 2. **营销**:优惠券详情/新增/编辑/删除/领取、充值方案增删改(8 个); 3. **会员**:等级增删改、会员删除/充值、充值订单(5 个); 4. **内容/统计**:商品评论 CRUD、客户端设置(h5/wxapp)、统计接口、`controller/initialize`、订单导出(约 14 个)。