# 微信支付 · 商户入驻与商品支付技术方案

> **依据：** 《确认订单页（多商品）功能需求》v1.1、《确认订单页（多商品）技术方案》v1.2  
> **范围：** 商户微信支付入驻信息、C 端 JSAPI 下单与回调、Mock/生产双模式；**不含** 前端 `wx.requestPayment` 改造细节。  
> **代码落位：** `com.ruoyi.web.modules.pay`、`com.ruoyi.web.modules.order`（支付成功落单）

---

## 1. 总体架构

| 层级 | 说明 |
|------|------|
| **平台支付配置** | `application.yml` → `wechat.pay` / `wechat.miniapp` |
| **商户入驻** | `biz_shop_wechat_pay` · 商家 `/agri/seller/wechat-pay` |
| **支付流水** | `biz_pay_record` · `out_trade_no = order_no` |
| **C 端下单** | `POST /api/order/{orderId}/pay` → JSAPI 参数 |
| **微信回调** | `POST /api/pay/wechat/notify`（`@Anonymous`） |
| **落单** | `OrderPaySuccessServiceImpl.completeWeChatPay`（扣库存、改状态、删购物车） |

### 1.1 支付模式

| 模式 | `wechat.pay.enabled` | `wechat.pay.mock` | 行为 |
|------|:--------------------:|:-----------------:|------|
| **开发 Mock** | false | **true**（默认） | pay 接口 **直接落单**；返回 `{ mock: true }` |
| **生产微信** | **true** | false | 统一下单 → 返回 prepay 参数；**回调**落单 |

### 1.2 商户结算模式（`biz_shop_wechat_pay.pay_mode`）

| 值 | 含义 | 下单 mch_id |
|:--:|------|-------------|
| **1** | 平台代收 | `wechat.pay.mch-id` |
| **2** | 店铺自有商户号 | 入驻表 `mch_id`（须审核启用） |
| **3** | 服务商子商户 | 入驻表 `sub_mch_id` |

> v1 默认 **平台代收**；店铺提交入驻资料后由平台审核并配置 `enabled=1`。

---

## 2. 数据库

| 脚本 | 表 | 说明 |
|------|-----|------|
| [`sql/biz_shop_wechat_pay.sql`](../../../sql/biz_shop_wechat_pay.sql) | `biz_shop_wechat_pay` | 商户入驻 |
| [`sql/biz_pay_record.sql`](../../../sql/biz_pay_record.sql) | `biz_pay_record` | 支付流水 |
| [`sql/biz_member.sql`](../../../sql/biz_member.sql) | `biz_member.wx_openid` | JSAPI payer.openid |

### 2.1 商户入驻字段（`biz_shop_wechat_pay`）

| 字段 | 必填（申请） | 说明 |
|------|:------------:|------|
| company_name | ✓ | 企业/个体户名称 |
| legal_person | ✓ | 法人 |
| license_no | ✓ | 统一社会信用代码 |
| contact_name / contact_mobile | ✓ | 联系人 |
| bank_name / bank_account_name / bank_account_no | 选填 | 结算账户 |
| pay_mode | ✓ | 1/2/3 |
| mch_id | 模式2 | 自有商户号 |
| sub_mch_id | 模式3 | 子商户号 |
| apply_status | — | 0未申请 1审核中 2通过 3驳回 |
| enabled | — | 平台审核通过后 1 |

---

## 3. 接口

### 3.1 C 端 · 绑定 openid

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/member/wx/bind` | Body: `{ "code": "wx.login code" }` |

### 3.2 C 端 · 发起支付

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/order/{orderId}/pay` | 返回 `WeChatPayParamsVO` |

**`WeChatPayParamsVO`：**

| 字段 | 说明 |
|------|------|
| orderId / orderNo | 订单 |
| mock | true=Mock 已落单 |
| payParams | 真实模式：`appId/timeStamp/nonceStr/package/signType/paySign` |

### 3.3 微信回调

| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/pay/wechat/notify` | 解密 resource → `completeWeChatPay` |

### 3.4 商家 · 入驻

| 方法 | 路径 | 权限 |
|------|------|------|
| GET | `/agri/seller/wechat-pay` | `agri:seller:wechatPay:query` |
| PUT | `/agri/seller/wechat-pay` | `agri:seller:wechatPay:edit` |

### 3.5 平台 · 入驻审核

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/agri/shop/wechat-pay/list` | `agri:shop:wechatPay:list` | 分页列表；`applyStatus` / `shopName` / `companyName` 检索 |
| GET | `/agri/shop/wechat-pay/pendingCount` | `agri:shop:wechatPay:list` | 待审数量 |
| GET | `/agri/shop/wechat-pay/{id}` | `agri:shop:wechatPay:query` | 详情 |
| PUT | `/agri/shop/wechat-pay/approve/{shopId}` | `agri:shop:wechatPay:audit` | 通过 → `apply_status=2, enabled=1` |
| PUT | `/agri/shop/wechat-pay/reject` | `agri:shop:wechatPay:audit` | 驳回 → `apply_status=3, enabled=0`；Body: `{ shopId, auditRemark }` |

