# 商户支付对接 API 文档

> 支付网关：`pay.boseway.com`
> 版本：v1.0
> 适用对象：所有接入本聚合支付网关的系统（如 iijh.cn、wxbo.cn 等）

本文档面向接入商户（下游业务系统），说明如何通过 API 发起支付下单、查询订单状态，以及接收支付结果的异步通知。

---

## 1. 接入准备

下单前商户需先在支付平台后台完成以下配置，并获得接入凭证：

| 凭证 | 说明 | 来源 |
|------|------|------|
| `app_id` | 商户唯一接入标识（如 `iijh_73411f81`） | 平台分配 |
| `app_secret` | 签名密钥，用于所有请求签名与回调验签 | 平台分配 |

> ⚠️ **安全须知**
> - `app_secret` 为敏感信息，严禁泄露到前端或日志。
> - 商户提交的 `notify_url` 必须是 **公网可访问的 HTTPS 地址**，用于接收支付结果异步通知。

### 数据库字段速查

订单表 `pay_orders` 关键字段含义：

- `status`：`0`-未支付，`1`-已支付，`2`-已退款，`3`-已关闭
- `channel`：`wx`-微信支付，`alipay`-支付宝
- `type`：`native`-扫码，`h5`-手机网页支付
- `fee`：手续费（默认费率 0.6%）

---

## 2. 签名算法（通用，所有接口一致）

使用 **MD5 签名**。规则如下：

1. 将除 `sign` 外的所有请求参数放入数组；
2. 过滤掉值为空字符串或 `null` 的参数；
3. 按参数名 **字典序（ASCII）升序** 排序；
4. 拼接为 `参数名=参数值&参数名=参数值...` 格式；
5. 在末尾追加 `key={app_secret}`，得到待签名字符串；
6. 对完整字符串计算 **MD5**，结果 **转小写** 即为 `sign`。

```
sign = lower(md5("amount=100&app_id=iijh...&key={app_secret}"))
```

### 参考实现（PHP）

```php
function generateSign($params, $secretKey) {
    unset($params['sign']);
    ksort($params);
    $buff = "";
    foreach ($params as $k => $v) {
        if ($v !== "" && $v !== null) {
            $buff .= $k . "=" . $v . "&";
        }
    }
    $buff .= "key=" . $secretKey;
    return strtolower(md5($buff));
}
```

> 无论 POST 表单请求还是 GET 请求，签名规则一致。回调通知验签也使用同一套规则。

---

## 3. 接口通用约定

- **请求编码**：UTF-8
- **Content-Type**：接口为表单请求，请以 `application/x-www-form-urlencoded` 或 `multipart/form-data` 提交
- **响应格式**：`application/json; charset=utf-8`

### 统一响应结构

所有接口均返回 JSON，结构统一：

```json
{
  "code": 200,
  "msg": "success",
  "data": { ... }
}
```

`code` 取值说明：

| code | 含义 |
|------|------|
| 200 | 请求成功 |
| 400 | 参数错误 / 业务校验失败 |
| 401 | 签名校验失败 |
| 403 | AppID 无效或系统被禁用 |
| 404 | 资源不存在（如订单未找到） |
| 405 | 请求方法不允许 |
| 429 | 请求过于频繁（触发频率限制） |
| 500 | 服务端 / 支付通道异常 |

---

## 4. 创建订单（支付下单）

**接口**：`POST /api/create_order`

下发一笔支付订单，返回支付二维码链接 / 收银台地址。

### 请求参数

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `app_id` | string | ✅ | 商户接入标识 |
| `out_trade_no` | string | ✅ | 商户订单号，同一商户内唯一 |
| `channel` | string | ✅ | 支付渠道：`wx`-微信 / `alipay`-支付宝 |
| `amount` | string/number | ✅ | 支付金额（元，可带小数） |
| `notify_url` | string | ✅ | 支付结果异步通知地址（HTTPS 公网可达） |
| `type` | string | ❌ | 支付类型：`native`-扫码（默认）/ `h5`-手机网页 |
| `return_url` | string | ❌ | 支付完成后同步跳转地址 |
| `subject` | string | ❌ | 商品标题（默认"聚合支付订单"） |
| `param` | string | ❌ | 商户自定义附加参数，回调时会原样返回 |
| `sign` | string | ✅ | 请求签名（见上文签名算法） |

### 请求示例

```
POST /api/create_order

app_id=iijh_73411f81
&out_trade_no=MCHNT20260907001
&channel=wx
&type=native
&amount=100.00
&notify_url=https://iijh.cn/pay/notify
&return_url=https://iijh.cn/order/done
&subject=会员充值
&param=uid1234
&sign=<按签名算法计算>
```

### 返回示例（成功）

