# 确认订单页（多商品）— 技术方案（C 端）

> **依据：** 《确认订单页（多商品）功能需求.md》v1.1  
> **关联：** 平台《订单管理技术方案》v1.0.2、《商品管理功能需求》v1.3.3、《店铺管理技术方案》v1.3.6、《会员管理技术方案》v1.3、《关联需求分析.md》v1.6 §11；C 端《购物车技术方案》v1.0、《商品详情内页技术方案》v1.0、《我的服务技术方案》v1.1；商家《运费模版技术方案》v1.1、《店铺商品列表技术方案》v1.6  
> **范围：** C 端 **`/api/checkout/**`** 确认页预览/提交 + **`/api/order/**`** 支付/关单；写入 **`biz_order` / `biz_order_item`**；**v1.2** 接入 **微信 JSAPI 支付**（Mock/生产可切换）。  
> **原则：** **复用** 订单三表 DDL；明细 **下单快照**；**支付成功** 扣库存；**待支付不扣**；同店 **一单多行**；v1.0 **统一规格**；**v1.1** 运费读 **`biz_goods_freight` + `IFreightTemplateFacade`** 真实计费（**待实现**）。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39** |
| ORM / 响应 | MyBatis；`AjaxResult`（`code`、`msg`、`data`） |
| 鉴权 | 须 **会员 Token**（`MemberWebConfig` → `/api/checkout/**`、`/api/order/**`） |
| 订单号 | `O` + `yyyyMMdd` + 6 位序号（Redis 或 DB 序列，与平台订单方案一致） |
| 支付超时 | `sys_config`：`order.pay.timeout.minutes`，默认 **1440**；`pay_expire_time` 写主表 |
| 支付 v1.2 | **微信 JSAPI**；`wechat.pay.mock=true` 开发直付；生产见《微信支付入驻与对接技术方案》v1.1 |
| 定时关单 | 复用 **`OrderPayTimeoutJob`**（平台订单方案 O8） |

### 1.1 模块落位

```text
baqing-shop/src/main/java/com/ruoyi/web/modules/order/
├── controller/
│   ├── CheckoutAppController.java        # /api/checkout/**  确认页
│   ├── OrderAppController.java           # /api/order/**     C 端支付/详情（新建）
│   ├── OrderController.java              # /agri/order       平台（已有）
│   └── SellerOrderController.java        # /agri/seller/order（已有）
├── service/
│   ├── ICheckoutAppService.java
│   ├── impl/CheckoutAppServiceImpl.java
│   ├── IOrderAppService.java             # C 端 pay / cancelPay
│   ├── impl/OrderAppServiceImpl.java
│   └── impl/OrderServiceImpl.java        # 平台+商家（已有）
├── support/
│   ├── OrderSupport.java                 # 已有 · 状态校验
│   └── CheckoutSupport.java              # 金额/地址/四条件/运费
├── constant/CheckoutConstants.java
├── dto/CheckoutPreviewDTO.java、CheckoutSubmitDTO.java …
├── vo/CheckoutPreviewVO.java、CheckoutSubmitResultVO.java …
├── domain/mapper/…                       # 已有 BizOrder*
└── job/OrderPayTimeoutJob.java           # 待建 · O8

cart/（已有 · 协作）
├── POST /api/cart/checkout/prepare       # 进页前轻量校验
└── facade/ICartFacade.removeItems

account/（已有 · 协作）
├── GET /api/member/address/list          # 地址簿
└── BizMemberAddressMapper

goods/（协作）
├── IGoodsPurchaseFacade.canPurchase
├── BizGoodsMapper                        # 读价/库/扣减
├── BizGoodsFreightMapper                 # 读各行 freight_type / fixed / template_id
└── BizGoodsServiceSnapshotMapper         # 服务快照展示

freight/（协作 · 已实现 Facade）
├── IFreightTemplateFacade.calcFreight    # 模版计费
├── IFreightTemplateFacade.buildFreightDesc
└── FreightCalcSupport                    # 纯函数（模版模块）

store/
└── BizShopMapper、IAgriShopFacade

com.ruoyi.web.modules.pay/         # v1.2 微信支付
├── controller/WeChatPayNotifyController
├── controller/SellerShopWechatPayController
├── service/IWeChatPayService、IShopWechatPayService
└── support/WeChatPaySupport

sql/
├── biz_order.sql                         # 已有 · 权威 DDL
├── biz_shop_wechat_pay.sql               # 商户入驻
├── biz_pay_record.sql                    # 支付流水
├── biz_member.sql                        # 会员（含 wx_openid）
└── biz_order_item_checkout.sql           # 明细扩展字段（§2.3）
```