**入驻状态机：**

| apply_status | 含义 | enabled | 商家可再提交 |
|:--:|------|:--:|:--:|
| 0 | 未申请 | 0 | ✓ |
| 1 | 审核中 | 0 | 覆盖提交 |
| 2 | 已通过 | 1 | 覆盖提交 |
| 3 | 已驳回 | 0 | 覆盖提交 |

---

## 4. 回调验签与幂等（P0）

| 项 | 说明 |
|----|------|
| 验签 | `Wechatpay-Timestamp` + `Wechatpay-Nonce` + rawBody → RSA 验签（`platform-public-key-path`） |
| 开关 | `notify-verify-enabled`（生产 **true**；Mock 模式自动跳过） |
| 时间戳 | 默认 ±300s 容差 |
| 幂等 | `biz_pay_record.wx_transaction_id` **唯一索引**（`uk_wx_transaction_id`）；`completeWeChatPay` 重复回调 **直接返回** |

**DDL：** [`sql/biz_pay_record.sql`](../../../sql/biz_pay_record.sql)（含 `uk_wx_transaction_id`）

---

## 5. 配置示例（生产）

```yaml
wechat:
  miniapp:
    app-id: wxXXXXXXXX
    app-secret: xxxxxxxx
  pay:
    enabled: true
    mock: false
    app-id: wxXXXXXXXX
    mch-id: 16XXXXXXXX
    api-v3-key: xxxxxxxxxxxxxxxxxxxxxxxx
    merchant-serial-no: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    private-key-path: classpath:cert/apiclient_key.pem
    platform-public-key-path: classpath:cert/wechatpay_platform.pem
    notify-verify-enabled: true
    notify-timestamp-tolerance-seconds: 300
    notify-url: https://your-domain.com/api/pay/wechat/notify
```

**部署清单：**

1. 执行 DDL：`biz_shop_wechat_pay`、`biz_pay_record`、`biz_member`（含 `wx_openid`）
2. 微信商户平台配置 **支付回调 URL** 与 **API 证书**
3. 下载 **平台证书公钥** 至 `platform-public-key-path`
4. 小程序与商户号 **绑定同一 appId**
5. 配置菜单权限 `agri:seller:wechatPay:*`、`agri:shop:wechatPay:*`

---

## 6. C 端生产联调序列

```text
1. wx.login → POST /api/member/wx/bind { code }     # 首次绑定 openid
2. checkout submit → order_status=0
3. POST /api/order/{id}/pay → payParams
4. wx.requestPayment(payParams)
   ├─ success → 轮询/跳转 GET /api/order/{id}（status=1）
   ├─ fail    → 提示重试，订单仍待支付
   └─ cancel  → POST /api/order/{id}/pay/cancel      # 须主动调用，否则保持待支付
5. 微信异步 POST /api/pay/wechat/notify → completeWeChatPay
```

> **注意：** 用户在微信收银台 **取消** 不会自动关单；C 端须在 `requestPayment` fail/cancel 回调中调用 **pay/cancel** 或引导用户继续支付。

---

## 7. 实现状态矩阵

| 能力 | 状态 |
|------|------|
| Mock 直付（开发） | ✅ 已实现 |
| JSAPI V3 下单 | ✅ 已实现 |
| 回调解密 + 验签 | ✅ 已实现（须配置平台公钥） |
| transaction_id 幂等 | ✅ 已实现 |
| 商家入驻提交 | ✅ 已实现 |
| 平台入驻审核 | ✅ 已实现 |
| 查单补偿 Job | ⏳ 待补强 |
| 退款 / 分账 | ⏳ 非本期 |

---

## 8. 协作链

```text
submit → biz_order(0)
    → POST /api/member/wx/bind（首次）
    → POST /api/order/{id}/pay
        Mock: completeWeChatPay 同步
        生产: 返回 prepay → 小程序 wx.requestPayment
            → 微信 POST /api/pay/wechat/notify
            → completeWeChatPay（deductStock + status=1 + 删购物车）
```

---

## 9. 修订记录

| 版本 | 说明 |
|------|------|
| **v1.1** | P0：回调验签、transaction_id 唯一、平台入驻审核 API、C 端生产联调序列、实现矩阵 |
| **v1.0** | 首版：入驻表、JSAPI V3、Mock/生产、回调落单 |

---

*关联《确认订单页（多商品）技术方案.md》v1.3 · DDL 见 `sql/biz_shop_wechat_pay.sql`、`sql/biz_pay_record.sql`*