```json
{
  "code": 200,
  "msg": "OK",
  "data": {
    "pay_info": {
      "amount": 100.00,
      "out_trade_no": "MCHNT20260907001",
      "trade_no": "PAY202609070110001234",
      "channel": "wx",
      "qrcode_url": "weixin://wxpay/bizpayurl?pr=xxxxxxx",
      "code_url": "weixin://wxpay/bizpayurl?pr=xxxxxxx",
      "qrcode_img": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...（PNG 二维码，可直接展示扫码）"
    },
    "cashier_url": "http://pay.boseway.com/pay?trade_no=PAY202609070110001234"
  }
}
```

> **支付二维码字段（`channel=wx` 且 `type=native` 时返回）**：
> - `code_url` / `qrcode_url`：真实 `weixin://wxpay/bizpayurl?...` 链接，**仅此链接可唤起微信原生扫码支付**。商户可用前端二维码库把该链接渲染成二维码，或直接用于跳转。
> - `qrcode_img`：`data:image/png;base64,...`，**平台已生成好的二维码图片**（base64 编码）。商户可直接作为 `<img>` 的 `src` 展示给用户扫码，**无需自行渲染**。这是最便捷的接入选扫码支付的方式。
>
> 商户可直接：
> - 使用 `qrcode_img` 直接展示二维码供用户扫码（推荐，native / 微信扫码即唤起支付）；
> - 渲染 `code_url` 生成二维码供用户扫码；
> - 或拼接 `cashier_url` 跳转到平台收银台页面。收银台支持展示二维码、H5 等场景。

### 返回示例（配置不完整时）

当后台未完整配置对应支付渠道（如微信/支付宝商户号、证书等）时，接口**不会伪装成成功**，而是明确返回缺失项：

```json
{
  "code": 500,
  "msg": "微信支付配置不完整，缺少: wx_mchid(商户号)、wx_appid(AppID)...",
  "data": {
    "need_config": ["wx_mchid", "wx_appid", ...]
  }
}
```

### 特殊业务逻辑

- **重复下单**：同一商户的 `out_trade_no` 已存在时——
  - 若原订单**已支付**（`status=1`），返回 `400`：`Order already paid`；
  - 若原订单**未支付**，返回该订单的支付信息，不重复创建新订单。
- **频率限制**：同一 IP 一分钟内最多下单 30 次，超出返回 `429`。

---

## 5. 查询订单

**接口**：`GET /api/query_order`

按平台交易号或商户订单号查询订单状态。

### 请求参数

| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `app_id` | string | ✅ | 商户接入标识 |
| `trade_no` | string | ❌* | 平台交易号（与 `out_trade_no` 二选一） |
| `out_trade_no` | string | ❌* | 商户订单号（与 `trade_no` 二选一） |

\* 二者必须传其一。

### 请求示例

```
GET /api/query_order?app_id=iijh_73411f81&out_trade_no=MCHNT20260907001
```

### 返回示例

```json
{
  "code": 200,
  "msg": "success",
  "data": {
    "trade_no": "PAY202609070110001234",
    "out_trade_no": "MCHNT20260907001",
    "system_code": "iijh",
    "channel": "wx",
    "amount": 100.00,
    "status": 0,
    "pay_time": null,
    "subject": "会员充值"
  }
}
```

> `status`：`0`-未支付，`1`-已支付，`2`-已退款，`3`-已关闭。`pay_time` 为 `null` 时表示尚未支付。

权限与边界：
- 只能查询属于**本商户**（`app_id` 对应）的订单，查询结果会按 `system_code` 隔离；
- 其他商户的订单返回 `404 Order not found`。

---

## 6. 异步通知（支付结果回调）

> **通知链路**：用户支付成功后，微信/支付宝先向本网关（`pay.boseway.com`）发起支付结果回调；本网关处理完毕后，会再**主动向商户下单时提交的 `notify_url` 发起 POST 通知**，把支付结果推送给商户端（iijh.cn 等）。HTTP `notify` 是商户确认支付结果的 **最主要手段**（带签名、可重发、可靠）。此外本网关还通过 WebSocket 广播即时推送（见 §6.1），WebSocket 与 HTTP notify 均会**通知到商户端**。

### 通知机制

- 请求为 `POST`，Content-Type 为表单/JSON 之一，参数以表单字段方式提交。
- 商户收到通知后须**验签**（用同一套签名算法，`app_secret` 计算），确认通知合法性。
- 商户处理业务（如更新订单、发货）成功后，须返回 `success`（字符串，忽略大小写），平台才认为通知**送达成功**。
- 商户业务处理失败或返回非 `success` 时，平台会**重发通知**。

### 通知参数