### 1.2 协作链

```text
【购物车路径】
    POST /api/cart/checkout/prepare（可选门禁）
        → 前端路由 A. 确认订单
        → POST /api/checkout/preview
        → POST /api/checkout/submit → biz_order(0) + biz_order_item
        → POST /api/order/{id}/pay → status=1 + deductStock
        → ICartFacade.removeItems(cartItemIds)

【立即购买路径】
    商品详情 → POST /api/checkout/preview（source=BUY_NOW）
        → submit / pay（同上；无 cart_item_id）

biz_member_address ──► consignee_* 快照写入 biz_order
biz_goods + biz_goods_service_snapshot ──► 预览读实时 / 提交写 item 快照
biz_member_cart_item ──► 购物车路径关联 cart_item_id；成功后删除
```

| 关联模块 | 协作 |
|----------|------|
| **平台 · 订单管理** | **同源** `biz_order`；平台 `/agri/order` 履约 |
| **平台 · 商品/审核** | 四条件；支付成功 **扣库存** |
| **平台 · 店铺管理** | `shop_id`、`merchant_id`、店名快照 |
| **C 端 · 购物车** | prepare 门禁；提交后 **删行** |
| **C 端 · 商品详情** | BUY_NOW 预览入参 |
| **C 端 · 我的服务** | 地址 **只读**；维护走 `/api/member/address/**`；**省级 regionId** 供运费模版地区匹配 |
| **C 端 · 我的订单** | 支付后列表/详情 **另接口** `/api/order/**` |
| **商家 · 运费模版** | 发品 **`biz_goods_freight`** 已落库；确认页 **消费** 模版规则 |

### 1.3 跨模块 Facade

| 接口 | 本模块用法 |
|------|------------|
| **`IGoodsPurchaseFacade.canPurchase(goodsId)`** | preview/submit **四条件** |
| **`IMemberFacade.isMemberEnabled`** | 全部写操作 |
| **`IAgriShopFacade.isShopOpenForOrder`** | 已含于 canPurchase；submit 再校验 |
| **`ICartFacade.removeItems`** | 购物车路径 **支付成功或 submit 后** 删行（见 §4.2） |
| **`IFreightTemplateFacade.calcFreight`** | 模版行计费；`CheckoutSupport` 聚合后写入 `freight_amount` |
| **`BizGoodsFreightMapper.selectByGoodsId`** | 预览/提交逐行读发品运费配置 |
| **`IGoodsStockFacade`（规划）** | `deductStock` / `restoreStock`；v1 可在 Service 内 `UPDATE biz_goods.stock` |

**库存扣减（v1 实现要点）：**

```sql
UPDATE biz_goods SET stock = stock - #{qty}
WHERE goods_id = #{goodsId} AND stock >= #{qty} AND del_flag = '0'
-- 影响行数=0 → 支付失败/回滚，提示库存不足
```

---

## 2. 数据库设计

### 2.1 复用表（主 DDL 不变）

