# API Contract: 平台订单状态受控调整 **Base path**: `/system/order` **Authentication**: RuoYi 管理后台 Bearer token **Permission**: `system:order:edit` **Content-Type**: `application/json` ## 1. Common Conventions ### 1.1 Response envelope 沿用 `AjaxResult`: ```json { "code": 200, "msg": "操作成功", "data": {} } ``` 业务校验、状态冲突或网关失败沿用项目约定返回非 200 的业务 `code` 与安全错误消息。前端不得只根据 HTTP 状态推断业务成功。 ### 1.2 Snapshot fields 所有写操作必须携带弹窗打开时的四状态快照: ```json { "expectedState": 2, "expectedDeliveryStatus": 2, "expectedPayStatus": 1, "expectedAfterSaleStatus": 0, "reason": "骑手端漏操作" } ``` `reason` trim 后 1~200 字。`expectedDeliveryStatus` 可为 `null`,其他快照字段必填。 ### 1.3 Conflict behavior 如果订单在上下文读取后被回调、骑手、定时任务或另一管理员更新: ```json { "code": 500, "msg": "订单状态已变化,请刷新后重试" } ``` 冲突请求不得产生订单、账单、积分、日志或推送副作用。 ## 2. Get Status Context ### `GET /system/order/{id}/status-context` 读取修改弹窗所需的最小数据和服务端能力。 **Response**: ```json { "code": 200, "data": { "id": 123, "ddId": "202608100001", "type": 0, "payType": "7", "state": 2, "deliveryStatus": 2, "payStatus": 1, "afterSaleStatus": 0, "sdTime": null, "riderAssigned": true, "allowedOrderStates": [2], "allowedDeliveryStatuses": [2, 3], "canConfirmOfflinePayment": false, "canConfirmOfflineRefund": false, "canReconcileOmg": false, "canRefundOmg": true, "canConfirmManualOmgRefund": false, "omgPaymentStatus": 1, "manualRefundPending": false, "refundUnknown": false } } ``` 规则: - `allowed*` 至少包含当前值,终态只有当前值; - 自取/堂食 `allowedDeliveryStatuses=[]`; - OMG 无流水时 `omgPaymentStatus=null` 且所有 OMG 写能力为 false; - 能力字段只控制显示,不能作为后端授权依据。 ## 3. Update Order or Delivery Status ### `PUT /system/order/{id}/status` **Annotations**: `@PreAuthorize(system:order:edit)`、`@RepeatSubmit` **Request**: ```json { "expectedState": 2, "expectedDeliveryStatus": 2, "expectedPayStatus": 1, "expectedAfterSaleStatus": 0, "targetState": null, "targetDeliveryStatus": 3, "reason": "骑手已实际送达,客户端漏操作" } ``` 至少一个目标字段非空且与当前值不同。未提交的目标字段保持不变。 **Success response**: 返回最新状态上下文。 ```json { "code": 200, "msg": "修改成功", "data": { "id": 123, "state": 3, "deliveryStatus": 3, "payStatus": 1, "afterSaleStatus": 0, "sdTime": "2026-08-10 11:30:00" } } ``` ### Validation - `targetState` 只能是 0~4;`targetDeliveryStatus` 只能是 0~3; - 只能执行 [data-model.md](../data-model.md) 中的下一步转换; - 非外送不得提交配送目标; - 配送到 1/2/3 要求已有骑手; - 完成要求已支付且无售后; - 已支付取消返回“请使用退款操作”; - 送达自动同步完成和送达时间;客户端不能提交 `sdTime`。 ## 4. Confirm Offline Payment ### `POST /system/order/{id}/offline-payment/confirm` **Request**: Common snapshot fields only. ```json { "expectedState": 2, "expectedDeliveryStatus": null, "expectedPayStatus": 0, "expectedAfterSaleStatus": 0, "reason": "门店确认已收到现金" } ``` **Allowed**: - `payType="1"`; - `payStatus=0`; - `state` 不是 3/4; - `afterSaleStatus=0`。 **Result**: 只把 `payStatus` 改为 1,不创建 OMG/历史在线支付流水,不自动完成订单。 ## 5. Confirm Offline Refund ### `POST /system/order/{id}/offline-refund/confirm` 此操作代表管理员已经在线下实际退回款项,前端必须先显示不可逆二次确认。 **Request**: ```json { "expectedState": 1, "expectedDeliveryStatus": null, "expectedPayStatus": 1, "expectedAfterSaleStatus": 0, "reason": "门店无法履约,已现场退还现金" } ``` **Allowed**: - `payType="1"`; - `payStatus=1`; - `state!=3`; - `afterSaleStatus=0`。 **Atomic result**: ```json { "state": 4, "payStatus": 2, "afterSaleStatus": 3 } ``` 订单使用积分时调用现有幂等积分返还逻辑。已完成订单返回“已完成订单需结算冲正,本期不支持退款”。 ## 6. Reconcile OMG Payment ### `POST /system/order/{id}/omg-payment/reconcile` **Request**: Common snapshot fields. **Allowed**: - `payType="7"`; - `payStatus=0`; - `state!=4`; - 存在最新 OMG 支付流水且流水可查询。 **Behavior**: 1. 复用 `reconcileByQuery(ddId, "admin")`; 2. 校验 OMG 返回交易状态、交易号、金额和日期; 3. 只有真实已支付时通过流水 CAS 和订单 CAS 核销; 4. 未支付、失败、未知或字段不符时不得写 `order.payStatus=1`; 5. 回调和补单并发只执行一次支付副作用。 **Paid response**: ```json { "code": 200, "msg": "OMG 支付核验成功", "data": { "payStatus": 1, "reconciled": true } } ``` **Still unpaid response**: ```json { "code": 200, "msg": "OMG 尚未确认支付", "data": { "payStatus": 0, "reconciled": false } } ``` 金额/订单信息不一致返回错误并写系统核对日志,不向前端返回原始网关报文。 ## 7. Refund OMG Payment ### `POST /system/order/{id}/omg-refund` 前端必须显示不可逆二次确认。 **Request**: Common snapshot fields. **Allowed**: - `payType="7"`; - `payStatus=1`; - `state!=3`; - `afterSaleStatus=0`; - 最新 OMG 流水为已支付,金额和订单一致。 ### Outcomes #### Automatic refund succeeded ```json { "code": 200, "msg": "退款成功", "data": { "outcome": "REFUNDED", "state": 4, "payStatus": 2, "afterSaleStatus": 3 } } ``` #### Manual refund required ```json { "code": 200, "msg": "该支付方式需在 OMG 后台人工退款,订单暂保持已支付", "data": { "outcome": "MANUAL_PENDING", "payStatus": 1, "manualRefundPending": true } } ``` 重复调用不重复新增人工待办。 #### Result unknown ```json { "code": 500, "msg": "OMG 退款结果待确认,请勿重复发起", "data": { "outcome": "UNKNOWN", "payStatus": 1, "refundUnknown": true } } ``` OMG 流水保持退款中,后续请求不得再次调用网关。 #### Explicit failure ```json { "code": 500, "msg": "OMG 退款失败,请核对后重试", "data": { "outcome": "FAILED", "payStatus": 1 } } ``` 错误消息不包含 HashKey、HashIV、完整响应或堆栈。 ## 8. Confirm Manual OMG Refund ### `POST /system/order/{id}/omg-refund/manual-confirm` 此接口只用于支付方式不支持 OMG 退款 API、平台管理员已经在 OMG 后台核实退款完成的场景。前端必须进行不可逆二次确认。 **Request**: Common snapshot fields;`reason` 必须说明人工退款核实依据。 **Allowed**: - `payType="7"`、订单 `payStatus=1`、`state!=3`、`afterSaleStatus=0`; - 最新 OMG 流水属于不支持自动退款的支付方式; - 同一支付流水存在 `action=NULL, rtnCode=NULL` 的未决人工待办; - 不存在 `action=NULL, rtnCode=1` 的人工完成记录。 **Behavior**: 1. 管理员二次确认; 2. OMG 支付流水复用现有 CAS 执行 `1→4→3`,不调用网关; 3. 插入 `action=NULL, rtnCode=1` 的人工退款完成记录,`rtnMsg` 标明管理员确认; 4. 同步 `state=4, payStatus=2, afterSaleStatus=3`; 5. 写管理员操作日志,包含管理员、前后状态和原因。 **Response**: ```json { "code": 200, "msg": "已确认 OMG 人工退款完成", "data": { "outcome": "REFUNDED", "state": 4, "payStatus": 2, "afterSaleStatus": 3, "manualRefundPending": false } } ``` 无待办、已完成订单、流水状态变化或重复确认均不得产生新的退款记录或业务副作用。 ## 9. Legacy Endpoint Hardening ### `PUT /system/order` 继续保留历史非状态编辑能力,但请求含以下任意字段时拒绝: - `state` - `deliveryStatus` - `payStatus` - `afterSaleStatus` - `sdTime` **Response**: ```json { "code": 500, "msg": "订单状态请使用状态专用操作" } ``` 旧 `state=7/12`、`kefuState→state=5` 等逻辑必须删除。 ## 10. Forbidden Payload Fields 新状态接口 DTO 不声明并不得使用以下字段: ```text amount, userId, shId, mdId, food, freight, points, payType, type, qsId, sdTime, payStatus, afterSaleStatus ``` 其中 `expectedPayStatus`、`expectedAfterSaleStatus` 仅作为旧快照,不是目标值。服务端必须从数据库读取订单类型、支付类型、金额、骑手和业务标识。 ## 11. Audit Requirements 每次实际改变数据的管理员操作写一条 `operatorType=1` 的同步订单日志,包含: - 管理员 ID/名称; - 动作类型; - 四状态前后值; - 管理员原因; - 服务端时间。 相同目标的幂等请求不重复写成功日志。OMG 补单/退款还保留现有 `operatorType=0` 系统资金日志。