932 lines
23 KiB
Markdown
932 lines
23 KiB
Markdown
# Pay-Bridge 支付网关接入文档
|
||
|
||
| 版本 | 日期 | 说明 |
|
||
|------|------|------|
|
||
| v1.2 | 2026-02-28 | 新增商户进件接口(SaaS 多商户场景)、统一下单支持 merchant_id |
|
||
| v1.1 | 2026-02-28 | 更新接入凭证申请方式 |
|
||
| v1.0 | 2026-02-28 | 初始版本 |
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [接入准备](#1-接入准备)
|
||
2. [请求规范](#2-请求规范)
|
||
3. [签名算法](#3-签名算法)
|
||
4. [统一响应格式](#4-统一响应格式)
|
||
5. [支付接口](#5-支付接口)
|
||
- 5.1 [统一下单](#51-统一下单)
|
||
- 5.2 [查询订单](#52-查询订单)
|
||
- 5.3 [关闭订单](#53-关闭订单)
|
||
- 5.4 [申请退款](#54-申请退款)
|
||
- 5.5 [查询退款](#55-查询退款)
|
||
6. [商户进件接口](#6-商户进件接口)(SaaS 多商户场景)
|
||
- 6.1 [创建商户](#61-创建商户)
|
||
- 6.2 [上传证件文件](#62-上传证件文件)
|
||
- 6.3 [提交进件申请](#63-提交进件申请)
|
||
- 6.4 [查询审核状态](#64-查询审核状态)
|
||
- 6.5 [查询商户详情](#65-查询商户详情)
|
||
- 6.6 [查询商户列表](#66-查询商户列表)
|
||
7. [异步通知](#7-异步通知)
|
||
8. [订单状态说明](#8-订单状态说明)
|
||
9. [错误码对照表](#9-错误码对照表)
|
||
10. [最佳实践](#10-最佳实践)
|
||
11. [签名示例代码](#11-签名示例代码)
|
||
|
||
---
|
||
|
||
## 1. 接入准备
|
||
|
||
### 1.1 获取接入凭证
|
||
|
||
接入凭证由**平台管理员**在管理后台创建。请联系平台管理员,按以下步骤为你的系统开通接入权限:
|
||
|
||
1. 管理员登录管理后台,进入「**接入应用**」模块
|
||
2. 点击「新建应用」,填写应用名称(如:商城系统、ERP 系统)
|
||
3. 系统自动生成 `app_id` 和 `app_secret`,**Secret 仅在创建时展示一次**
|
||
4. 管理员将凭证告知接入方
|
||
|
||
> **重要提示**:`app_secret` 创建后无法再次查看,如遗失需由管理员在后台执行「重置密钥」操作,旧密钥立即失效。
|
||
|
||
获得的凭证如下:
|
||
|
||
| 参数 | 示例 | 说明 |
|
||
|------|------|------|
|
||
| `app_id` | `app_2602280a3f1b7c2d` | 应用唯一标识,格式为 `app_` + 日期 + 随机串 |
|
||
| `app_secret` | `A3F8C2D1E4B7F9A0...` | 64 位十六进制字符串,用于请求签名,**请妥善保管,切勿泄露或提交至代码仓库** |
|
||
|
||
### 1.2 接口基础地址
|
||
|
||
| 环境 | 地址 |
|
||
|------|------|
|
||
| 生产环境 | `https://pay.your-domain.com` |
|
||
| 沙箱环境 | `https://sandbox-pay.your-domain.com` |
|
||
|
||
---
|
||
|
||
## 2. 请求规范
|
||
|
||
### 2.1 基本要求
|
||
|
||
- 协议:HTTPS
|
||
- 方法:POST(下单/退款/关闭)、GET(查询)
|
||
- 编码:UTF-8
|
||
- Content-Type:`application/json`
|
||
|
||
### 2.2 公共请求头
|
||
|
||
所有请求必须携带以下 Header:
|
||
|
||
| Header | 类型 | 必填 | 说明 |
|
||
|--------|------|------|------|
|
||
| `X-App-Id` | string | 是 | 平台分配的 app_id |
|
||
| `X-Timestamp` | string | 是 | 当前 Unix 时间戳(秒),与服务器时间差须在 **5 分钟**以内 |
|
||
| `X-Sign` | string | 是 | 请求签名,见[签名算法](#3-签名算法) |
|
||
| `Content-Type` | string | 是 | `application/json` |
|
||
|
||
**示例:**
|
||
|
||
```http
|
||
POST /api/v1/pay/unified-order HTTP/1.1
|
||
Host: pay.your-domain.com
|
||
Content-Type: application/json
|
||
X-App-Id: app_20260101001
|
||
X-Timestamp: 1740700800
|
||
X-Sign: a3f8c2d1e4b7f9a0c5d2e8f1b4a7c3d6e9f2b5a8c1d4e7f0a3b6c9d2e5f8a1b4
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 签名算法
|
||
|
||
### 3.1 算法说明
|
||
|
||
签名算法为 **HMAC-SHA256**,签名结果为 **十六进制小写字符串**。
|
||
|
||
### 3.2 签名步骤
|
||
|
||
**第一步**:拼接签名原文
|
||
|
||
```
|
||
签名原文 = app_id + timestamp + requestBody
|
||
```
|
||
|
||
- `app_id`:Header 中的 X-App-Id
|
||
- `timestamp`:Header 中的 X-Timestamp
|
||
- `requestBody`:请求体原始 JSON 字符串(GET 请求时为空字符串 `""`)
|
||
|
||
**第二步**:HMAC-SHA256 签名
|
||
|
||
```
|
||
sign = HMAC-SHA256(签名原文, app_secret)
|
||
```
|
||
|
||
**第三步**:Hex 编码,填入 `X-Sign` Header
|
||
|
||
### 3.3 注意事项
|
||
|
||
- 请求体 JSON 中字段顺序不影响签名,但序列化后的**字节内容必须与实际发送的 body 完全一致**
|
||
- 时间戳精度为秒,不是毫秒
|
||
- GET 请求 body 为空字符串参与签名
|
||
|
||
---
|
||
|
||
## 4. 统一响应格式
|
||
|
||
所有接口均返回 JSON,HTTP 状态码为 200(业务错误也返回 200,通过 `code` 字段区分)。
|
||
|
||
**成功响应:**
|
||
|
||
```json
|
||
{
|
||
"code": "0",
|
||
"message": "success",
|
||
"data": { ... },
|
||
"trace_id": "7f3a2b1c4d5e6f7a"
|
||
}
|
||
```
|
||
|
||
**失败响应:**
|
||
|
||
```json
|
||
{
|
||
"code": "30004",
|
||
"message": "退款金额超过可退金额",
|
||
"trace_id": "7f3a2b1c4d5e6f7a"
|
||
}
|
||
```
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `code` | string | `"0"` 为成功,其他为错误码,见[错误码表](#9-错误码对照表) |
|
||
| `message` | string | 提示信息 |
|
||
| `data` | object | 业务数据,仅成功时返回 |
|
||
| `trace_id` | string | 请求追踪 ID,排查问题时提供给平台 |
|
||
|
||
---
|
||
|
||
## 5. 支付接口
|
||
|
||
### 5.1 统一下单
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
POST /api/v1/pay/unified-order
|
||
```
|
||
|
||
**请求参数**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `merchant_order_no` | string | 是 | 商户系统订单号,同一 app_id 下唯一,最长 64 位 |
|
||
| `pay_method` | string | 是 | 支付方式,见下方枚举 |
|
||
| `amount` | int64 | 是 | 订单金额,单位**分**,最小 1 |
|
||
| `subject` | string | 是 | 商品描述,最长 256 位 |
|
||
| `notify_url` | string | 是 | 支付结果异步通知地址,须为合法 HTTPS URL |
|
||
| `expire_minutes` | int | 否 | 订单有效期(分钟),默认 30 分钟 |
|
||
| `profit_sharing_amount` | int64 | 否 | 分润金额(分),0 表示不分润 |
|
||
| `extra` | object | 否 | 支付方式附加参数,见下方说明 |
|
||
| `merchant_id` | string | 否 | 指定收款商户 ID(SaaS 多商户场景),须为本 app_id 下已进件审核通过的商户 |
|
||
|
||
**`pay_method` 枚举值**
|
||
|
||
| 值 | 说明 |
|
||
|----|------|
|
||
| `WECHAT_JSAPI` | 微信公众号支付 |
|
||
| `WECHAT_H5` | 微信 H5 支付 |
|
||
| `WECHAT_NATIVE` | 微信扫码支付 |
|
||
| `WECHAT_MINI` | 微信小程序支付 |
|
||
| `ALIPAY` | 支付宝扫码支付 |
|
||
| `QUICK_PAY` | 快捷支付 |
|
||
|
||
**`extra` 附加参数说明**
|
||
|
||
| 支付方式 | 参数 | 说明 |
|
||
|---------|------|------|
|
||
| `WECHAT_JSAPI` | `openid`(必填) | 用户在公众号的 openid |
|
||
| `WECHAT_MINI` | `openid`(必填) | 用户在小程序的 openid |
|
||
| `WECHAT_JSAPI` / `WECHAT_MINI` | `sub_appid`(可选) | 子商户公众号/小程序 AppID |
|
||
|
||
**请求示例**
|
||
|
||
```json
|
||
{
|
||
"merchant_order_no": "ORD20260228001",
|
||
"pay_method": "WECHAT_JSAPI",
|
||
"amount": 9900,
|
||
"subject": "会员充值 - 月卡",
|
||
"notify_url": "https://your-server.com/callback/pay",
|
||
"expire_minutes": 15,
|
||
"extra": {
|
||
"openid": "oxxxxxxxxxxxxxxxxxxxxxxx"
|
||
}
|
||
}
|
||
```
|
||
|
||
**成功响应 `data`**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `trade_no` | string | 平台交易号,格式 `PAYyyMMddXXXXXXXX` |
|
||
| `pay_credential` | object | 支付凭证,透传给前端拉起支付,格式因支付方式不同而异 |
|
||
| `is_idempotent` | bool | `true` 表示该订单号已存在,返回的是已有订单信息 |
|
||
|
||
**响应示例**
|
||
|
||
```json
|
||
{
|
||
"code": "0",
|
||
"message": "success",
|
||
"data": {
|
||
"trade_no": "PAY26022800000001",
|
||
"pay_credential": {
|
||
"appId": "wx1234567890",
|
||
"timeStamp": "1740700800",
|
||
"nonceStr": "abc123xyz",
|
||
"package": "prepay_id=wx28...",
|
||
"signType": "RSA",
|
||
"paySign": "..."
|
||
},
|
||
"is_idempotent": false
|
||
}
|
||
}
|
||
```
|
||
|
||
> **幂等说明**:使用相同的 `merchant_order_no` 重复调用,不会重复下单,会直接返回已有订单的支付凭证,并且 `is_idempotent` 为 `true`。
|
||
|
||
---
|
||
|
||
### 5.2 查询订单
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
GET /api/v1/pay/query/{trade_no}
|
||
```
|
||
|
||
**路径参数**
|
||
|
||
| 参数 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `trade_no` | string | 平台交易号(下单时返回的 `trade_no`) |
|
||
|
||
**请求示例**
|
||
|
||
```http
|
||
GET /api/v1/pay/query/PAY26022800000001
|
||
```
|
||
|
||
**成功响应 `data`**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `trade_no` | string | 平台交易号 |
|
||
| `merchant_order_no` | string | 商户订单号 |
|
||
| `pay_method` | string | 支付方式 |
|
||
| `amount` | int64 | 订单金额(分) |
|
||
| `status` | string | 订单状态,见[订单状态说明](#8-订单状态说明) |
|
||
| `channel_trade_no` | string | 渠道侧交易号 |
|
||
| `pay_time` | string | 支付成功时间,ISO8601 格式,未支付时为 null |
|
||
| `created_at` | string | 订单创建时间,ISO8601 格式 |
|
||
|
||
**响应示例**
|
||
|
||
```json
|
||
{
|
||
"code": "0",
|
||
"message": "success",
|
||
"data": {
|
||
"trade_no": "PAY26022800000001",
|
||
"merchant_order_no": "ORD20260228001",
|
||
"pay_method": "WECHAT_JSAPI",
|
||
"amount": 9900,
|
||
"status": "PAID",
|
||
"channel_trade_no": "4200002345202602280000000001",
|
||
"pay_time": "2026-02-28T10:05:00Z",
|
||
"created_at": "2026-02-28T10:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5.3 关闭订单
|
||
|
||
关闭仍在支付中的订单,已支付订单不可关闭(请使用退款接口)。
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
POST /api/v1/pay/close
|
||
```
|
||
|
||
**请求参数**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `trade_no` | string | 是 | 平台交易号 |
|
||
|
||
**请求示例**
|
||
|
||
```json
|
||
{
|
||
"trade_no": "PAY26022800000001"
|
||
}
|
||
```
|
||
|
||
**成功响应**
|
||
|
||
```json
|
||
{
|
||
"code": "0",
|
||
"message": "success"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 5.4 申请退款
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
POST /api/v1/pay/refund
|
||
```
|
||
|
||
**请求参数**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `trade_no` | string | 是 | 平台交易号 |
|
||
| `refund_amount` | int64 | 是 | 退款金额(分),不得超过原订单金额 |
|
||
| `reason` | string | 否 | 退款原因,最长 256 位 |
|
||
| `notify_url` | string | 否 | 退款结果异步通知地址 |
|
||
|
||
**请求示例**
|
||
|
||
```json
|
||
{
|
||
"trade_no": "PAY26022800000001",
|
||
"refund_amount": 9900,
|
||
"reason": "用户申请退款",
|
||
"notify_url": "https://your-server.com/callback/refund"
|
||
}
|
||
```
|
||
|
||
**成功响应 `data`**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `refund_no` | string | 平台退款单号 |
|
||
| `trade_no` | string | 关联的平台交易号 |
|
||
| `refund_amount` | int64 | 退款金额(分) |
|
||
| `status` | string | 退款状态:`PENDING` / `PROCESSING` / `SUCCESS` / `FAILED` |
|
||
| `channel_refund_no` | string | 渠道侧退款单号 |
|
||
|
||
**响应示例**
|
||
|
||
```json
|
||
{
|
||
"code": "0",
|
||
"message": "success",
|
||
"data": {
|
||
"refund_no": "REF26022800000001",
|
||
"trade_no": "PAY26022800000001",
|
||
"refund_amount": 9900,
|
||
"status": "PROCESSING",
|
||
"channel_refund_no": "50300807092026022800000000001"
|
||
}
|
||
}
|
||
```
|
||
|
||
> **注意**:退款为异步处理,状态 `PROCESSING` 表示已提交渠道,最终结果通过异步通知推送,建议同时通过查询接口轮询确认。
|
||
|
||
---
|
||
|
||
### 5.5 查询退款
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
GET /api/v1/pay/refund/query/{refund_no}
|
||
```
|
||
|
||
**路径参数**
|
||
|
||
| 参数 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `refund_no` | string | 平台退款单号(申请退款时返回的 `refund_no`) |
|
||
|
||
**成功响应 `data`**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `refund_no` | string | 平台退款单号 |
|
||
| `trade_no` | string | 关联的平台交易号 |
|
||
| `refund_amount` | int64 | 退款金额(分) |
|
||
| `status` | string | 退款状态 |
|
||
| `channel_refund_no` | string | 渠道侧退款单号 |
|
||
| `refund_time` | string | 退款成功时间,ISO8601 格式,未完成时为 null |
|
||
|
||
---
|
||
|
||
## 6. 商户进件接口
|
||
|
||
> 适用场景:你的系统是 **SaaS 平台**,平台上的客户需要先完成进件(企业入网)才能以自己名义收款。
|
||
>
|
||
> 所有进件接口与支付接口使用**相同的 HMAC 鉴权**(`app_id` + `app_secret`),且数据完全按 `app_id` 隔离——你只能看到自己名下的商户。
|
||
|
||
### 典型流程
|
||
|
||
```
|
||
1. 创建商户记录(获取 merchant_id)
|
||
↓
|
||
2. 上传营业执照等证件文件(获取 file_id)
|
||
↓
|
||
3. 提交进件申请(将 file_id 填入 submit_data)
|
||
↓
|
||
4. 轮询查询审核状态,直至 APPROVED
|
||
↓
|
||
5. 统一下单时传入 merchant_id,指定该商户收款
|
||
```
|
||
|
||
---
|
||
|
||
### 6.1 创建商户
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
POST /api/v1/merchant
|
||
```
|
||
|
||
**请求参数**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `merchant_id` | string | 是 | 你方系统的商户唯一标识,最长 32 位,由调用方自定义 |
|
||
| `merchant_name` | string | 是 | 商户名称 |
|
||
| `license_no` | string | 否 | 营业执照号 |
|
||
| `legal_person` | string | 否 | 法定代表人姓名 |
|
||
| `bank_account` | string | 否 | 结算银行账号(脱敏存储) |
|
||
|
||
**请求示例**
|
||
|
||
```json
|
||
{
|
||
"merchant_id": "tenant_0001",
|
||
"merchant_name": "示例科技有限公司",
|
||
"license_no": "91310000XXXXXXXXXX",
|
||
"legal_person": "张三",
|
||
"bank_account": "6222021234567890123"
|
||
}
|
||
```
|
||
|
||
**成功响应 `data`**
|
||
|
||
```json
|
||
{
|
||
"merchant_id": "tenant_0001"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 6.2 上传证件文件
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
POST /api/v1/merchant/upload-file
|
||
```
|
||
|
||
**请求格式**:`multipart/form-data`
|
||
|
||
| 字段 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `file` | file | 是 | 证件图片,支持 JPG / PNG / PDF |
|
||
| `file_media_type` | string | 是 | 文件类型代码,由渠道定义,如 `01`=营业执照、`02`=身份证正面 |
|
||
| `channel_code` | string | 否 | 渠道代码,默认 `HEEPAY` |
|
||
|
||
**成功响应 `data`**
|
||
|
||
```json
|
||
{
|
||
"file_id": "3000000001234567"
|
||
}
|
||
```
|
||
|
||
> 上传返回的 `file_id` 在提交进件时填入 `submit_data`,有效期以渠道规定为准(通常 48 小时内使用)。
|
||
|
||
---
|
||
|
||
### 6.3 提交进件申请
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
POST /api/v1/merchant/{merchant_id}/apply
|
||
```
|
||
|
||
**路径参数**
|
||
|
||
| 参数 | 说明 |
|
||
|------|------|
|
||
| `merchant_id` | 创建商户时使用的商户 ID |
|
||
|
||
**请求参数**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `channel_code` | string | 是 | 渠道代码,如 `HEEPAY` |
|
||
| `submit_data` | object | 否 | 渠道要求的进件业务参数,内容因渠道而异,详见各渠道对接手册 |
|
||
|
||
**请求示例**
|
||
|
||
```json
|
||
{
|
||
"channel_code": "HEEPAY",
|
||
"submit_data": {
|
||
"business_license_no": "91310000XXXXXXXXXX",
|
||
"business_license_copy": "3000000001234567",
|
||
"id_card_front": "3000000001234568",
|
||
"id_card_back": "3000000001234569",
|
||
"contact_name": "张三",
|
||
"contact_phone": "138XXXXXXXX",
|
||
"bank_account_no": "6222021234567890123",
|
||
"bank_name": "中国工商银行"
|
||
}
|
||
}
|
||
```
|
||
|
||
**成功响应 `data`**
|
||
|
||
```json
|
||
{
|
||
"application_id": "APP1a2b3c4d5e6f7a8b"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 6.4 查询审核状态
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
GET /api/v1/merchant/{merchant_id}/audit
|
||
```
|
||
|
||
**成功响应 `data`**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `application_id` | string | 进件申请 ID |
|
||
| `merchant_id` | string | 商户 ID |
|
||
| `channel_code` | string | 渠道代码 |
|
||
| `audit_status` | string | 审核状态,见下方枚举 |
|
||
| `reject_reason` | string | 拒绝原因,仅 `REJECTED` 时返回 |
|
||
| `submitted_at` | string | 提交时间 |
|
||
| `audited_at` | string | 审核完成时间,未完成时为 null |
|
||
|
||
**`audit_status` 枚举值**
|
||
|
||
| 值 | 说明 |
|
||
|----|------|
|
||
| `SUBMITTING` | 提交中,正在调用渠道 |
|
||
| `REVIEWING` | 审核中,渠道人工审核 |
|
||
| `APPROVED` | 审核通过,商户可正常收款 |
|
||
| `REJECTED` | 审核拒绝,查看 `reject_reason` 修改后重新提交 |
|
||
|
||
**响应示例**
|
||
|
||
```json
|
||
{
|
||
"code": "0",
|
||
"message": "success",
|
||
"data": {
|
||
"application_id": "APP1a2b3c4d5e6f7a8b",
|
||
"merchant_id": "tenant_0001",
|
||
"channel_code": "HEEPAY",
|
||
"audit_status": "APPROVED",
|
||
"submitted_at": "2026-02-28T10:00:00Z",
|
||
"audited_at": "2026-02-28T14:30:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 6.5 查询商户详情
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
GET /api/v1/merchant/{merchant_id}
|
||
```
|
||
|
||
**成功响应 `data`**:返回完整商户对象,字段同创建时入参,额外包含:
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `status` | string | 商户状态:`PENDING` / `ACTIVE` / `FROZEN` / `REJECTED` |
|
||
| `channel_merchant_id` | string | 渠道侧商户 ID,进件审核通过后由渠道下发 |
|
||
| `created_at` | string | 创建时间 |
|
||
|
||
> 如传入的 `merchant_id` 不属于当前 `app_id`,接口返回 404。
|
||
|
||
---
|
||
|
||
### 6.6 查询商户列表
|
||
|
||
**接口地址**
|
||
|
||
```
|
||
GET /api/v1/merchant
|
||
```
|
||
|
||
**Query 参数**
|
||
|
||
| 参数 | 类型 | 必填 | 说明 |
|
||
|------|------|------|------|
|
||
| `status` | string | 否 | 按状态过滤:`PENDING` / `ACTIVE` / `FROZEN` / `REJECTED` |
|
||
| `limit` | int | 否 | 每页数量,默认 20 |
|
||
| `offset` | int | 否 | 偏移量,默认 0 |
|
||
|
||
**响应示例**
|
||
|
||
```json
|
||
{
|
||
"code": "0",
|
||
"message": "success",
|
||
"data": {
|
||
"list": [
|
||
{
|
||
"merchant_id": "tenant_0001",
|
||
"merchant_name": "示例科技有限公司",
|
||
"status": "ACTIVE",
|
||
"created_at": "2026-02-28T10:00:00Z"
|
||
}
|
||
],
|
||
"limit": 20,
|
||
"offset": 0
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 6.7 指定商户收款(统一下单扩展)
|
||
|
||
商户进件通过后,在[统一下单](#51-统一下单)时传入 `merchant_id`,平台会将该商户的渠道收款账号注入下单参数,实现**分账到具体商户**:
|
||
|
||
```json
|
||
{
|
||
"merchant_order_no": "ORD20260228002",
|
||
"pay_method": "WECHAT_JSAPI",
|
||
"amount": 9900,
|
||
"subject": "订单支付",
|
||
"notify_url": "https://your-server.com/callback/pay",
|
||
"merchant_id": "tenant_0001",
|
||
"extra": {
|
||
"openid": "oxxxxxxxxxxxxxxxxxxxxxxx"
|
||
}
|
||
}
|
||
```
|
||
|
||
> `merchant_id` 必须属于当前 `app_id`,且商户状态为 `ACTIVE`,否则返回 `30001`。
|
||
|
||
---
|
||
|
||
## 7. 异步通知
|
||
|
||
### 7.1 支付成功通知
|
||
|
||
用户支付成功后,平台将向下单时提供的 `notify_url` 发送 POST 请求。
|
||
|
||
**通知内容**
|
||
|
||
```json
|
||
{
|
||
"trade_no": "PAY26022800000001",
|
||
"merchant_order_no": "ORD20260228001",
|
||
"status": "PAID",
|
||
"amount": 9900,
|
||
"pay_method": "WECHAT_JSAPI",
|
||
"channel_trade_no": "4200002345202602280000000001",
|
||
"pay_time": "2026-02-28T10:05:00Z"
|
||
}
|
||
```
|
||
|
||
### 7.2 退款结果通知
|
||
|
||
退款处理完成后,平台将向退款时提供的 `notify_url` 发送 POST 请求。
|
||
|
||
**通知内容**
|
||
|
||
```json
|
||
{
|
||
"refund_no": "REF26022800000001",
|
||
"trade_no": "PAY26022800000001",
|
||
"merchant_order_no": "ORD20260228001",
|
||
"refund_amount": 9900,
|
||
"status": "SUCCESS",
|
||
"refund_time": "2026-02-28T10:30:00Z"
|
||
}
|
||
```
|
||
|
||
### 7.3 通知应答规则
|
||
|
||
**接收通知后,你的服务器必须在 10 秒内响应以下内容:**
|
||
|
||
```
|
||
HTTP 200
|
||
Body: success
|
||
```
|
||
|
||
返回任何其他内容,或超时未响应,平台将认为通知失败并重试。
|
||
|
||
### 7.4 重试策略
|
||
|
||
| 第 N 次 | 延迟 |
|
||
|--------|------|
|
||
| 1 | 立即 |
|
||
| 2 | 15 秒后 |
|
||
| 3 | 30 秒后 |
|
||
| 4 | 1 分钟后 |
|
||
| 5 | 5 分钟后 |
|
||
| 6 | 30 分钟后 |
|
||
| 7 | 1 小时后 |
|
||
| 8 | 6 小时后 |
|
||
| 9 | 12 小时后 |
|
||
|
||
超过 9 次仍失败将停止重试,请通过**查询接口**主动同步最终状态。
|
||
|
||
### 7.5 防重处理
|
||
|
||
通知可能因网络原因重复发送,请以 `trade_no` 为唯一键做幂等处理,避免重复发货或重复入账。
|
||
|
||
---
|
||
|
||
## 8. 订单状态说明
|
||
|
||
### 8.1 交易订单状态
|
||
|
||
| 状态 | 说明 |
|
||
|------|------|
|
||
| `CREATING` | 创建中,正在调用渠道下单 |
|
||
| `PAYING` | 待支付,等待用户付款 |
|
||
| `PAID` | 支付成功 |
|
||
| `CLOSED` | 已关闭 |
|
||
| `FAILED` | 支付失败 |
|
||
| `CREATE_FAILED` | 下单失败,渠道返回错误 |
|
||
| `REFUNDED` | 已全额退款 |
|
||
|
||
### 8.2 退款状态
|
||
|
||
| 状态 | 说明 |
|
||
|------|------|
|
||
| `PENDING` | 待处理 |
|
||
| `PROCESSING` | 处理中,已提交渠道 |
|
||
| `SUCCESS` | 退款成功 |
|
||
| `FAILED` | 退款失败,可联系平台处理 |
|
||
|
||
---
|
||
|
||
## 9. 错误码对照表
|
||
|
||
| 错误码 | HTTP 状态码 | 说明 | 处理建议 |
|
||
|--------|------------|------|---------|
|
||
| `0` | 200 | 成功 | — |
|
||
| `10001` | 400 | 参数校验失败 | 检查请求参数格式和必填项 |
|
||
| `10002` | 400 | 缺少必填参数 | 补充缺少的参数 |
|
||
| `10003` | 400 | 不支持的支付方式 | 检查 pay_method 是否正确 |
|
||
| `10004` | 400 | 金额非法 | 金额须为正整数(分) |
|
||
| `20001` | 401 | 签名验证失败 | 检查签名算法和 app_secret |
|
||
| `20002` | 401 | 应用不存在或已禁用 | 联系平台确认 app_id 状态 |
|
||
| `30001` | 422 | 订单不存在 | 检查 trade_no 是否正确 |
|
||
| `30002` | 422 | 订单已支付 | 勿重复支付 |
|
||
| `30003` | 422 | 订单已关闭 | 需重新下单 |
|
||
| `30004` | 422 | 退款金额超过可退金额 | 检查退款金额 |
|
||
| `30009` | 422 | 订单未支付,无法退款 | 确认订单已支付后再退款 |
|
||
| `30010` | 422 | 退款单不存在 | 检查 refund_no 是否正确 |
|
||
| `40001` | 502 | 渠道下单失败 | 稍后重试,仍失败请联系平台 |
|
||
| `40002` | 502 | 渠道退款失败 | 稍后重试,仍失败请联系平台 |
|
||
| `40003` | 502 | 渠道调用超时 | 稍后重试 |
|
||
| `50099` | 500 | 系统内部错误 | 记录 trace_id 联系平台排查 |
|
||
|
||
---
|
||
|
||
## 10. 最佳实践
|
||
|
||
### 10.1 下单流程建议
|
||
|
||
```
|
||
1. 用户确认支付
|
||
↓
|
||
2. 你的服务端调用【统一下单】接口
|
||
↓
|
||
3. 返回 pay_credential 给前端
|
||
↓
|
||
4. 前端调用微信/支付宝 SDK 拉起支付
|
||
↓
|
||
5. 接收异步通知 → 更新订单状态 → 返回 "success"
|
||
↓
|
||
6. (兜底)若通知未收到,定时调用【查询订单】接口轮询
|
||
```
|
||
|
||
### 10.2 安全注意事项
|
||
|
||
- `app_secret` 只在服务端使用,**禁止出现在前端代码或 App 客户端中**
|
||
- 异步通知的 IP 建议加入白名单(联系平台获取出口 IP 段)
|
||
- 通知接收后务必**先返回 `success`,再处理业务逻辑**,避免超时导致重复通知
|
||
|
||
### 10.3 金额单位
|
||
|
||
所有金额字段均以**分**为单位,整数类型:
|
||
- 1 元 = `100`
|
||
- 9.9 元 = `990`
|
||
- 99.99 元 = `9999`
|
||
|
||
### 10.4 时区
|
||
|
||
所有时间字段均为 **UTC 时间**,ISO8601 格式,例如 `2026-02-28T10:05:00Z`。
|
||
|
||
---
|
||
|
||
## 11. 签名示例代码
|
||
|
||
### Go
|
||
|
||
```go
|
||
import (
|
||
"crypto/hmac"
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"fmt"
|
||
"strconv"
|
||
"time"
|
||
)
|
||
|
||
func sign(appID, appSecret, body string) (timestamp, sign string) {
|
||
ts := strconv.FormatInt(time.Now().Unix(), 10)
|
||
raw := appID + ts + body
|
||
mac := hmac.New(sha256.New, []byte(appSecret))
|
||
mac.Write([]byte(raw))
|
||
return ts, hex.EncodeToString(mac.Sum(nil))
|
||
}
|
||
```
|
||
|
||
### Python
|
||
|
||
```python
|
||
import hmac
|
||
import hashlib
|
||
import time
|
||
|
||
def sign(app_id: str, app_secret: str, body: str) -> tuple[str, str]:
|
||
timestamp = str(int(time.time()))
|
||
raw = app_id + timestamp + body
|
||
signature = hmac.new(
|
||
app_secret.encode("utf-8"),
|
||
raw.encode("utf-8"),
|
||
hashlib.sha256
|
||
).hexdigest()
|
||
return timestamp, signature
|
||
```
|
||
|
||
### Java
|
||
|
||
```java
|
||
import javax.crypto.Mac;
|
||
import javax.crypto.spec.SecretKeySpec;
|
||
import java.nio.charset.StandardCharsets;
|
||
|
||
public static String[] sign(String appId, String appSecret, String body) throws Exception {
|
||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||
String raw = appId + timestamp + body;
|
||
Mac mac = Mac.getInstance("HmacSHA256");
|
||
mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
|
||
byte[] bytes = mac.doFinal(raw.getBytes(StandardCharsets.UTF_8));
|
||
StringBuilder sb = new StringBuilder();
|
||
for (byte b : bytes) sb.append(String.format("%02x", b));
|
||
return new String[]{timestamp, sb.toString()};
|
||
}
|
||
```
|
||
|
||
### PHP
|
||
|
||
```php
|
||
function sign(string $appId, string $appSecret, string $body): array {
|
||
$timestamp = (string)time();
|
||
$raw = $appId . $timestamp . $body;
|
||
$signature = hash_hmac('sha256', $raw, $appSecret);
|
||
return [$timestamp, $signature];
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
如有问题请联系平台技术支持,并提供接口返回的 `trace_id`。
|