| 表 | 本模块用途 | 权威 DDL |
|----|------------|----------|
| `biz_order` | 订单主表：状态、金额、收货快照、支付字段 | `sql/biz_order.sql` |
| `biz_order_item` | 商品明细 **下单快照** | `sql/biz_order.sql` + §2.3 扩展 |
| `biz_order_logistics_trace` | **本模块不写**；发货后平台写 | `sql/biz_order.sql` |
| `biz_member_address` | 提交时 **读**；快照写入 `biz_order` | `sql/biz_member.sql` |
| `biz_goods` | 实时价/库；扣减库存 | `sql/biz_goods.sql` |
| `biz_goods_freight` | 各行 **固定/模版** 运费配置（发品 **必填**） | `sql/biz_goods_freight.sql` |
| `biz_goods_service_snapshot` | 预览展示 **服务说明** | `sql/biz_goods.sql` |
| `biz_shop_freight_template` | 模版主表（**不直读**；经 Facade） | `sql/biz_shop_freight_template.sql` |
| `biz_shop` | 店档案、`merchant_id` | `sql/biz_shop.sql` |
| `biz_member_cart_item` | 购物车路径 **`cart_item_id`** | `sql/biz_member_cart_item.sql` |

### 2.2 `biz_order` 本模块写入字段

| 字段 | 写入时机 | 说明 |
|------|----------|------|
| order_no | submit | `OrderSupport.nextOrderNo()` |
| member_id | submit | 当前会员 |
| shop_id / merchant_id | submit | 来自 `biz_shop` |
| order_status | submit | **`0`** 待支付 |
| pay_status | submit | **`0`** 未支付 |
| goods_amount | submit | Σ 行小计 |
| freight_amount | submit | **`CheckoutSupport.calcFreight`** 聚合结果（§2.4）；快照 **不随模版事后修改** |
| pay_amount | submit | goods_amount + freight_amount |
| consignee_name/mobile/address | submit | 地址簿 **快照**（`regionName + detailAddress`） |
| member_nick_name / shop_name | submit | 昵称、店名快照 |
| create_time | submit | 下单时间 |
| pay_expire_time | submit | create_time + 超时分钟 |
| pay_type / pay_time / trade_no | pay 成功 | 微信支付；Mock 可写固定值 |
| close_type / close_reason | pay 取消 | **`2`** 买家取消（需求 §12.1） |

> **不新建** 确认页会话表；preview **无状态**，submit **幂等靠前端** + 服务端防重复（可选 Redis 锁 `checkout:member:{id}`，本期 **可不建**）。

### 2.3 扩展 · `biz_order_item`（需求：行备注、服务展示）

在 [`sql/biz_order.sql`](../../../sql/biz_order.sql) 基础上 **追加字段**（脚本 `sql/biz_order_item_checkout.sql`）：

| 字段 | 类型 | 说明 |
|------|------|------|
| buyer_remark | varchar(500) | 买家 **行备注**（CO-R）；默认空 |
| service_desc | varchar(500) | 服务快照摘要（服务名逗号拼接）；提交时写入 |
| cart_item_id | bigint | 来源购物车行 ID；**BUY_NOW 为 NULL** |
| spec_key | varchar(64) | 规格键；v1 默认 **`''`** |

```sql
ALTER TABLE `biz_order_item`
  ADD COLUMN `buyer_remark` varchar(500) NOT NULL DEFAULT '' COMMENT '买家行备注' AFTER `line_amount`,
  ADD COLUMN `service_desc` varchar(500) NOT NULL DEFAULT '' COMMENT '服务快照摘要' AFTER `buyer_remark`,
  ADD COLUMN `cart_item_id` bigint(20) DEFAULT NULL COMMENT '来源购物车行ID' AFTER `service_desc`,
  ADD COLUMN `spec_key` varchar(64) NOT NULL DEFAULT '' COMMENT '规格键' AFTER `cart_item_id`,
  ADD KEY `idx_cart_item_id` (`cart_item_id`);
```

**原有字段仍用：** `goods_id, goods_name, goods_spec, goods_image, unit_price, quantity, line_amount, sort_no`。

### 2.4 运费（v1.1 · 对齐发品配置）

> **上游已就绪：** 商家发品 **`biz_goods_freight`**（`freightType` 必填）+ **`IFreightTemplateFacade`** + `FreightCalcSupport`（运费模版模块 **已实现**）。  
> **本模块待实现：** `CheckoutSupport.calcFreight` 在 preview/submit 调用；**v1.0 若未上线计费** 可临时 `freight_amount=0`（仅联调过渡）。

#### 2.4.1 输入与输出