| 参数 | 说明 |
|------|------|
| `system_code` | 商户系统代号（如 `iijh`） |
| `out_trade_no` | 商户订单号 |
| `trade_no` | 平台交易号 |
| `amount` | 支付金额（元） |
| `channel` | 支付渠道 |
| `status` | 支付状态：`1`-已支付 |
| `param` | 下单时传入的自定义参数（如有） |
| `sign` | 通知签名，用 `app_secret` 校验 |

### 通知报文示例

```json
{
  "system_code": "iijh",
  "out_trade_no": "MCHNT20260907001",
  "trade_no": "PAY202609070110001234",
  "amount": "100.00",
  "channel": "wx",
  "status": 1,
  "param": "uid1234",
  "sign": "<按签名算法计算>"
}
```

### 商户响应要求

- **成功**：响应体返回 `success`（大小写不敏感），平台停止重发。
- **失败/超时**：返回其他内容或非 200，平台会重发通知。

---

## 6.1 收银台 WebSocket 即时通知（页面端实时结算）

> **适用场景**：商户把用户引导到平台**收银台页面**（`cashier_url`，或自行渲染前端页面）时，页面可订阅本网关的 **WebSocket 即时推送**，支付成功后**无需轮询**即可实时刷新。

### 服务地址

- 前台广播端口：`ws://pay.boseway.com:2346`（若页面为 HTTPS，则用 `wss://`）
- 支付处理成功时，网关会向该端口广播一条 JSON 事件。

### 订阅方式

前端使用标准 `WebSocket` API 连接即可，无需鉴权：

```js
var proto = (location.protocol === 'https:') ? 'wss://' : 'ws://';
var ws = new WebSocket(proto + 'pay.boseway.com:2346');

ws.onmessage = function (ev) {
    var msg = JSON.parse(ev.data);
    // 支付成功事件，data 中包含平台交易号 trade_no
    if (msg.type === 'payment_success' && msg.data.trade_no === 'PAY202609070110001234') {
        // 本订单已支付，刷新页面/切换支付成功 UI
        location.reload();
    }
};
```

### 广播消息格式

```json
{
  "type": "payment_success",
  "data": {
    "system_code": "iijh",
    "out_trade_no": "MCHNT20260907001",
    "trade_no": "PAY202609070110001234",
    "amount": "100.00",
    "channel": "wx",
    "status": 1
  }
}
```

- `data.trade_no` 与下单返回保持一致，前端据此判断"正是本订单已支付"。
- 收银台页面已内建该订阅：**WebSocket 优先，连接失败/断开时自动降级回 `/api/query_order` 轮询**，保证兼容。

---

## 7. 错误码汇总

| code | 含义 | 触发场景 |
|------|------|----------|
| 200 | 成功 | 请求处理成功 |
| 400 | 参数错误 | 缺失必填参数、金额非法、订单已支付等 |
| 401 | 验签失败 | `sign` 计算不匹配 |
| 403 | 未授权 | `app_id` 无效或系统被禁用 |
| 404 | 未找到 | 按订单号查询不到订单 |
| 405 | 方法不允许 | 使用了错误的 HTTP 方法 |
| 429 | 频率受限 | 同 IP 一分钟下单超 30 次 |
| 500 | 服务异常 | 通道配置不完整、上游网关错误等 |

---

## 8. 对接流程速览（建议时序）

```
商户系统                          支付平台                         用户/第三方通道
   │ ①POST /api/create_order        │                                │
   ├───────────────────────────────►│                                │
   │ ◄── 返回 cashier_url +         │ ②下发支付请求(WeChat Native)   │
   │     pay_info.qrcode_img(base64)│                                │
   │ ③收银台展示 qrcode_img         │                                │
   │   (扫码直接唤起微信原生支付) ──┼──────────────────────────────► │
   │                                │                                │ ④用户完成支付
   │                                │  ◄───③' 通道回调给平台 ────────┤
   │     ⑤异步通知 POST /notify_url │                                │
   │ ◄──────────────────────────────┤ ⑤'平台同时经 WS(2346)广播      │
   │ ⑥验签 → 返回 success           │     payment_success 给商户端    │
   │ ⑦(可选)主动查询 /api/query_order│                                │
```

> **推荐做法**：
> - 支付二维码：优先用返回的 `pay_info.qrcode_img`（base64 图片），商户收银台 `<img src>` 直接展示即可扫码，扫码即唤起原生微信支付。
> - 支付结果确认：以 **异步通知（HTTP notify）为主**（唯一可靠支付成功凭证，平台收到通道回调后转推给商户），WebSocket 即时推送达辅助；两者均通知到商户端。
> - 查询接口仅作辅助/对账。

---

*文档版本 v1.0，生成于平台商户 API 下单链路完整验证之后。若有出入请以实际接口行为为准。*
