Files
xin-procurement/docs/api/mini-online-payment.md
T
2026-08-27 21:17:02 +08:00

236 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 小程序接口文档:在线支付(旺铺网关 JSAPI)
> 门店端在线支付:选择本店未支付账单合并付款,后端经旺铺支付网关(统一下单B-JSAPI)下单,
> 小程序调起 `wx.requestPayment` 完成支付;支付结果由**网关后台通知**自动结账,
> 小程序端也可**主动查询**同步结果(回调延迟/丢失时的兜底)。
>
> 结账效果与线下凭证审核一致:关联账单批量置「已支付」、累加门店总采购金额、门店收到支付成功通知。
## 整体流程
```
小程序 后端 旺铺网关 微信
│ wx.login → code │ │ │
│ POST /mini/payment/online (bill_ids, code) │ │
│────────────────────▶│ code2session 换 openid │──────────────────▶│
│ │ 创建支付单+锁定账单 │ │
│ │ 统一下单(mer_order_id=支付单号) ──────────▶│
│ 返回 pay_params │◀──────── order_id + 调起参数 ────────────│
│◀────────────────────│ │ │
│ wx.requestPayment(pay_params) ─────────────────────────────────▶│
│ │ POST /mini/payment/notify(支付成功通知) │
│ │◀───────────────────────│ │
│ │ 验签 → 幂等结账 → 应答 {"code":"00"} │
│ GET .../query 主动同步(兜底) │ │
│────────────────────▶│ 交易查询 → 已支付则结账 │ │
```
## 通用约定
| 项 | 值 |
|---|---|
| 鉴权 | 门店 token`Authorization: Bearer <token>`(登录见 `/mini/auth/login` |
| 响应格式 | `{ "success": true|false, "data": {...}, "msg": "..." }` |
| 金额单位 | 元,字符串/数字两位小数(如 `150.50` |
---
## 1. 发起在线支付(合并账单下单)
| 项 | 值 |
|---|---|
| 请求方式 | `POST` |
| 路径 | `/mini/payment/online` |
| 鉴权 | 门店 token |
### 请求参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `bill_ids` | int[] | 是 | 要合并付款的账单 ID 数组(`GET /mini/bill?payable=1` 返回的 `id`),至少 1 个 |
| `code` | string | 是 | 小程序 `wx.login()` 返回的登录凭证(后端用它换付款人 openid) |
| `remark` | string | 否 | 付款备注,最长 255 字 |
### 校验规则(失败均返回 `success:false`
- 账单必须全部属于当前门店,且为「未支付」且未被其他支付单锁定;
- 任一账单已支付 / 正在支付中(含凭证审核中)→ 拒绝并提示对应账单号;
- 合计金额必须大于 0
- 微信 `code` 无效(code2session 失败)→ 报错,不产生支付单;
- 网关下单失败 → 支付单自动作废、账单释放,可重新发起。
### 响应示例
```json
{
"success": true,
"msg": "下单成功,请调起支付",
"data": {
"id": 12,
"payment_no": "ZF202608270001",
"amount": "150.50",
"pay_params": {
"order_id": "202608271201444525348059",
"tradeNo": "2021082722001407831438768160",
"user_openid": "oXxx123"
}
}
}
```
### 字段说明
| 字段 | 说明 |
|---|---|
| `payment_no` | 本系统支付单号(即上送网关的商户订单号 `mer_order_id`),后续查询/对账用 |
| `amount` | 应付金额(= 所选账单总额合计,元) |
| `pay_params` | 旺铺网关 `data` 节点原样透传。**调起支付所需参数以网关返回为准**(服务商配置后通常包含 `timeStamp`/`nonceStr`/`package`/`signType`/`paySign` 等),直接透传给 `wx.requestPayment` 即可 |
### 小程序端调用示例
```js
// 1. 获取登录凭证
const { code } = await wx.login();
// 2. 后端下单
const res = await request.post('/mini/payment/online', { bill_ids: [101, 102], code });
const { payment_no, pay_params } = res.data;
// 3. 调起微信支付(pay_params 以旺铺网关实际返回的调起参数为准)
await wx.requestPayment({
timeStamp: pay_params.timeStamp,
nonceStr: pay_params.nonceStr,
package: pay_params.package,
signType: pay_params.signType || 'RSA',
paySign: pay_params.paySign,
});
// 4. 支付完成后主动同步结果(回调兜底)
const q = await request.get(`/mini/payment/online/${payment_no}/query`);
// q.data.status === 1 → 支付成功,刷新账单列表
```
---
## 2. 查询支付结果(主动同步)
| 项 | 值 |
|---|---|
| 请求方式 | `GET` |
| 路径 | `/mini/payment/online/{payment_no}/query` |
| 鉴权 | 门店 token(仅可查询本店支付单) |
### 路径参数
| 参数 | 说明 |
|---|---|
| `payment_no` | 下单返回的支付单号(如 `ZF202608270001` |
### 行为说明
- 本地已是「支付成功」→ 直接返回,不再请求网关;
- 否则向旺铺网关发起「交易查询」,网关返回已支付(`order_status=1`)则**立即结账**(与后台通知同一幂等逻辑);
- 建议在 `wx.requestPayment` 成功回调后、以及付款页 `onShow` 时各调一次;
- 用户中途取消支付时本接口返回 `status: 0`,账单仍处于锁定中,可稍后重试或联系客服释放。
### 响应示例
```json
{
"success": true,
"msg": "支付成功",
"data": {
"payment_no": "ZF202608270001",
"status": 1,
"status_name": "支付成功",
"paid_at": "2026-08-27 10:00:00",
"trade_no": "2021082722001407831438768160"
}
}
```
### `status` 取值
| 值 | 含义 |
|---|---|
| `0` | 待支付(网关未确认) |
| `1` | 支付成功(账单已置已支付) |
| `2` | 支付失败(网关下单失败作废,账单已释放,可重新下单) |
---
## 3. 支付结果后台通知(服务端对接,无需小程序调用)
| 项 | 值 |
|---|---|
| 请求方式 | `POST`(表单) |
| 路径 | `/mini/payment/notify` |
| 鉴权 | 无(公开路由,以报文 `sign` 验签) |
| 来源 | 旺铺网关(下单时上送的 `notifyurl`,由后端自动生成:`{APP_URL}/mini/payment/notify` |
### 处理逻辑
1. 按旺铺加签规则验签(参数 ASCII 升序 `key=value``&` 拼接 + `&key=SignKey`MD5 大写);
2. 验签失败 → 应答 `{"code":"01"}`
3.`mer_order_id` 定位支付单,校验 `order_status=1``order_amt` 与支付单金额一致(防篡改);
4. **幂等结账**:支付单置成功(记录 `trade_no`/`order_id`/支付时间)→ 关联账单批量置已支付 → 累加门店总采购金额(只统计商品金额)→ 站内通知门店;
5. 重复通知直接应答成功,不重复结账。
### 应答报文(网关约定格式)
```json
{ "code": "00", "msg": "成功", "timestamp": "20260827100000123" }
```
网关收到 `code=00` 才认为通知成功,否则按 2ⁿ 分钟重试 7 次。
---
## 后台配置(上线前必配)
配置优先级:后台「系统设置 → 支付配置」> `.env`
### 后台支付配置(site_config `pay` 组,已内置配置项)
| 配置项 | 说明 |
|---|---|
| `wangpu_base_url` | 旺铺网关域名(接口地址前缀) |
| `wangpu_organiz_no` | 合作机构渠道号 organiz_no |
| `wangpu_mer_no` | 旺铺内部商户号 mer_no(进件入网后返回) |
| `wangpu_mer_code` | 商户号 mer_code |
| `wangpu_term_code` | 终端号 term_code |
| `wangpu_sign_key` | 加签专用 KeySignKey |
| `wangpu_sub_appid` | 下单微信子 appid,留空取小程序自身 appid |
| `wangpu_payway_code` | 支付方式代码(默认 `WECHAT_MINI`,以旺铺数据词典为准) |
### .env 备选配置
```dotenv
WECHAT_MINI_APPID= # 小程序 AppIDcode2session 必需)
WECHAT_MINI_SECRET= # 小程序 Secretcode2session 必需)
WANGPU_BASE_URL=
WANGPU_ORGANIZ_NO=
WANGPU_MER_NO=
WANGPU_MER_CODE=
WANGPU_TERM_CODE=
WANGPU_SIGN_KEY=
WANGPU_SUB_APPID=
WANGPU_PAYWAY_CODE=WECHAT_MINI
```
> 注意:`APP_URL` 必须是网关可访问的公网 HTTPS 地址,否则支付成功通知无法送达(此时依赖小程序主动查询兜底结账)。
## 与线下凭证支付的关系
两种付款方式并存,同一张账单同一时刻只能处于一条支付链路:
| | 在线支付(本档) | 线下凭证支付 |
|---|---|---|
| 入口 | `POST /mini/payment/online` | `POST /mini/payment` |
| 支付单 `pay_type` | `2` | `1` |
| 支付单 `pay_method` | `4` 旺铺支付 | `1` 微信 / `2` 支付宝 / `3` 对公汇款 |
| 结账方式 | 网关通知/主动查询自动结账 | 后台审核通过 |
| 失败/拒绝 | 账单自动释放可重新付款 | 审核拒绝后释放 |
账单被任一支付单锁定期间(`payment_id ≠ 0`),两种入口均会拒绝重复提交。