| 输入 | 来源 |
|------|------|
| shopId | 同店校验后唯一 |
| 各行 goodsId、quantity、lineAmount | preview/submit 行 |
| goodsAmount | Σ lineAmount（整单商品总价） |
| provinceId | `biz_member_address.region_code` → 解析 **省级** `biz_region.id` |

| 输出 | 写入 |
|------|------|
| freightAmount | `CheckoutPreviewVO.freightAmount`；submit → `biz_order.freight_amount` |
| freightDesc | 预览展示（可选）；固定行可拼「运费 ¥X.XX」；模版行走 `buildFreightDesc` |
| payAmount | goodsAmount + freightAmount |

#### 2.4.2 聚合算法（同店 · 多商品）

```text
CheckoutSupport.calcFreight(shopId, lines[], provinceId, goodsAmount)
    fixedTotal = 0
    templateGroups = Map<templateId, { pieceCount, weightKg }>
    FOR each line IN lines:
        freight = BizGoodsFreightMapper.selectByGoodsId(line.goodsId)
        IF freight IS NULL → throw「商品运费未配置」
        IF freight.freight_type = '1':
            fixedTotal += freight.fixed_amount
        ELSE IF freight.freight_type = '2':
            group = templateGroups[freight.freight_template_id]
            group.pieceCount += line.quantity
            group.weightKg += line.quantity * DEFAULT_KG_PER_PIECE   // v1 无商品重量，默认 1kg/件
    templateTotal = 0
    FOR each (templateId, group) IN templateGroups:
        templateTotal += IFreightTemplateFacade.calcFreight(
            templateId, shopId, provinceId,
            group.pieceCount, group.weightKg, goodsAmount)
    RETURN fixedTotal + templateTotal
```

| 规则 | 说明 |
|------|------|
| 固定运费 | 每行 `fixed_amount` **累加**（CO-F2） |
| 相同模版 | 同 `freight_template_id` **合并件数/重量** 后 **计一次**（CO-F3） |
| 不同模版 | 各组分别 `calcFreight` 后 **相加** |
| 满免/包邮 | 由 Facade 内 `FreightCalcSupport` 处理；`goodsAmount` 传 **整单商品总价** |
| 地址变更 | preview 入参 `addressId` 变化 → 重新解析 `provinceId` → **重算** |
| 重量缺失 | v1 **统一规格** 且无 `goods_weight` 字段 → `weightKg = quantity × 1`（可配置常量） |

#### 2.4.3 与订单快照

| 项 | 说明 |
|----|------|
| submit | 将 **当时** 计算得到的 `freight_amount` 写入 `biz_order` |
| 事后 | 商家改模版/改发品配置 **不影响** 已生成订单（FT11 / 需求 §2.10） |
| 明细 | **不** 在 `biz_order_item` 拆分行级运费；整单运费仅在主表 |

### 2.5 枚举（与平台订单方案一致）

| 字段 | 提交 | 支付成功 | 取消支付 | 超时 |
|------|------|----------|----------|------|
| order_status | 0 | 1 | 4 | 4 |
| pay_status | 0 | 1 | 0 | 0 |
| close_type | — | — | 2 | 1 |
| pay_type | — | 1（微信） | — | — |

---

## 3. C 端接口设计 · 确认页 `/api/checkout`

**鉴权：** `Authorization: Bearer {token}`；`MemberWebConfig` 追加 **`/api/checkout/**`**

### 3.1 接口一览

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/checkout/preview` | **确认页预览**（地址+店+商品+金额） |
| POST | `/api/checkout/submit` | **提交订单** → 待支付 + 返回支付参数 |

**复用（非本 Controller）：**

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/member/address/list` | 切换地址列表 |
| POST | `/api/cart/checkout/prepare` | 购物车 **进页前** 校验（可选） |

### 3.2 预览 `POST /api/checkout/preview`

**Body · `CheckoutPreviewDTO`：**

| 字段 | 必填 | 说明 |
|------|:----:|------|
| source | ✓ | `CART` / `BUY_NOW` |
| addressId | 否 | 指定地址；不传则 **默认地址** 或 **唯一地址** |
| cartItemIds | CART | 购物车行 ID 列表；**同店** |
| items | BUY_NOW | `[{ goodsId, quantity, specKey?, specText? }]`（通常 1 行） |

