diff --git "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/process/wqq/C03-\350\256\242\345\215\225\350\266\205\346\227\266\346\265\201\347\250\213.md" "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/process/wqq/C03-\350\256\242\345\215\225\350\266\205\346\227\266\346\265\201\347\250\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..572be1465852090b034ba6c888b1aeb13f0adedc --- /dev/null +++ "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/process/wqq/C03-\350\256\242\345\215\225\350\266\205\346\227\266\346\265\201\347\250\213.md" @@ -0,0 +1,187 @@ +# C03 订单超时自动取消流程 + +> 负责人:韦乾强 +> 覆盖:C03 订单超时自动取消 +> 基础核心流程:M04 订单状态机(F08/F09)、M05 支付(F10) +> 直接协作:罗皓晨(M00 Worker 基础设施)、张海洋(M05 支付)、朱惠惠(M03 购物车) +> 文档状态:修订中 +> 需求事实源:[需求规格说明书 C03 订单超时自动取消](../../../01-需求文档/需求规格说明书.md) 的"C03 订单超时自动取消"完整章节 + +## 一、范围与事实来源 + +本挑战模块负责在买家未按时支付时,由系统自动取消订单并回补库存。核心目标是"超时即取消、取消即回补、并发安全"。Worker 定时扫描 PendingPayment 订单,对超时订单执行与买家主动取消相同的状态变更和库存回补逻辑。 + +本文先确认超时触发条件、超时时间配置、扫描策略、与支付模块的状态竞争处理,再登记对 M04 订单状态机的复用边界和对 M09 站内消息的输出接口。 + +| 设计对象 | 当前成熟度 | 本文处理 | +|---|---|---| +| C03 需求 | 完整定义 | 作为超时取消业务语义事实源 | +| 超时时间配置 | 正式环境 30 分钟,演示环境可配置 | 由部署配置注入,不硬编码 | +| 超时扫描与取消事务 | 修订中 | 修正重试语义矛盾、库存通道区分、锁策略 | +| 与支付的状态竞争 | 完整定义 | 条件更新保证幂等 | +| 站内消息通知 | M09 承载 | 登记输出事件类型和消息内容要求 | + +## 二、基础核心流程依赖 + +```mermaid +flowchart LR + ORDER["M04 Ordering
PendingPayment 订单创建"] --> TIMEOUT["C03 超时 Worker
定时扫描 PendingPayment 订单"] + ORDER --> PAY["M05 Payment
买家主动支付"] + TIMEOUT -->|"超时取消事务|回补库存"| STOCK["M02 Catalog
库存回补"] + TIMEOUT -->|"OrderCancelledIntegrationEvent
超时取消通知"| MSG["M09 站内消息"] + PAY -->|"OrderPaidIntegrationEvent
支付成功通知"| MSG + MSG -->|"超时取消通知
买家站内消息"| BUYER["买家消息中心"] +``` + +关键约束: + +- C03 复用 M04 的订单状态变更和库存回补逻辑,不独立发明新事务。 +- C03 由 Worker 后台任务触发,不提供买家主动接口。 +- 超时时间从订单 `created_at` 计算,不从其他时间点计算。 + +## 三、超时时间配置 + +```mermaid +flowchart TD + A["Worker 启动"] --> B["读取配置 OrderTimeoutMinutes"] + B --> C{"配置存在?"} + C -- "否" --> D["使用默认值 30 分钟"] + C -- "是" --> E["使用配置值"] + D --> F["订单超时时间已确认"] + E --> F +``` + +关键约束: + +- 超时时间通过配置项 `OrderTimeoutMinutes` 管理。 +- 正式环境默认 30 分钟。 +- 演示环境可通过配置调整为更短时间(如 5 分钟),但需说明与正式参数的对应关系。 +- 配置不得硬编码。 + +## 四、Worker 超时扫描流程 + +```mermaid +flowchart TD + A["Worker 定时触发(建议间隔 ≤ 超时时间/2)"] --> B["查询超时订单(FOR UPDATE SKIP LOCKED):status = PendingPayment AND created_at + timeout < NOW()"] + B --> C{"有待处理订单?"} + C -- "否" --> Z["本次扫描结束"] + C -- "是" --> D["按批次处理(建议每批 ≤ 100 条)"] + D --> E["对每条超时订单开启独立事务"] + E --> F["条件更新状态为 Cancelled:WHERE status = PendingPayment"] + F --> G{"影响行数 = 1?"} + G -- "否" --> H["订单已被其他操作处理(如已支付/已取消),跳过"] + G -- "是" --> I{"订单类型?"} + I -- "普通订单" --> J1["回补普通库存:stock = stock + quantity"] + I -- "秒杀订单" --> J2["回补秒杀活动库存 + 释放买家限购额度"] + J1 --> J3["记录 cancelled_at = NOW() 和 cancel_reason = TIMEOUT"] + J2 --> J3 + J3 --> K["写入 Outbox:OrderCancelledIntegrationEvent(cancel_reason = TIMEOUT)"] + K --> L["提交事务"] + L --> M{"提交成功?"} + M -- "否" --> N["记录错误日志"] + N --> O{"当前重试次数 < 3?"} + O -- "是" --> P["重试次数 +1,继续处理该订单"] + P --> E + O -- "否" --> Q["重试次数已达上限,标记失败并告警"] + M -- "是" --> R["重试次数归零,继续处理下一条"] + Q --> R + R --> S{"批次处理完成?"} + S -- "否" --> D + S -- "是" --> Z +``` + +关键约束: + +- 扫描间隔建议 ≤ 超时时间/2(如超时 30 分钟,扫描间隔 ≤ 15 分钟)。 +- 每批次处理上限 100 条,避免长时间锁表。 +- 使用条件更新 `WHERE status = 'PendingPayment'` 保证幂等。 +- 库存回补与订单状态变更、Outbox 写入处于同一事务;任一失败则整体回滚,不允许出现"订单已取消但库存未回补"的部分成功状态。 +- 单笔订单最多重试 3 次;3 次均失败后标记该订单为永久失败、发送告警,由人工介入处理,不再自动重试。 +- 重试次数记录在订单或独立调度记录中,不依赖 Worker 进程内存。 + +## 五、与支付的状态竞争处理 + +```mermaid +flowchart TD + subgraph 并发竞争 + A["C03 Worker:超时扫描并尝试取消"] --> B["条件更新状态为 Cancelled"] + C["买家:主动点击支付"] --> D["M05 支付事务:条件更新状态为 Paid"] + end + + B --> E{"乐观锁结果"} + D --> F{"乐观锁结果"} + + E -- "取消成功,行数=1" --> G["库存已回补,订单已取消"] + F -- "支付成功,行数=1" --> H["余额已扣减,订单已支付"] + E -- "行数=0:已被支付" --> I["取消跳过,返回幂等成功"] + F -- "行数=0:已被取消" --> J["支付跳过,返回余额未扣减"] +``` + +关键约束: + +- 取消与支付使用相同的条件更新 `WHERE status = 'PendingPayment'`。 +- 最终只有一个操作成功,避免"又支付又取消"的矛盾状态。 +- 两者竞争时数据库事务隔离保证最终一致性。 + +## 六、事件输出 + +```mermaid +flowchart TD + A["超时取消事务提交成功"] --> B["发布 OrderCancelledIntegrationEvent(cancel_reason = TIMEOUT)"] + B --> C["Outbox 投递到 MQ"] + C --> D["Messaging 事件总线路由"] + D --> E["M09 消费事件,生成站内消息:订单超时取消通知"] + E --> F["买家查看消息中心"] +``` + +事件内容要求: + +- 消息标题:订单超时取消 +- 消息内容:您的订单 {订单号} 因超时未支付已自动取消,库存已回补,如有需要可重新下单。 +- cancel_reason = 'TIMEOUT' 用于 M09 生成差异化文案。 + +## 七、异常与边界场景 + +| 场景 | C03 处理 | 最终状态/责任 | +|---|---|---| +| Worker 停止 | 重启后继续扫描 | 不漏扫,不重复取消 | +| 数据库连接短暂中断 | 记录错误日志,本次扫描重试 | 最多延迟一个扫描周期 | +| 扫描时订单已被支付 | 条件更新影响行数=0,跳过 | 订单保持 Paid | +| 扫描时订单已被买家取消 | 条件更新影响行数=0,跳过 | 订单保持 Cancelled | +| 库存回补时商品已删除或秒杀活动已结束 | 事务整体回滚,记录错误日志;若该订单重试次数 < 3,下次扫描继续重试;若已达 3 次,标记永久失败并告警 | 订单保留 PendingPayment,等待人工处理 | +| 多实例 Worker 并发扫描 | 扫描查询使用 SELECT FOR UPDATE SKIP LOCKED;领取与处理在同一事务内完成 | 同一订单只被一个 Worker 处理 | +| 单笔订单重试 3 次均失败 | 标记永久失败,发送告警,人工介入 | 订单保留 PendingPayment | + +## 八、与 M04 订单模块的复用关系 + +C03 复用的 M04 逻辑: + +| M04 逻辑 | C03 复用方式 | +|---|---| +| 取消条件校验 | 直接复用订单归属和状态判断 | +| 库存回补事务 | 直接复用库存回补 SQL | +| 订单状态变更 | 直接复用条件更新 SQL | +| cancelled_at 和 cancel_reason | 直接复用字段写入 | +| OrderCancelledIntegrationEvent | 复用事件结构,cancel_reason = TIMEOUT | + +C03 不改变的 M04 逻辑: + +- 订单创建逻辑 +- 订单查询逻辑 +- 买家主动取消的具体接口契约 + +## 九、验收证据清单 + +- [ ] 超时订单被自动取消,状态变为 Cancelled +- [ ] 库存正确回补,回补量 = 订单项数量 +- [ ] 普通订单回补普通库存,秒杀订单回补秒杀活动库存并释放限购 +- [ ] cancelled_at 和 cancel_reason = TIMEOUT 已写入 +- [ ] OrderCancelledIntegrationEvent(TIMEOUT)已发布到 Outbox +- [ ] 买家收到站内消息通知 +- [ ] 买家在超时前支付成功,取消被跳过 +- [ ] 并发取消与支付只有一个成功,不出现矛盾状态 +- [ ] 重复取消返回幂等成功,库存只回补一次 +- [ ] Worker 重启后继续扫描,不漏扫 +- [ ] 多实例 Worker 不重复处理同一订单 +- [ ] 单笔订单重试 3 次均失败后告警,不无限重试 +- [ ] 演示环境超时时间可配置(如 30 秒、5 分钟) diff --git "a/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/process/wqq/M04-\350\256\242\345\215\225\346\265\201\347\250\213.md" "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/process/wqq/M04-\350\256\242\345\215\225\346\265\201\347\250\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..1b6809d0538ec7e8cf560e68933a1f9515c01482 --- /dev/null +++ "b/docs/02-\350\256\276\350\256\241\346\226\207\346\241\243/process/wqq/M04-\350\256\242\345\215\225\346\265\201\347\250\213.md" @@ -0,0 +1,278 @@ +# M04 订单流程 + +> 负责人:韦乾强 +> 覆盖:M04-01 提交订单(F08)、M04-02 订单列表与详情(F09)、M04-03 取消订单(F09) +> 基础核心流程:M01 身份与鉴权、M02 分类与商品、M03 购物车、M05 支付、M06-02 后台订单管理 +> 直接协作:朱惠惠(M03 购物车)、张海洋(M05 支付)、顾欣月(M02 商品)、罗皓晨(M09 站内消息) +> 文档状态:修订中 +> 需求事实源:[需求规格说明书 M04 订单模块](../../../01-需求文档/需求规格说明书.md) 的"M04-01、M04-02、M04-03"完整七节 + +## 一、范围与事实来源 + +本模块负责买家在已登录态下完成订单创建、订单查询、订单取消,以及商家在后台完成订单发货。订单模块承担"交易确认与履约的核心状态机",连接买家购物车结算、支付模拟、商家履约和管理员监督。 + +本文先依据需求确定业务参与者、上游输入、状态派生口径、原子结果和模块出入口,再由这些动作派生接口契约。A301~A308 只用于流程完成后的契约映射与缺口检查,不能反向拼接流程,也不能用现有 Axxx 反向覆盖业务语义。 + +| 设计对象 | 当前成熟度 | 本文处理 | +|---|---|---| +| M04-01/F08、M04-02/F09、M04-03/F09 需求 | 完整定义 | 作为订单业务语义事实源 | +| 本文业务流程 | 修订中 | 修正库存条件语义、Mermaid 围栏、事件消费路径 | +| A301~A308 接口 | 部分定义、未冻结 | 由流程派生并做映射;冲突记为接口待评审项 | +| DBxxx(orders、order_items) | 模板/占位 | 本文不发明字段、约束和索引 | +| C03 订单超时取消 | 独立扩展 | 登记边界,不混入 F08/F09 主流程 | +| M06-02 后台订单管理 | F12 基础履约 | 仅登记与 M04 共享的订单状态机边界 | + +## 二、模块直接出入口 + +```mermaid +flowchart LR + ID["M01 Identity
已认证买家、角色、账号状态"] -->|"BuyerOnly 通过"| ORD["M04 Ordering
订单状态机:PendingPayment → Paid → Shipped → Completed"] + CART["M03 Cart
已选中条目、服务端金额"] -->|"提交结算:商品+数量+地址+幂等键"| ORD + CAT["M02 Catalog
实时价格、实时库存、上架状态"] -->|"价格快照、库存扣减/回补"| ORD + PAY["M05 Payment
模拟支付状态、余额"] -->|"支付成功/失败、余额变化"| ORD + ORD -->|"订单列表、详情、取消/确认动作"| BUYER["买家订单页"] + ORD -->|"发货操作、物流信息"| MERCHANT["商家后台订单页"] + ORD -->|"站内消息通知"| MSG["M09 站内消息"] + MSG -->|"订单状态变更通知"| BUYER + MSG -->|"待发货通知"| MERCHANT + + ID -. "买家取消 / 超时取消".-> ORD + CAT -. "商品下架/改价".-> ORD +``` + +边界约束: + +- M04 不接受前端传入最终金额,订单总额由服务端按快照计算。 +- M04 库存扣减/回补在事务内完成,使用条件更新防止超卖。 +- M04 不承担购物车、支付、站内消息的内部状态,只消费外部输入并输出确定性结果。 +- C03 超时取消复用 M04 的状态机与库存回补逻辑,但由 Worker 触发,不走买家主动接口。 + +## 三、提交订单(M04-01 / F08) + +```mermaid +flowchart TD + ID["M01:已认证且状态正常的买家"] --> A["买家从购物车提交订单:商品 ID 列表 + 地址 ID + 幂等键"] + CART["M03 Cart:读取选中条目"] --> B + CAT["M02 Catalog:读取实时价格、实时库存、上架状态"] --> B + A --> B{"全部商品可售且库存充足?"} + B -- "否" --> X["整单拒绝,返回问题商品和当前可购数量"] + B -- "是" --> C{"地址归属当前买家?"} + C -- "否" --> Y["拒绝:地址无效"] + C -- "是" --> D["服务端计算订单总额 = Σ(实时单价 × 数量)"] + D --> E["开启订单创建事务"] + E --> F["条件扣减库存:商品可售 AND 库存充足"] + F --> G["创建订单主记录(PendingPayment)+ 订单项快照"] + G --> H["解析并保存 assignedMerchantUserId"] + H --> I["删除已下单的购物车条目"] + I --> J["写入 Outbox:OrderCreatedIntegrationEvent"] + J --> K{"事务提交成功?"} + K -- "否" --> R["库存回滚、订单不创建"] + K -- "是" --> L["返回订单号、应付金额、PendingPayment 状态"] +``` + +关键约束: + +- 同一幂等键 `(buyer_id, idempotency_key)` 只创建一张订单,重复请求返回首次成功结果。 +- 库存扣减在事务内使用商品可售且库存充足的条件更新,避免并发超卖和商品下架后仍被下单。 +- 订单项保存商品名称、图片、成交单价快照,后续改价不影响已有订单。 +- 地址保存快照,后续修改不影响已有订单。 +- 订单金额由服务端计算,不接受客户端传入。 +- 购物车清理在同事务内完成;清理失败时事务整体回滚,库存不扣减,订单不创建。 + +## 四、订单列表与详情(M04-02 / F09) + +### 4.1 订单列表 + +```mermaid +flowchart TD + ID["M01:已认证买家"] --> A["买家请求订单列表:分页 + 可选状态筛选"] + A --> B["按 buyer_id = 当前用户过滤"] + B --> C["按状态筛选(PendingPayment/Paid/Shipped/Completed/Cancelled)"] + C --> D["按创建时间倒序返回列表"] + D --> E["返回订单号、状态、总额、创建时间、商品摘要"] +``` + +关键约束: + +- 买家只能查看本人订单,按 buyer_id 过滤。 +- 订单号可脱敏展示,商品摘要最多 3 个。 +- 分页参数 page ≥ 1,pageSize 默认 10,上限 50。 + +### 4.2 订单详情 + +```mermaid +flowchart TD + ID["M01:已认证买家"] --> A["买家请求订单详情:orderId"] + A --> B{"订单存在且归属当前买家?"} + B -- "否" --> X["返回 404 或 403,不泄露归属"] + B -- "是" --> C["返回完整订单信息"] + C --> D["地址快照:收件人、手机号(脱敏)、省市区、详细地址"] + D --> E["订单项快照:商品名称/图片/单价/数量/小计"] + E --> F["状态时间线:创建/支付/发货/完成/取消时间"] + F --> G["可用操作入口:根据状态展示 cancel/confirm/pay"] +``` + +关键约束: + +- 订单项为快照,不读取商品实时价格。 +- 地址为快照,不读取地址实时状态。 +- 状态时间线展示所有状态变更节点。 + +## 五、取消订单(M04-03 / F09) + +```mermaid +flowchart TD + ID["M01:已认证买家"] --> A["买家请求取消订单:orderId"] + A --> B{"订单存在且归属当前买家?"} + B -- "否" --> X["返回 404 或 403"] + B -- "是" --> C{"订单状态为 PendingPayment?"} + C -- "否" --> Y["返回 409:状态不允许取消"] + C -- "是" --> D["开启取消事务"] + D --> E["条件更新状态为 Cancelled:WHERE status = PendingPayment"] + E --> F{"订单类型?"} + F -- "普通订单" --> G1["回补普通库存:stock = stock + quantity"] + F -- "秒杀订单" --> G2["回补秒杀活动库存 + 释放买家限购额度"] + G1 --> H["记录 cancelled_at 和 cancel_reason = BUYER_CANCELLED"] + G2 --> H + H --> I["写入 Outbox:OrderCancelledIntegrationEvent"] + I --> J{"事务提交成功?"} + J -- "否" --> R["返回错误,不回补库存"] + J -- "是" --> K["返回取消成功"] +``` + +关键约束: + +- 只有 PendingPayment 状态可取消。 +- 使用条件更新 `WHERE status = 'PendingPayment'` 保证幂等。 +- 重复取消返回成功,不重复回补库存。 +- 取消事务回滚时库存不变化。 + +## 六、与支付模块的协作(M05) + +```mermaid +flowchart TD + PENDING["PendingPayment 订单"] --> PAY["M05 Payment:买家确认支付"] + PAY --> A{"余额充足?"} + A -- "否" --> X["提示余额不足,引导充值"] + A -- "是" --> B["开启支付事务"] + B --> C["扣减钱包余额"] + C --> D["写入 payment 记录"] + D --> E["更新订单状态为 Paid"] + E --> F["写入 Outbox:OrderPaidIntegrationEvent"] + F --> G{"事务提交成功?"} + G -- "否" --> R["余额回滚,订单保持 PendingPayment"] + G -- "是" --> H["返回支付成功"] + H --> I["Messaging 消费 OrderPaidIntegrationEvent,触发 M09 站内消息"] +``` + +关键约束: + +- 支付使用条件更新 `WHERE status = 'PendingPayment'` 保证幂等。 +- M05 与 M04 的状态更新共享同一 PendingPayment 条件,避免"又支付又取消"。 +- 支付成功后订单状态流转为 Paid,进入待发货阶段。 +- 订单状态由 M05 在支付事务内直接更新;事件只用于异步消息通知,不再次触发状态变更。 + +## 七、与商家发货的协作(M06-02 / F12) + +```mermaid +flowchart TD + PAID["Paid 订单"] --> SHIP["商家请求发货:orderId + 物流信息"] + SHIP --> A{"订单存在且 assignedMerchantUserId == currentUserId?"} + A -- "否" --> X["返回 404 或 403"] + A -- "是" --> B{"存在未完结售后申请锁定该订单行?"} + B -- "是" --> Y["返回 409:存在进行中售后,暂不允许发货"] + B -- "否" --> C{"订单状态为 Paid?"} + C -- "否" --> Z["返回 409:状态不允许发货"] + C -- "是" --> D["更新状态为 Shipped,记录 shipped_at、物流信息"] + D --> E["写入 Outbox:OrderShippedIntegrationEvent"] + E --> F{"事务提交成功?"} + F -- "否" --> R["返回错误"] + F -- "是" --> G["返回发货成功"] + G --> H["Messaging 消费 OrderShippedIntegrationEvent,触发 M09 站内消息"] +``` + +关键约束: + +- 商家只能操作 `assignedMerchantUserId == currentUserId` 的订单(单店 B2C 严格校验)。 +- 发货前须检查是否存在未完结的售后申请(AfterSales 锁定同一订单行)。 +- 发货使用条件更新 `WHERE status = 'Paid'` 保证幂等。 +- 重复发货返回成功,不重复变更状态。 + +## 八、买家确认收货 + +```mermaid +flowchart TD + SHIPPED["Shipped 订单"] --> CONFIRM["买家请求确认收货:orderId"] + CONFIRM --> A{"订单存在且归属当前买家?"} + A -- "否" --> X["返回 404 或 403"] + A -- "是" --> B{"订单状态为 Shipped?"} + B -- "否" --> Y["返回 409:状态不允许确认"] + B -- "是" --> C["更新状态为 Completed,记录 completed_at 和 completed_by = BUYER_CONFIRMED"] + C --> D["写入 Outbox:OrderCompletedIntegrationEvent"] + D --> E{"事务提交成功?"} + E -- "否" --> R["返回错误"] + E -- "是" --> F["返回确认成功"] + F --> G["Messaging 消费 OrderCompletedIntegrationEvent,触发 M09 站内消息"] + G --> H["X01 开放评价入口(若已实现)"] +``` + +关键约束: + +- 只有 Shipped 状态可确认收货。 +- 确认收货后买家可对订单项进行评价(X01)。 +- 使用条件更新 `WHERE status = 'Shipped'` 保证幂等。 + +## 九、异常、状态竞争与责任 + +| 场景 | M04 处理 | 最终状态/责任 | +|---|---|---| +| 游客、商家、管理员访问买家订单接口 | 拒绝 | 401/403,不返回数据 | +| 订单不存在或不属于买家 | 404/403 | 不泄露归属 | +| 库存不足 | 整单拒绝 | 事务回滚,库存不扣减 | +| 商品下架 | 整单拒绝 | 事务回滚 | +| 地址无效或不归属 | 整单拒绝 | 事务回滚 | +| 幂等键重复提交 | 返回首次成功结果 | 不重复扣库存,不重复创建订单 | +| 支付时余额不足 | 支付失败 | 订单保持 PendingPayment | +| 取消时订单已为 Cancelled | 条件更新影响行数=0,返回幂等成功 | 不重复取消,不重复回补库存 | +| 取消时订单为 Paid/Shipped/Completed | 条件更新影响行数=0,返回 409 状态冲突 | 订单保持不变 | +| 并发取消与支付 | 条件更新竞争,最终只有一个成功 | 不会出现"又支付又取消" | +| 商家发货时状态已变更 | 条件更新影响行数=0,返回幂等成功 | 不重复变更 | +| C03 超时取消与支付竞争 | 条件更新竞争,最终只有一个成功 | 不会出现矛盾状态 | + +## 十、由流程派生的接口映射 + +| 已确认流程能力 | 当前派生接口 | 接口必须承载的业务结果 | 当前状态 | +|---|---|---|---| +| 提交订单(可选幂等) | A301 | 校验库存/地址/商品状态,原子扣减,创建订单快照,返回订单号和 PendingPayment | 待评审 | +| 查询订单列表(分页+筛选) | A302 | 仅返回当前买家订单,按创建时间倒序 | 待评审 | +| 查询订单详情 | A303 | 返回地址快照、订单项快照、状态时间线 | 待评审 | +| 买家取消订单 | A304 | 条件更新状态为 Cancelled,回补库存,写入 cancelled_at | 待评审 | +| 商家查询订单列表 | A305 | 仅返回与商家商品相关的订单 | 待评审 | +| 商家查询订单详情 | A306 | 返回商家可见的订单信息 | 待评审 | +| 商家发货 | A307 | 条件更新状态为 Shipped,记录物流信息 | 待评审 | +| 买家确认收货 | A308 | 条件更新状态为 Completed,记录 completed_at | 待评审 | + +## 十一、扩展接入边界 + +- C03 订单超时自动取消:复用取消事务逻辑,Worker 触发,不走买家主动接口;C03 复用 M04 的库存回补和状态变更逻辑。 +- X01 商品评价:Completed 状态后开放评价入口,评价模块消费 OrderCompletedIntegrationEvent。 +- M09 站内消息:消费 OrderCreatedIntegrationEvent、OrderCancelledIntegrationEvent、OrderShippedIntegrationEvent、OrderCompletedIntegrationEvent(由 Messaging 事件总线路由,不由 M05/M04 直接调用)。 +- M05 支付:在支付事务内更新订单状态为 Paid 并发布 OrderPaidIntegrationEvent;Messaging 事件总线消费事件后触发 M09 站内消息。 + +## 十二、验收证据清单 + +- [ ] 正常下单:返回订单号、PendingPayment 状态、订单总额 +- [ ] 库存不足:整单拒绝,库存不扣减 +- [ ] 商品下架:整单拒绝,库存不扣减 +- [ ] 地址无效:整单拒绝 +- [ ] 幂等键重复:返回首次成功结果,不重复扣库存 +- [ ] 订单列表:仅返回当前买家订单,分页正确 +- [ ] 订单详情:地址快照、订单项快照、状态时间线正确 +- [ ] 买家取消:状态变为 Cancelled,库存回补,站内消息通知 +- [ ] 重复取消:幂等成功,库存只回补一次 +- [ ] 已支付/已发货/已完成订单取消被拒绝(返回 409) +- [ ] 商家发货:状态变为 Shipped +- [ ] 重复发货:幂等成功 +- [ ] 买家确认收货:状态变为 Completed,站内消息通知 +- [ ] 支付与取消/发货竞争:条件更新保证最终只有一个成功 +- [ ] C03 超时取消:状态变为 Cancelled,库存回补,站内消息通知