Files
pay-bridge/docs/api-integration-guide.md
2026-03-13 15:51:59 +08:00

23 KiB
Raw Blame History

Pay-Bridge 支付网关接入文档

版本 日期 说明
v1.2 2026-02-28 新增商户进件接口SaaS 多商户场景)、统一下单支持 merchant_id
v1.1 2026-02-28 更新接入凭证申请方式
v1.0 2026-02-28 初始版本

目录

  1. 接入准备
  2. 请求规范
  3. 签名算法
  4. 统一响应格式
  5. 支付接口
  6. 商户进件接口SaaS 多商户场景)
  7. 异步通知
  8. 订单状态说明
  9. 错误码对照表
  10. 最佳实践
  11. 签名示例代码

1. 接入准备

1.1 获取接入凭证

接入凭证由平台管理员在管理后台创建。请联系平台管理员,按以下步骤为你的系统开通接入权限:

  1. 管理员登录管理后台,进入「接入应用」模块
  2. 点击「新建应用」填写应用名称商城系统、ERP 系统)
  3. 系统自动生成 app_idapp_secretSecret 仅在创建时展示一次
  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-Typeapplication/json

2.2 公共请求头

所有请求必须携带以下 Header

Header 类型 必填 说明
X-App-Id string 平台分配的 app_id
X-Timestamp string 当前 Unix 时间戳(秒),与服务器时间差须在 5 分钟以内
X-Sign string 请求签名,见签名算法
Content-Type string application/json

示例:

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_idHeader 中的 X-App-Id
  • timestampHeader 中的 X-Timestamp
  • requestBody:请求体原始 JSON 字符串GET 请求时为空字符串 ""

第二步HMAC-SHA256 签名

sign = HMAC-SHA256(签名原文, app_secret)

第三步Hex 编码,填入 X-Sign Header

3.3 注意事项

  • 请求体 JSON 中字段顺序不影响签名,但序列化后的字节内容必须与实际发送的 body 完全一致
  • 时间戳精度为秒,不是毫秒
  • GET 请求 body 为空字符串参与签名

4. 统一响应格式

所有接口均返回 JSONHTTP 状态码为 200业务错误也返回 200通过 code 字段区分)。

成功响应:

{
  "code": "0",
  "message": "success",
  "data": { ... },
  "trace_id": "7f3a2b1c4d5e6f7a"
}

失败响应:

{
  "code": "30004",
  "message": "退款金额超过可退金额",
  "trace_id": "7f3a2b1c4d5e6f7a"
}
字段 类型 说明
code string "0" 为成功,其他为错误码,见错误码表
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 指定收款商户 IDSaaS 多商户场景),须为本 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

请求示例

{
  "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 表示该订单号已存在,返回的是已有订单信息

响应示例

{
  "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_idempotenttrue


5.2 查询订单

接口地址

GET /api/v1/pay/query/{trade_no}

路径参数

参数 类型 说明
trade_no string 平台交易号(下单时返回的 trade_no

请求示例

GET /api/v1/pay/query/PAY26022800000001

成功响应 data

字段 类型 说明
trade_no string 平台交易号
merchant_order_no string 商户订单号
pay_method string 支付方式
amount int64 订单金额(分)
status string 订单状态,见订单状态说明
channel_trade_no string 渠道侧交易号
pay_time string 支付成功时间ISO8601 格式,未支付时为 null
created_at string 订单创建时间ISO8601 格式

响应示例

{
  "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 平台交易号

请求示例

{
  "trade_no": "PAY26022800000001"
}

成功响应

{
  "code": "0",
  "message": "success"
}

5.4 申请退款

接口地址

POST /api/v1/pay/refund

请求参数

参数 类型 必填 说明
trade_no string 平台交易号
refund_amount int64 退款金额(分),不得超过原订单金额
reason string 退款原因,最长 256 位
notify_url string 退款结果异步通知地址

请求示例

{
  "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 渠道侧退款单号

响应示例

{
  "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 结算银行账号(脱敏存储)

请求示例

{
  "merchant_id": "tenant_0001",
  "merchant_name": "示例科技有限公司",
  "license_no": "91310000XXXXXXXXXX",
  "legal_person": "张三",
  "bank_account": "6222021234567890123"
}

成功响应 data

{
  "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

{
  "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 渠道要求的进件业务参数,内容因渠道而异,详见各渠道对接手册

请求示例

{
  "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

{
  "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 修改后重新提交

响应示例

{
  "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

响应示例

{
  "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 指定商户收款(统一下单扩展)

商户进件通过后,在统一下单时传入 merchant_id,平台会将该商户的渠道收款账号注入下单参数,实现分账到具体商户

{
  "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 请求。

通知内容

{
  "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 请求。

通知内容

{
  "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

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

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

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

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