**校验：**

1. `isMemberEnabled`  
2. CART：`cartItemIds` 均属当前会员、**同一 shop_id**  
3. 逐行：`canPurchase` + `quantity <= stock`  
4. 地址：`addressId` 有效或解析默认；**无地址** → 400「请先添加收货地址」

**`data` · `CheckoutPreviewVO`：**

| 字段 | 说明 |
|------|------|
| shop | `{ shopId, shopName, shopAvatar, shopStatus }` |
| address | `{ addressId, consigneeName, mobile, regionName, detailAddress, isDefault }`；无地址 **null** |
| items | 见下表 |
| goodsAmount | 商品总价 |
| freightAmount | 运费（**v1.1** `CheckoutSupport.calcFreight`；过渡可 0） |
| freightDesc | 运费展示摘要（可选） |
| payAmount | 应付总额 |
| payType | 固定 **`WECHAT`**（展示用） |

**`items[]` · `CheckoutPreviewItemVO`：**

| 字段 | 说明 |
|------|------|
| cartItemId | CART 有值；BUY_NOW **null** |
| goodsId | |
| goodsName / mainPic | 实时 |
| specText | CART 读 cart 快照；BUY_NOW 入参或生成 |
| serviceDesc | 读 `biz_goods_service_snapshot` 拼接 |
| salePrice | 当前售价 |
| quantity | |
| subtotal | salePrice × quantity |
| maxStock | 当前库存上限 |
| freightType | **1** 固定 **2** 模版（读 `biz_goods_freight`） |
| freightDesc | 行级展示摘要（固定金额或模版 `buildFreightDesc`） |

### 3.3 提交订单 `POST /api/checkout/submit`

**Body · `CheckoutSubmitDTO`：**

| 字段 | 必填 | 说明 |
|------|:----:|------|
| source | ✓ | `CART` / `BUY_NOW` |
| addressId | ✓ | 收货地址 ID |
| payType | ✓ | v1 固定 **`1`**（微信，字典 `biz_order_pay_type`） |
| cartItemIds | CART | 与 preview 一致 |
| items | 两者 | `[{ cartItemId?, goodsId, quantity, specKey?, specText?, buyerRemark? }]` |

**事务逻辑：**

```text
1. 复用 preview 同级校验（四条件、同店、地址、数量）
2. freightAmount = CheckoutSupport.calcFreight(…)
3. INSERT biz_order（status=0, freight_amount, pay_amount=goods+freight, pay_expire_time=…）
4. BATCH INSERT biz_order_item（快照价/图/规格/备注/服务/cart_item_id）
5. 返回 CheckoutSubmitResultVO
6. **不扣库存**；**不删购物车**（支付成功后再删，见 §4.2）
```

**`data` · `CheckoutSubmitResultVO`：**

| 字段 | 说明 |
|------|------|
| orderId | |
| orderNo | |
| payAmount | |
| payExpireTime | 支付截止时间 |
| wechatPayParams | v1 Mock：`{ mock: true }` 或空；前端调 **pay 接口** 完成闭环 |

失败 → **400** + 明确 msg；**不生成** 订单。

### 3.4 错误 msg（示例）

| 场景 | msg |
|------|-----|
| 未选地址 | 请选择收货地址 |
| 跨店 | 请选择同一店铺的商品结算 |
| 四条件不满足 | 沿用 `GoodsPurchaseVO.reason` / 库存不足 |
| 商品运费未配置 | 商品运费未配置 |
| 重复提交 | 请勿重复提交（可选） |

---

## 4. C 端接口设计 · 支付 `/api/order`

**鉴权：** `/api/order/**` 追加至 `MemberWebConfig`

