236 lines
9.0 KiB
Markdown
236 lines
9.0 KiB
Markdown
# 小程序接口文档:在线支付(旺铺网关 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` | 加签专用 Key(SignKey) |
|
||
| `wangpu_sub_appid` | 下单微信子 appid,留空取小程序自身 appid |
|
||
| `wangpu_payway_code` | 支付方式代码(默认 `WECHAT_MINI`,以旺铺数据词典为准) |
|
||
|
||
### .env 备选配置
|
||
|
||
```dotenv
|
||
WECHAT_MINI_APPID= # 小程序 AppID(code2session 必需)
|
||
WECHAT_MINI_SECRET= # 小程序 Secret(code2session 必需)
|
||
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`),两种入口均会拒绝重复提交。
|