### 4.1 接口一览

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/order/{orderId}/pay` | **发起支付** → `WeChatPayParamsVO`（Mock 同步落单） |
| POST | `/api/member/wx/bind` | 绑定 openid（支付前置） |
| POST | `/api/pay/wechat/notify` | 微信回调（Anonymous） |
| POST | `/api/order/{orderId}/pay/cancel` | **取消支付** → 已关闭 |
| GET | `/api/order/{orderId}` | 订单详情（支付结果页/我的订单 **共用**） |
| GET | `/api/order/list` | 会员订单列表（**占位**；详册见我的订单） |

### 4.2 支付 `POST /api/order/{orderId}/pay`

| 项 | 说明 |
|----|------|
| 归属 | `member_id` 校验 |
| 前置 | `order_status=0` 且 `now < pay_expire_time`；会员已绑定 **wx_openid**（生产） |
| Mock | `wechat.pay.mock=true` → **同步** `completeWeChatPay`；返回 `{ mock: true }` |
| 生产 | 微信 V3 JSAPI 下单 → 返回 **payParams**；**回调**执行扣库存与改状态 |
| 库存失败 | **整单失败**；订单 **保持待支付** |
| 购物车 | 支付成功后 `ICartFacade.removeItems` |

**响应 `WeChatPayParamsVO`：** `orderId`、`orderNo`、`mock`、`payParams`（appId/timeStamp/nonceStr/package/signType/paySign）。

> 详见 **《微信支付入驻与对接技术方案》** v1.1。

### 4.2.1 C 端生产支付时序

| 步骤 | 接口 / 动作 | 说明 |
|:--:|-------------|------|
| 1 | `POST /api/member/wx/bind` | 首次支付前绑定 openid |
| 2 | `POST /api/order/{id}/pay` | 获取 `payParams` |
| 3 | `wx.requestPayment` | 调起微信收银台 |
| 4a | 支付成功 | 微信回调 → 后端落单；前端查 `GET /api/order/{id}` |
| 4b | 用户取消 | **须** 调 `POST /api/order/{id}/pay/cancel` 关闭订单 |
| 4c | 支付失败 | 订单保持待支付，可重新 pay |

### 4.3 取消支付 `POST /api/order/{orderId}/pay/cancel`

| 项 | 说明 |
|----|------|
| 前置 | `order_status=0` |
| 动作 | `order_status=4, close_type=2`（买家取消） |
| 库存 | **无**（未扣） |
| 购物车 | **不删** 行（需求：取消后可再下单） |

### 4.4 订单详情 `GET /api/order/{orderId}`

| 项 | 说明 |
|----|------|
| 校验 | `member_id` 匹配 |
| 内容 | 主表 + `items[]`（含 buyerRemark、serviceDesc）+ 剩余支付秒数（待支付时） |

---

## 5. Service 分层

```text
CheckoutAppController
    → ICheckoutAppService.preview / submit
        → CheckoutSupport.buildItems(source)
        → CheckoutSupport.resolveAddress(memberId, addressId)
        → CheckoutSupport.resolveProvinceId(address)          # region_code → 省级 id
        → CheckoutSupport.validateSameShop / validateFourConditions
        → CheckoutSupport.calcFreight(shopId, lines, provinceId, goodsAmount)
        → BizOrderMapper.insert + BizOrderItemMapper.batchInsert

OrderAppController
    → IOrderAppService.createPay
        → IWeChatPayService.createJsapiPay
        → [Mock] IOrderPaySuccessService.completeWeChatPay
WeChatPayNotifyController
    → IWeChatPayService.handleNotify → completeWeChatPay
```

| 要点 | 说明 |
|------|------|
| memberId | **仅** `MemberContext` |
| 地址快照 | `consignee_address = regionName + detailAddress`（空格拼接） |
| 服务快照 | `GROUP_CONCAT(service_name)` 或 Java 拼接 → `service_desc` |
| 防超卖 | pay 时 **条件 UPDATE stock**；与订单 **同事务** |
| 与 cart prepare | prepare **可保留** 为轻量门禁；**preview 为权威页数据** |

---

## 6. 与平台后台联动

| 平台操作 | 本模块影响 |
|----------|------------|
| 商品下架/改价 | preview/submit **实时校验**；已生成订单 **读快照** |
| 店铺停业 | **阻断** submit |
| 会员禁用 | **403** |
| 平台发货 | **不变** 本模块；走 `/agri/order` |
| O8 超时关单 | Job 关 `status=0` → `4, close_type=1` |
| 已支付关单 | 平台 **restoreStock**（订单方案 §3.7） |

---

## 7. 与兄弟模块分工

| 能力 | 购物车 | 本模块 checkout | 本模块 order | 我的服务 |
|------|--------|-----------------|--------------|----------|
| prepare 门禁 | ✓ | — | — | — |
| 预览/提交 | — | ✓ | — | — |
| 地址列表 | — | 复用 member | — | ✓ 维护 |
| 支付/取消 | — | — | ✓ | — |
| 删购物车行 | — | — | ✓ pay 后 | — |

**MemberWebConfig 变更：**

```java
.addPathPatterns(..., "/api/cart/**", "/api/checkout/**", "/api/order/**")
```

---

## 8. 业务规则映射

| 需求规则 | 实现 |
|----------|------|
| CO0 须登录 | 拦截器 + `isMemberEnabled` |
| CO-A1 统一地址 | 单 `addressId` → `biz_order` 收货快照 |
| CO4 行字段 | `CheckoutPreviewItemVO` + item 快照 |
| CO5 改数量 | preview/submit `quantity` 校验 ≤stock |
| CO6 应付 | goodsAmount + freightAmount（`calcFreight`） |
| CO-F1～F7 | §2.4.2 聚合算法 |
| CO7 行备注 | `buyer_remark` |
| CO8 微信支付 | `pay_type=1`；Mock pay 接口 |
| CO9 提交待支付 | `order_status=0` |
| CO10 支付成功 | pay → `1` + deductStock |
| CO11 取消支付 | pay/cancel → `4, close_type=2` |
| CO12 超时 | `pay_expire_time` + Job |
| CO13 四条件 | preview + submit |
| CT 删购物车行 | pay 成功 → `ICartFacade` |
| GD12 立即购买 | `source=BUY_NOW` |

---

## 9. 非本期

| 项 | 说明 |
|----|------|
| 查单补偿 Job | 回调丢失时主动查单（支付方案 v1.1 待补强） |
| 退款 / 分账 | 需求未要求 |
| 优惠券/满减 | 需求未要求 |
| 跨店合单 | **不支持** |
| 确认页 Redis 会话 | 可选；本期 **无** |
| C 端选配送方式 | 平台发货时定 |
| 商品重量字段 | v1 缺省 **1kg/件**；后续 `biz_goods.weight` 接入后替换 |

**支付能力现状（v1.2 + P0）：**

| 能力 | 状态 |
|------|------|
| Mock 直付 | ✅ |
| JSAPI V3 下单 + 回调落单 | ✅（须生产证书与平台公钥） |
| 回调验签 + transaction_id 幂等 | ✅ |
| 平台商户入驻审核 | ✅ |
| C 端 wx.requestPayment 对接 | 前端待联调 |

---

## 10. 实现分期

| 阶段 | 内容 | 状态 |
|------|------|------|
| **v1.0** | checkout preview/submit、order pay/cancel；`freight_amount=0` 过渡 | **规划/部分** |
| **v1.2·支付** | 微信 JSAPI + 入驻 + Mock/生产 | **已实现** |
| **v1.2·P0** | 回调验签、幂等、平台审核、C 端联调文档 | **已实现** |
| **v1.1·运费** | `CheckoutSupport.calcFreight` | **待实现** |
| **v1.3** | 商品重量参与按重计费；C 端详情 `freightDesc` 与确认页一致 | 规划 |

---

## 11. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.3** | P0 支付补强：验签/幂等/平台审核；C 端生产时序；修正实现状态表述 |
| **v1.2** | 微信 JSAPI 支付、商户入驻、Mock/生产双模式 |
| **v1.1** | 对齐 **`biz_goods_freight`** 与运费模版 Facade；§2.4 真实计费算法 |
| **v1.0** | 首版：checkout preview/submit、order pay/cancel；扩展 order_item |

---

*文档版本：v1.3 · 关联《确认订单页（多商品）功能需求.md》v1.1、《微信支付入驻与对接技术方案.md》v1.1*
