概述
PayHub 支付平台为第三方商户提供统一收单 API。商户通过简单的 HTTP 接口即可快速接入多渠道支付能力。
| 支付方式 |
接口 |
适用场景 |
| 聚合扫码支付 |
POST /api/cashier/qrcode-pay |
PC / H5 展示二维码,用户扫码 |
| 工商E支付 |
POST /api/cashier/icbc-epay |
生成支付链接,用户跳转工商E支付收银台 |
| 微信 JSAPI |
POST /api/cashier/wechat-js-pay |
微信公众号 / 小程序内唤起支付 |
| 亿速易汇支付 |
POST /api/cashier/yisuyihui-pay |
生成上游签名支付链接,浏览器打开后付款 |
| 订单查询 |
POST /api/cashier/order-query |
按平台单号或商户单号查询订单状态 |
接入准备
获取凭证
在 PayHub 管理后台完成商户入驻后,获取以下信息:
| 凭证 |
说明 |
app_id |
商户应用标识(唯一) |
app_secret |
应用密钥,用于 HMAC-SHA256 签名 |
callback_url |
支付结果回调地址 |
⚠ 重要app_secret 仅创建时展示一次,请妥善保管。遗失可在管理后台重置。
环境地址
| 环境 |
Base URL |
| 生产 |
https://payment.xmlhy.cn |
| 测试 |
https://payment.xmlhy.cn |
安全机制
| 机制 |
说明 |
| HMAC-SHA256 签名 |
每个请求必须携带签名,防篡改 |
| IP 白名单 |
商户可配置允许请求的服务器 IP 列表,非白名单 IP 直接返回 401 |
| 提单限频 |
同一 IP 3 秒内仅允许提交 1 次订单(基于 Redis),防止恶意刷单 |
| 时间戳校验 |
timestamp 与服务器时间偏差超过 ±5 分钟则拒绝请求 |
| 随机串防重放 |
nonce 随机字符串,防止请求被截获后重放 |
签名算法(HMAC-SHA256)
签名步骤
- 取所有请求参数(不含 sign),去掉值为空的参数
- 按 key 的 ASCII 升序排列
- 拼接成
key1=value1&key2=value2&... 格式
- 以
app_secret 为密钥,做 HMAC-SHA256 运算,输出 64 位小写 hex
签名示例
原始参数:
app_id = pk_live_51H8xK2eZvKYlo2C9xK2eZvKYlo2C
amount = 1000
merchant_order_no = ORD20260315001
notify_url = https://example.com/notify
timestamp = 1710489862
nonce = a1b2c3d4
排序拼接:
amount=1000&app_id=pk_live_51H8xK2eZvKYlo2C9xK2eZvKYlo2C&merchant_order_no=ORD20260315001&nonce=a1b2c3d4¬ify_url=https://example.com/notify×tamp=1710489862
签名:
sign = hmac_sha256(拼接字符串, app_secret) → "e5b7a3f1c9d2..."
多语言实现
// PHP
$params = [
'app_id' => $appId,
'amount' => 1000,
'merchant_order_no' => 'ORD20260315001',
'notify_url' => 'https://example.com/notify',
'timestamp' => time(),
'nonce' => bin2hex(random_bytes(16)),
];
// 1. 去空值 + 排序
$params = array_filter($params, fn($v) => $v !== '' && $v !== null);
ksort($params);
// 2. 拼接
$signStr = http_build_query($params); // 自动 key=value&key=value
// 3. HMAC-SHA256
$params['sign'] = hash_hmac('sha256', $signStr, $appSecret);
# Python
import hmac, hashlib, time, secrets, urllib.parse
params = {
'app_id': app_id,
'amount': 1000,
'merchant_order_no': 'ORD20260315001',
'notify_url': 'https://example.com/notify',
'timestamp': int(time.time()),
'nonce': secrets.token_hex(16),
}
# 去空值 + 排序 + 拼接
filtered = {k: v for k, v in params.items() if v not in ('', None)}
sign_str = '&'.join(f'{k}={v}' for k, v in sorted(filtered.items()))
# HMAC-SHA256
params['sign'] = hmac.new(
app_secret.encode(), sign_str.encode(), hashlib.sha256
).hexdigest()
// Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
import org.apache.commons.codec.binary.Hex;
Map<String, String> params = new TreeMap<>(); // TreeMap 自动排序
params.put("app_id", appId);
params.put("amount", "1000");
params.put("merchant_order_no", "ORD20260315001");
params.put("notify_url", "https://example.com/notify");
params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
params.put("nonce", UUID.randomUUID().toString().replace("-", ""));
// 拼接
StringBuilder sb = new StringBuilder();
params.forEach((k, v) -> {
if (v != null && !v.isEmpty()) {
if (sb.length() > 0) sb.append("&");
sb.append(k).append("=").append(v);
}
});
// HMAC-SHA256
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(appSecret.getBytes(), "HmacSHA256"));
String sign = Hex.encodeHexString(mac.doFinal(sb.toString().getBytes()));
// Node.js
const crypto = require('crypto');
const params = {
app_id: appId,
amount: 1000,
merchant_order_no: 'ORD20260315001',
notify_url: 'https://example.com/notify',
timestamp: Math.floor(Date.now() / 1000),
nonce: crypto.randomBytes(16).toString('hex'),
};
// 去空值 + 排序 + 拼接
const signStr = Object.keys(params)
.filter(k => params[k] !== '' && params[k] != null)
.sort()
.map(k => `${k}=${params[k]}`)
.join('&');
// HMAC-SHA256
params.sign = crypto.createHmac('sha256', appSecret)
.update(signStr).digest('hex');
// Go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"sort"
"strings"
)
params := map[string]string{
"app_id": appId,
"amount": "1000",
"merchant_order_no": "ORD20260315001",
"notify_url": "https://example.com/notify",
"timestamp": fmt.Sprintf("%d", time.Now().Unix()),
"nonce": hex.EncodeToString(randomBytes(16)),
}
// 排序 + 拼接
keys := make([]string, 0, len(params))
for k, v := range params {
if v != "" { keys = append(keys, k) }
}
sort.Strings(keys)
pairs := make([]string, len(keys))
for i, k := range keys { pairs[i] = k + "=" + params[k] }
signStr := strings.Join(pairs, "&")
// HMAC-SHA256
h := hmac.New(sha256.New, []byte(appSecret))
h.Write([]byte(signStr))
sign := hex.EncodeToString(h.Sum(nil))
通用规范
请求格式
| 项目 |
要求 |
| 协议 |
HTTPS(生产环境强制) |
| 方法 |
POST |
| Content-Type |
application/json |
| 字符编码 |
UTF-8 |
公共请求参数
每个 API 请求都必须包含:
| 参数 |
类型 |
必填 |
说明 |
app_id |
string |
✅ |
商户应用 ID |
timestamp |
int |
✅ |
Unix 秒级时间戳,±5 分钟有效 |
nonce |
string |
✅ |
随机字符串,≤32 位 |
sign |
string |
✅ |
HMAC-SHA256 签名 |
统一响应结构
{
"code": 200,
"message": "ok",
"data": { ... }
}
聚合扫码支付
生成聚合二维码,用户可使用 支付宝 / 微信 / 云闪付 暂时只支持支付宝 扫码或跳转直链(H5 场景可直跳唤起)完成支付。
请求参数
| 参数 |
类型 |
必填 |
说明 |
app_id |
string |
✅ |
商户应用 ID |
merchant_order_no |
string |
✅ |
商户订单号,最长 64 位,回调原样返回 |
amount |
int |
✅ |
支付金额,单位:分,最小值 1 |
notify_url |
string |
✅ |
商户方回调地址,支付成功后通知此地址 |
body |
string |
❌ |
商品描述 JSON,可为空 |
extra |
string |
❌ |
自定义透传参数,回调原样返回 |
channel_code |
string |
❌ |
支付渠道编码 在线渠道编码中查询 |
note |
string |
❌ |
订单备注,最长 250 字符,默认 "在线支付" |
client_ip |
string |
❌ |
客户端 IP(可选) |
timestamp |
int |
✅ |
Unix 时间戳 |
nonce |
string |
✅ |
随机字符串 |
sign |
string |
✅ |
HMAC-SHA256 签名 |
请求示例
POST /api/cashier/qrcode-pay HTTP/1.1
Content-Type: application/json
{
"app_id": "pk_live_51H8xK2eZvKYlo2C9xK2eZvKYlo2C",
"merchant_order_no": "ORD20260315001",
"amount": 1000,
"notify_url": "https://your-domain.com/pay/callback",
"body": "{\"goods\":\"VIP月卡\",\"sku\":\"vip_monthly\"}",
"extra": "{\"user_id\":12345,\"channel\":\"ios\"}",
"note": "VIP月卡充值",
"timestamp": 1710489862,
"nonce": "a1b2c3d4e5f6",
"sign": "e5b7a3f1c9d2e8b4a7f6..."
}
成功响应
{
"code": 200,
"message": "下单成功",
"data": {
"order_no": "QR20260315120000ABCD1234",
"merchant_order_no": "ORD20260315001",
"amount": 1000,
"extra": "{\"user_id\":12345,\"channel\":\"ios\"}",
"qrCodeUrl": "https://qr.example.com/xxxx.png",
"sourceQrCodeUrl": "alipays://platformapi/startapp?...",
"expire_minutes": 30
}
}
响应字段说明
| 字段 |
类型 |
说明 |
order_no |
string |
平台订单号 |
merchant_order_no |
string |
商户订单号(原样返回) |
amount |
int |
金额(分) |
extra |
string |
透传参数(原样返回) |
qrCodeUrl |
string |
二维码图片 URL,前端直接 <img> 展示 |
sourceQrCodeUrl |
string |
支付宝跳转直链(H5 场景可直跳唤起) |
expire_minutes |
int |
二维码过期时间(分钟) |
💡 集成建议
PC 场景 — 将 qrCodeUrl 渲染为图片让用户扫码
H5 场景 — 可直接 < script > location.href = sourceQrCodeUrl; < / script > 唤起支付宝
错误响应示例
// 参数缺失 (422)
{"code":422,"message":"The merchant_order_no field is required.","data":{...}}
// 业务错误 (400)
{"code":400,"message":"金额必须大于 0","data":null}
// 签名错误 (401)
{"code":401,"message":"签名验证失败","data":null}
// 请求过频 (429)
{"code":429,"message":"请求过于频繁,请3秒后重试","data":null}
工商E支付
生成支付链接,用户跳转至工商E支付收银台完成付款。支持 工商E支付 渠道。
请求参数
| 参数 |
类型 |
必填 |
说明 |
app_id |
string |
✅ |
商户应用 ID |
merchant_order_no |
string |
✅ |
商户订单号,最长 64 位,回调原样返回 |
amount |
int |
✅ |
支付金额,单位:分,最小值 1 |
notify_url |
string |
❌ |
支付结果回调地址(留空使用商户默认回调地址) |
extra |
string |
❌ |
自定义透传参数,回调原样返回 |
note |
string |
❌ |
订单备注,最长 250 字符,默认 "工商E支付" |
client_ip |
string |
❌ |
客户端 IP(可选) |
channel_code |
string |
❌ |
子渠道编码:alipay(支付宝,默认)或 wechat(微信) |
timestamp |
int |
✅ |
Unix 时间戳 |
nonce |
string |
✅ |
随机字符串 |
sign |
string |
✅ |
HMAC-SHA256 签名 |
请求示例
POST /api/cashier/icbc-epay HTTP/1.1
Content-Type: application/json
{
"app_id": "pk_live_51H8xK2eZvKYlo2C9xK2eZvKYlo2C",
"merchant_order_no": "ORD20260719001",
"amount": 10000,
"notify_url": "https://your-domain.com/pay/callback",
"extra": "{\"user_id\":12345}",
"note": "工商E支付测试",
"timestamp": 1710489862,
"nonce": "a1b2c3d4e5f6",
"sign": "e5b7a3f1c9d2e8b4a7f6..."
}
成功响应
{
"code": 200,
"message": "ok",
"data": {
"order_no": "ICBC20260719120000ABCD1234",
"merchant_order_no": "ORD20260719001",
"amount": 100.00,
"zfb_pay_url": "alipays://platformapi/startapp?...",
"expire_minutes": 30
}
}
响应字段说明
| 字段 |
类型 |
说明 |
order_no |
string |
平台订单号 |
merchant_order_no |
string |
商户订单号(原样返回) |
amount |
float |
金额(元) |
zfb_pay_url |
string |
实际支付唤起链接(如 alipays://platformapi/startapp?...),。H5 场景可直接跳转唤起支付 |
expire_minutes |
int |
支付链接过期时间(分钟),默认 30 |
💡 集成建议
下单成功后,将 pay_url 返回给前端,引导用户跳转至工商E支付收银台完成付款。
支付结果通过 异步回调 notify_url 通知商户。
错误响应示例
// 参数缺失 (422)
{"code":422,"message":"商户订单号不能为空","data":null}
// 业务错误 (400)
{"code":400,"message":"金额必须大于 0","data":null}
// 签名错误 (401)
{"code":401,"message":"签名验证失败","data":null}
// 请求过频 (429)
{"code":429,"message":"请求过于频繁,请3秒后重试","data":null}
工行二维码
生成二维码内容与收银台链接,用户扫码完成付款。对接上游 工行二维码(二维码生成V4) 通道,支持 支付宝 / 微信 扫码。
请求参数
| 参数 |
类型 |
必填 |
说明 |
app_id |
string |
✅ |
商户应用 ID |
merchant_order_no |
string |
✅ |
商户订单号,最长 64 位,回调原样返回 |
amount |
int |
✅ |
支付金额,单位:分,对应 1 ~ 10000 元 |
notify_url |
string |
❌ |
支付结果回调地址(留空使用商户默认回调地址) |
extra |
string |
❌ |
自定义透传参数,回调原样返回 |
note |
string |
❌ |
订单备注,最长 250 字符,默认 "工行二维码" |
client_ip |
string |
❌ |
客户端 IP(可选) |
channel_code |
string |
❌ |
子渠道编码:alipay(支付宝,默认)或 wechat(微信) |
timestamp |
int |
✅ |
Unix 时间戳 |
nonce |
string |
✅ |
随机字符串 |
sign |
string |
✅ |
HMAC-SHA256 签名 |
请求示例
POST /api/cashier/icbc-qrcode HTTP/1.1
Content-Type: application/json
{
"app_id": "pk_live_51H8xK2eZvKYlo2C9xK2eZvKYlo2C",
"merchant_order_no": "ORD20261001001",
"amount": 10000,
"notify_url": "https://your-domain.com/pay/callback",
"extra": "{\"user_id\":12345}",
"note": "工行二维码测试",
"channel_code": "alipay",
"timestamp": 1759287600,
"nonce": "a1b2c3d4e5f6",
"sign": "e5b7a3f1c9d2e8b4a7f61d0c93ae45b8c2f7d1a0e6b39c84f5a2d7e1b0c93a46"
}
成功响应
{
"code": 200,
"message": "ok",
"data": {
"order_no": "ICBCQR20261001120000ABCD1234",
"merchant_order_no": "ORD20261001001",
"amount": 100.00,
"qr_code": "https://qr.alipay.com/bax0xxxxxxxxxxxxxxxx",
"pay_url": "https://api.jiahemeili.cn/api/cashier?orderId=ICBCQR20261001120000ABCD1234",
"expire_minutes": 30
}
}
响应字段说明
| 字段 |
类型 |
说明 |
order_no |
string |
平台订单号(前缀 ICBCQR) |
merchant_order_no |
string |
商户订单号(原样返回) |
amount |
float |
订单金额(元) |
qr_code |
string |
二维码内容字符串,需由前端二维码库渲染成图片(如 qrcodejs) |
pay_url |
string |
上游收银台链接,H5 场景可直接跳转唤起支付 |
expire_minutes |
int |
二维码 / 收银台链接过期时间(分钟),默认 30 |
💡 集成建议
下单成功后,将 qr_code 用二维码库渲染成二维码供用户扫码,或将 pay_url 引导用户跳转至收银台完成付款。
金额限额 1 ~ 10000 元(即 amount 取值 100 ~ 1000000 分),超出将返回业务错误。
支付结果通过 异步回调 notify_url 通知商户,详见「异步回调 (Webhook)」章节。
错误响应示例
// 参数缺失 (422)
{"code":422,"message":"商户订单号不能为空","data":null}
// 业务错误 (400)
{"code":400,"message":"金额必须大于 0","data":null}
{"code":400,"message":"下单失败:金额最低 1 元","data":null}
{"code":400,"message":"下单失败:金额最高 10000 元","data":null}
// 签名错误 (401)
{"code":401,"message":"签名验证失败","data":null}
咕友支付(guyouPay)
生成支付宝支付链接,用户扫码或跳转完成付款。对接上游 咕友支付 聚合通道。
请求参数
| 参数 |
类型 |
必填 |
说明 |
app_id |
string |
✅ |
商户应用 ID |
merchant_order_no |
string |
✅ |
商户订单号,最长 64 位,回调原样返回 |
amount |
int |
✅ |
支付金额,单位:分,最小值 100(即 1 元) |
channel_code |
string |
❌ |
子渠道编码:alipay_h5(支付宝h5,默认)/ alipay_h5 / alipay_js |
notify_url |
string |
❌ |
支付结果回调地址(留空使用商户默认回调地址) |
return_url |
string |
✅ |
支付成功同步返回地址(H5 场景) |
note |
string |
❌ |
订单备注,默认 "咕友支付" |
body |
string |
❌ |
商品描述信息 |
client_ip |
string |
❌ |
客户端 IP(可选) |
expired_time |
int |
❌ |
订单失效时间,单位秒,默认 7200(2 小时) |
extra |
string |
❌ |
自定义透传参数,回调原样返回 |
channel_extra |
string |
❌ |
通道特殊参数(JSON 字符串) |
timestamp |
int |
✅ |
Unix 时间戳 |
nonce |
string |
✅ |
随机字符串 |
sign |
string |
✅ |
HMAC-SHA256 签名 |
请求示例
POST /api/cashier/guyou-pay HTTP/1.1
Content-Type: application/json
{
"app_id": "pk_live_51H8xK2eZvKYlo2C9xK2eZvKYlo2C",
"merchant_order_no": "ORD20260719002",
"amount": 100,
"channel_code": "alipay",
"notify_url": "https://your-domain.com/pay/callback",
"note": "咕友支付测试",
"timestamp": 1710489862,
"nonce": "a1b2c3d4e5f6",
"sign": "e5b7a3f1c9d2e8b4a7f6..."
}
成功响应
{
"code": 200,
"message": "ok",
"data": {
"order_no": "GY20260719120000ABCD1234",
"merchant_order_no": "ORD20260719002",
"amount": 1.00,
"pay_url": "https://openapi.alipay.com/gateway.do?...",
"pay_order_id": "20240419101811459000123355",
"expire_minutes": 120
}
}
响应字段说明
| 字段 |
类型 |
说明 |
order_no |
string |
平台订单号 |
merchant_order_no |
string |
商户订单号(原样返回) |
amount |
float |
金额(元) |
pay_url |
string |
支付链接(支付宝收银台,可转二维码或直接跳转) |
pay_order_id |
string |
上游平台流水号 |
expire_minutes |
int |
支付链接过期时间(分钟) |
💡 集成建议
下单成功后,将 pay_url 返回给前端:PC 场景可生成二维码供用户扫码,H5 场景可直接跳转唤起支付宝。
支付结果通过 异步回调 notify_url 通知商户。
错误响应示例
// 参数缺失 (422)
{"code":422,"message":"商户订单号不能为空","data":null}
// 业务错误 (400)
{"code":400,"message":"金额最低 1 元(100 分)","data":null}
// 签名错误 (401)
{"code":401,"message":"签名验证失败","data":null}
微信 JSAPI 支付
用于微信公众号内唤起微信支付。采用两阶段下单模式:
- 商户调用接口获取
pay_url
- 将
pay_url 引导用户在微信浏览器中打开,系统自动完成 OAuth 授权并唤起微信支付
商户无需自行集成微信 OAuth 和 WeixinJSBridge,开箱即用。
需使用 HMAC-SHA256 签名认证。
请求参数
| 参数 |
类型 |
必填 |
说明 |
app_id |
string |
✅ |
商户应用 ID(公共参数,参与签名) |
merchant_order_no |
string |
✅ |
商户订单号,最长 64 位,回调原样返回 |
amount |
int |
✅ |
支付金额,单位:分,最小值 1 |
subOpenId |
string |
❌ |
微信用户 OpenID。可选:不传则由系统在用户打开 pay_url 时自动通过微信 OAuth 获取 |
appId |
string |
✅ |
微信公众号或小程序 AppID |
notify_url |
string |
❌ |
支付结果回调地址(留空使用系统默认回调) |
isMiniPg |
string |
❌ |
"1" 微信小程序、"2" 微信公众号(默认 "2") |
timeOutExpress |
string |
❌ |
订单超时时间(分钟),默认 30 |
note |
string |
❌ |
订单备注(最长 125 字符) |
timestamp |
int |
✅ |
Unix 秒级时间戳(公共参数,参与签名) |
nonce |
string |
✅ |
随机字符串 ≤32 位(公共参数,参与签名) |
client_ip |
string |
❌ |
客户端 IP(可选) |
sign |
string |
✅ |
HMAC-SHA256 签名(公共参数) |
💡 注意公共参数
app_id、timestamp、nonce、sign 与其他业务参数一起参与签名,所有参数平级放在 JSON
body 中。
请求示例
POST /api/cashier/wechat-js-pay HTTP/1.1
Content-Type: application/json
{
"app_id": "pk_live_51H8xK2eZvKYlo2C9xK2eZvKYlo2C",
"merchant_order_no": "ORD20260315002",
"amount": 100,
"appId": "wx1234567890abcdef",
"isMiniPg": "2",
"note": "测试商品",
"timestamp": 1710489862,
"nonce": "a1b2c3d4e5f6",
"sign": "e5b7a3f1c9d2e8b4a7f6..."
}
成功响应
{
"code": 200,
"message": "下单成功",
"data": {
"order_no": "WX20260315120000ABCD5678",
"merchant_order_no": "ORD20260315002",
"amount": 1.00,
"pay_url": "https://payment.xmlhy.cn/wechat-pay-invoke.html?token=d3f8a1b2c3d4e5f6...",
"pay_token": "d3f8a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
}
}
响应字段说明
| 字段 |
类型 |
说明 |
order_no |
string |
平台订单号 |
merchant_order_no |
string |
商户订单号(原样返回) |
amount |
float |
金额(元) |
pay_url |
string |
支付跳转链接 — 引导用户在微信浏览器中打开此 URL,系统自动完成 OAuth 授权并唤起微信支付 |
pay_token |
string |
支付凭证 token,有效期 10 分钟。可用于查询订单或自定义支付页面 |
🔗 推荐接入方式(零前端代码)
商户服务端下单后,只需将返回的
pay_url 引导用户在微信中打开即可。
常用做法:
- 公众号菜单/模板消息:配置
pay_url 作为跳转链接
- 微信内 H5 页面:
window.location.href = pay_url
- 服务端 302 重定向:商户接收下单请求后直接返回
302 Location: pay_url
系统自动处理微信 OAuth 静默授权(snsapi_base)、获取用户 OpenID、调用银盛网关、唤起微信支付收银台。
支付结果通过 异步回调 notify_url 通知商户。
pay_url 支付页面流程
- 用户在微信中打开 pay_url
- 系统检测无 OpenID → 自动跳转微信 OAuth 静默授权
- 微信回调返回 code → 后端换取 openid
- 用 token + openid 调银盛网关正式下单
- 自动调用 WeixinJSBridge 唤起微信支付收银台
前端唤起支付(如自行集成 JSAPI)
// 微信公众号 H5 — 使用 WeixinJSBridge
function onBridgeReady() {
WeixinJSBridge.invoke(
'getBrandWCPayRequest',
response.data.jsapi_pay_info,
function(res) {
if (res.err_msg === "get_brand_wcpay_request:ok") {
// 支付成功
}
}
);
}
if (typeof WeixinJSBridge === 'undefined') {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else {
onBridgeReady();
}
// 微信小程序
wx.requestPayment({
timeStamp: response.data.jsapi_pay_info.timeStamp,
nonceStr: response.data.jsapi_pay_info.nonceStr,
package: response.data.jsapi_pay_info.package,
signType: response.data.jsapi_pay_info.signType,
paySign: response.data.jsapi_pay_info.paySign,
success(res) { console.log('支付成功', res); },
fail(res) { console.log('支付失败', res); }
});
💡 如果使用 pay_url 接入则无需自行编写上述唤起代码,系统已自动处理。仅当商户需要完全自建支付页面时才需自行集成 JSAPI。
错误响应示例
// 参数缺失 (422)
{"code":422,"message":"商户订单号不能为空","data":null}
// 订单已存在 (400)
{"code":400,"message":"商户订单号已存在,请勿重复提交","data":null}
// 签名错误 (401)
{"code":401,"message":"签名验证失败","data":null}
// 支付链接过期(pay_url 超时未使用)
{"code":400,"message":"支付链接已过期,请重新生成","data":null}
亿速易汇支付(yisuyihui)
生成亿速易汇上游签名支付链接,用户使用浏览器打开后完成付款。对接上游 亿速易汇聚合支付。
⚠ 重要:必须由浏览器打开支付链接
上游明确要求:下单接口不能由服务端发起 HTTP 请求,必须把拼接好参数的链接交给客户端浏览器打开,
上游系统才会生成订单。本平台已完成签名,并将链接放在 pay_url 中返回,前端直接跳转即可。
请求参数
| 参数 | 类型 | 必填 | 说明 |
app_id | string | ✅ | 商户应用 ID |
merchant_order_no | string | ✅ | 商户订单号,最长 64 位,回调原样返回 |
amount | int | ✅ | 支付金额,单位:分,最小值 100(即 1 元) |
notify_url | string | ❌ | 商户支付结果回调地址;留空使用商户中心配置的回调地址 |
return_url | string | ❌ | 仅本地记录;该上游通道未提供同步返回参数,请以异步回调为准 |
note | string | ❌ | 订单标题,默认“亿速易汇支付”,超过 20 个字符自动截断 |
body | string | ❌ | 商品描述信息,仅本地记录 |
client_ip | string | ❌ | 客户端 IP;留空自动取当前请求 IP。上游要求必填 |
pay_way | string | ❌ | 支付方式:alipay / wechat;留空使用 alipay |
extra | string | ❌ | 自定义透传参数,平台回调时原样返回 |
timestamp | int | ✅ | Unix 时间戳(±5 分钟) |
nonce | string | ✅ | 随机字符串 |
sign | string | ✅ | 平台商户 HMAC-SHA256 签名 |
请求示例
POST /api/cashier/yisuyihui-pay HTTP/1.1
Content-Type: application/json
{
"app_id": "app_xxx",
"merchant_order_no": "ORD20261019001",
"amount": 100,
"notify_url": "https://your-domain.com/pay/callback",
"note": "亿速易汇测试",
"client_ip": "1.2.3.4",
"pay_way": "alipay",
"timestamp": 1760000000,
"nonce": "a1b2c3d4e5f6",
"sign": "e5b7a3f1c9d2e8b4a7f6..."
}
成功响应
{
"code": 200,
"message": "ok",
"data": {
"order_no": "YSYH20261019120000ABCD1234",
"merchant_order_no": "ORD20261019001",
"amount": 1.00,
"pay_url": "https://pay.blmpay.cn/api/rpay/pay?appKey=...&sign=...",
"pay_order_id": "YC261019120000ABCDEF",
"channel_order_no": "YC261019120000ABCDEF",
"expire_minutes": 30
}
}
响应字段说明
| 字段 | 类型 | 说明 |
order_no | string | 平台订单号(YSYH 开头) |
merchant_order_no | string | 商户订单号(原样返回) |
amount | float | 金额(元) |
pay_url | string | 亿速易汇签名支付链接,必须由浏览器打开 |
pay_order_id | string | 上游商户订单号(YC 开头,20 位) |
channel_order_no | string | 同 pay_order_id,用于查询和排查 |
expire_minutes | int | 订单有效期(分钟) |
💡 集成建议
下单成功后,将 pay_url 直接赋给 window.location.href 或生成可点击链接。
PC 场景也可把该 URL 生成二维码供用户扫码。支付结果通过异步回调通知商户。
订单查询
查询本地订单并同步上游支付状态,需使用与下单相同的商户签名。
| 参数 | 类型 | 必填 | 说明 |
app_id | string | ✅ | 商户应用 ID |
order_no | string | ❌ | 平台订单号,与商户订单号二选一 |
merchant_order_no | string | ❌ | 商户订单号,与平台订单号二选一 |
timestamp | int | ✅ | Unix 时间戳 |
nonce | string | ✅ | 随机字符串 |
sign | string | ✅ | 平台商户 HMAC-SHA256 签名 |
{
"code": 200,
"message": "ok",
"data": {
"order_no": "YSYH20261019120000ABCD1234",
"merchant_order_no": "ORD20261019001",
"channel_order_no": "YC261019120000ABCDEF",
"amount": "1.00",
"actual_amount": "1.00",
"fee": "0.01",
"status": 1,
"status_text": "支付成功",
"pay_time": "2026-10-19 12:05:00",
"upstream_status": "2",
"upstream_status_text": "支付成功",
"upstream": { "mchOrderNo": "YC261019120000ABCDEF", "payAmount": 100, "payState": "2" }
}
}
上游支付状态
| payState | 含义 |
0 | 订单生成 |
1 | 支付中 |
2 | 支付成功(平台入账) |
3 | 支付失败 |
4 | 已撤销 |
5 | 已退款 |
7 | 取消支付 |
异步回调
上游通过 POST-FORM 回调本平台 /api/cashier/notify/yisuyihui-pay,平台验签并核对金额后入账,
再通过统一的商户 Webhook 通知商户 notify_url。平台处理完成返回纯文本 SUCCESS。
错误响应示例
// 参数缺失 (400)
{"code":400,"message":"下单失败:商户订单号不能为空","data":null}
// 金额错误 (400)
{"code":400,"message":"下单失败:金额最低 1 元(100 分)","data":null}
// 签名错误 (401)
{"code":401,"message":"签名验证失败","data":null}
订单查询
查询指定订单的当前状态。通过 order_no 或 merchant_order_no 均可查询,二选一。
请求参数
| 参数 |
类型 |
必填 |
说明 |
app_id |
string |
✅ |
商户应用 ID |
order_no |
string |
🔶 |
平台订单号(与 merchant_order_no 二选一) |
merchant_order_no |
string |
🔶 |
商户订单号(与 order_no 二选一) |
timestamp |
int |
✅ |
Unix 时间戳 |
nonce |
string |
✅ |
随机字符串 |
sign |
string |
✅ |
HMAC-SHA256 签名 |
请求示例
POST /api/cashier/order-query HTTP/1.1
Content-Type: application/json
{
"app_id": "pk_live_51H8xK2eZvKYlo2C9xK2eZvKYlo2C",
"order_no": "QR20260315120000ABCD1234",
"timestamp": 1710489862,
"nonce": "a1b2c3d4e5f6",
"sign": "e5b7a3f1c9d2e8b4a7f6..."
}
💡 提示可以只传 order_no(平台单号)或
merchant_order_no(商户单号),至少传一个。两个都传时优先以 order_no 查询。
成功响应
{
"code": 200,
"message": "ok",
"data": {
"order_no": "QR20260315120000ABCD1234",
"merchant_order_no": "ORD20260315001",
"amount": "10.00",
"actual_amount": "9.94",
"fee": "0.06",
"status": 1,
"status_text": "支付成功",
"channel_code": "YINSHENG_QRCODE",
"pay_time": "2026-03-15 12:30:00",
"created_at": "2026-03-15 12:00:00"
}
}
响应字段说明
| 字段 |
类型 |
说明 |
order_no |
string |
平台订单号 |
merchant_order_no |
string |
商户订单号(下单时传入) |
amount |
string |
订单金额(元) |
actual_amount |
string |
实际支付金额(元) |
fee |
string |
手续费(元) |
status |
int |
订单状态码,见下方状态表 |
status_text |
string |
订单状态中文描述 |
channel_code |
string |
支付渠道编码 |
pay_time |
string/null |
支付时间(YYYY-MM-DD HH:mm:ss),未支付为 null |
created_at |
string |
订单创建时间 |
订单状态
| status |
含义 |
说明 |
| 0 |
待支付 |
订单已创建,等待用户扫码支付 |
| 1 |
支付成功 |
已完成支付,相应金额已入账 |
| 2 |
支付失败 |
支付过程出现错误 |
| 4 |
已关闭/已过期 |
订单已超时或手动关闭 |
错误响应示例
// 订单不存在 (404)
{"code":404,"message":"订单不存在","data":null}
// 两个单号都没传 (400)
{"code":400,"message":"order_no 和 merchant_order_no 至少传一个","data":null}
// 签名错误 (401)
{"code":401,"message":"签名验证失败","data":null}
// 请求过频 (429)
{"code":429,"message":"请求过于频繁,请3秒后重试","data":null}
异步回调 (Webhook)
支付成功后,平台向商户的 notify_url 发送 POST 请求通知支付结果。
回调机制
| 项目 |
说明 |
| 触发条件 |
渠道确认支付成功 |
| 请求方式 |
POST + application/json |
| 重试策略 |
商户未返回 "success" 时,1 分钟后重试,最多 3 次 |
| 重试间隔 |
1min → 1min → 1min(共 3 次推送) |
| 实现方式 |
延迟派发 |
| 幂等要求 |
商户须保证同一订单多次通知的幂等处理 |
回调参数
| 字段 |
类型 |
说明 |
order_no |
string |
平台订单号 |
merchant_order_no |
string |
商户订单号(原样返回) |
amount |
string |
订单金额(元) |
actual_amount |
string |
实际支付金额(元) |
status |
int |
1 = 已支付 |
channel_code |
string |
支付渠道编码 |
pay_time |
string |
支付时间 YYYY-MM-DD HH:mm:ss |
extra |
string |
透传参数(原样返回) |
sign |
string |
回调签名(HMAC-SHA256) |
回调示例
POST https://your-domain.com/pay/callback HTTP/1.1
Content-Type: application/json
{
"order_no": "QR20260315120000ABCD1234",
"merchant_order_no": "ORD20260315001",
"amount": "10.00",
"actual_amount": "10.00",
"status": 1,
"channel_code": "YINSHENG_QRCODE",
"pay_time": "2026-03-15 12:30:00",
"extra": "{\"user_id\":12345,\"channel\":\"ios\"}",
"sign": "a1b2c3d4e5f6..."
}
商户验签 & 响应
收到回调后,用与请求签名相同的算法验证 sign,处理业务后返回:
HTTP 200
Content-Type: text/plain
success
⚠ 注意
必须返回纯文本 success(不是 JSON),否则平台将触发重试。
订单状态枚举
| status |
含义 |
标识 |
| 0 |
待支付 |
PENDING |
| 1 |
已支付 |
PAID |
| 2 |
已关闭(超时) |
CLOSED |
| 3 |
已退款 |
REFUNDED |
| 4 |
支付失败 |
FAILED |
支付渠道编码
| channel_code |
名称 |
类型 |
alipay_h5 |
支付宝 H5 |
alipay |
alipay_pc |
支付宝 PC |
alipay |
alipay_app |
支付宝 APP |
alipay |
wxpay_h5 |
微信 H5 |
wxpay |
wxpay_native |
微信 Native |
wxpay |
wxpay_js |
微信 JSAPI |
wxpay |
wxpay_app |
微信 APP |
wxpay |
unionpay_h5 |
银联 H5 |
unionpay |
icbc_epay |
工商E支付 |
icbc |
ICBC_QRCODE |
工行二维码 |
icbc |
guyou_pay |
咕友支付 |
guyou |
yisuyihui_pay |
亿速易汇支付 |
yisuyihui |
错误码
| HTTP Status |
code |
message 示例 |
处理建议 |
| 200 |
200 |
下单成功 |
— |
| 400 |
400 |
商户订单号已存在,请勿重复提交 |
更换 merchant_order_no 后重试 |
| 400 |
400 |
金额必须大于 0 |
检查 amount 参数 |
| 401 |
401 |
缺少 app_id 参数 |
请求中加入 app_id |
| 401 |
401 |
商户不存在 / 商户已停用 |
确认 app_id 是否正确 |
| 401 |
401 |
请求已过期,请检查 timestamp |
timestamp 与服务器时间差 ±5 分钟内 |
| 401 |
401 |
IP 未授权 |
检查 IP 白名单配置 |
| 401 |
401 |
签名验证失败 |
检查签名算法与 app_secret |
| 422 |
422 |
商户订单号不能为空 |
补充必填参数 |
| 429 |
429 |
请求过于频繁,请3秒后重试 |
同一 app_id + IP 3 秒内限 1 次 |
| 500 |
500 |
服务异常 |
联系平台技术支持 |
接入流程
聚合扫码支付时序图
sequenceDiagram
participant M as 商户服务器
participant P as PayHub 平台
participant C as 支付渠道(银盛)
participant U as 用户
M->>P: 1. POST /api/cashier/qrcode-pay (带签名)
P->>P: 2. 验签 + IP白名单 + 订单防重 + 限频
P->>C: 3. 调用渠道下单
C-->>P: 4. 返回二维码URL
P-->>M: 5. 返回 qrCodeUrl + order_no
M->>U: 6. 展示二维码
U->>C: 7. 扫码支付
C->>P: 8. 渠道回调通知
P->>P: 9. 更新订单状态
P->>M: 10. Webhook (merchant_order_no + extra)
M-->>P: 11. 返回 "success"
步骤说明
- 商户服务器 → PayHub:POST /api/cashier/qrcode-pay(携带签名)
- PayHub:验签 → 商户状态 → 时间戳 → IP 白名单 → 订单防重 → 限频检查
- PayHub → 支付渠道:调用渠道下单接口
- 支付渠道 → PayHub:返回二维码 URL
- PayHub → 商户服务器:返回 qrCodeUrl + order_no
- 商户前端 → 用户:展示二维码
- 用户:扫码完成支付
- 支付渠道 → PayHub:异步回调通知
- PayHub:更新订单状态
- PayHub → 商户 notify_url:Webhook 推送(含 merchant_order_no + extra)
- 商户服务器 → PayHub:返回 "success"
SDK & 示例代码
以下为各语言的核心调用代码,包含签名计算与下单请求。
// PHP — 签名 + 下单
$params = [
'app_id' => $appId,
'merchant_order_no' => 'ORD' . date('YmdHis') . mt_rand(1000, 9999),
'amount' => 1000,
'notify_url' => 'https://your-domain.com/pay/callback',
'extra' => json_encode(['user_id' => 12345]),
'timestamp' => time(),
'nonce' => bin2hex(random_bytes(16)),
];
// 签名
$filtered = array_filter($params, fn($v) => $v !== '' && $v !== null);
ksort($filtered);
$signStr = urldecode(http_build_query($filtered));
$params['sign'] = hash_hmac('sha256', $signStr, $appSecret);
// 下单
$ch = curl_init($baseUrl . '/api/cashier/qrcode-pay');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_RETURNTRANSFER => true,
]);
$result = json_decode(curl_exec($ch), true);
// $result['data']['qrCodeUrl'] → 二维码图片
// Java — 签名 + 下单 (需引入 okhttp、commons-codec)
TreeMap<String, String> params = new TreeMap<>();
params.put("app_id", appId);
params.put("merchant_order_no", "ORD" + System.currentTimeMillis());
params.put("amount", "1000");
params.put("notify_url", "https://your-domain.com/pay/callback");
params.put("extra", "{\"user_id\":12345}");
params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
params.put("nonce", UUID.randomUUID().toString().replace("-", ""));
// 签名
StringBuilder sb = new StringBuilder();
params.forEach((k, v) -> {
if (v != null && !v.isEmpty()) {
if (sb.length() > 0) sb.append("&");
sb.append(k).append("=").append(v);
}
});
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(appSecret.getBytes(), "HmacSHA256"));
String sign = Hex.encodeHexString(mac.doFinal(sb.toString().getBytes()));
params.put("sign", sign);
// 下单
RequestBody body = RequestBody.create(
new Gson().toJson(params), MediaType.parse("application/json"));
Request request = new Request.Builder()
.url(baseUrl + "/api/cashier/qrcode-pay")
.post(body).build();
Response response = new OkHttpClient().newCall(request).execute();
// 解析 response.body().string()
# Python — 签名 + 下单
import hmac, hashlib, time, secrets, requests, json
params = {
'app_id': app_id,
'merchant_order_no': f'ORD{int(time.time())}',
'amount': 1000,
'notify_url': 'https://your-domain.com/pay/callback',
'extra': json.dumps({'user_id': 12345}),
'timestamp': int(time.time()),
'nonce': secrets.token_hex(16),
}
# 签名
filtered = {k: v for k, v in params.items() if v not in ('', None)}
sign_str = '&'.join(f'{k}={v}' for k, v in sorted(filtered.items()))
params['sign'] = hmac.new(
app_secret.encode(), sign_str.encode(), hashlib.sha256
).hexdigest()
# 下单
resp = requests.post(
f'{base_url}/api/cashier/qrcode-pay', json=params
).json()
# resp['data']['qrCodeUrl'] → 二维码图片
// Node.js — 签名 + 下单
const crypto = require('crypto');
const axios = require('axios');
const params = {
app_id: appId,
merchant_order_no: `ORD${Date.now()}`,
amount: 1000,
notify_url: 'https://your-domain.com/pay/callback',
extra: JSON.stringify({user_id: 12345 }),
timestamp: Math.floor(Date.now() / 1000),
nonce: crypto.randomBytes(16).toString('hex'),
};
// 签名
const signStr = Object.keys(params)
.filter(k => params[k] !== '' && params[k] != null)
.sort()
.map(k => `${k}=${params[k]}`)
.join('&');
params.sign = crypto.createHmac('sha256', appSecret)
.update(signStr).digest('hex');
// 下单
const {data} = await axios.post(
`${baseUrl}/api/cashier/qrcode-pay`, params
);
// data.data.qrCodeUrl → 二维码图片
// Go — 签名 + 下单
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"sort"
"strings"
"time"
)
params := map[string]string{
"app_id": appId,
"merchant_order_no": fmt.Sprintf("ORD%d", time.Now().Unix()),
"amount": "1000",
"notify_url": "https://your-domain.com/pay/callback",
"extra": `{"user_id":12345}`,
"timestamp": fmt.Sprintf("%d", time.Now().Unix()),
"nonce": hex.EncodeToString(randomBytes(16)),
}
// 签名
keys := make([]string, 0)
for k, v := range params {
if v != "" {keys = append(keys, k)}
}
sort.Strings(keys)
pairs := make([]string, len(keys))
for i, k := range keys {pairs[i] = k + "=" + params[k]}
signStr := strings.Join(pairs, "&")
h := hmac.New(sha256.New, []byte(appSecret))
h.Write([]byte(signStr))
params["sign"] = hex.EncodeToString(h.Sum(nil))
// 下单
jsonData, _ := json.Marshal(params)
resp, _ := http.Post(
baseUrl+"/api/cashier/qrcode-pay",
"application/json",
bytes.NewBuffer(jsonData),
)
// 解析 resp.Body → data.qrCodeUrl
<?php
/**
* 聚合扫码支付 API 完整调用示例
*
* 包含签名计算、下单请求、响应处理的完整流程
*/
$appId = 'your_app_id'; // 公钥
$appSecret = 'your_app_secret'; // 秘钥
$baseUrl = 'https://pay.your-domain.com/api/cashier'; // 生产环境地址
// ─── 正确签名下单 ───
$orderNo = 'ORD_' . date('YmdHis') . '_' . mt_rand(1000, 9999);
$params = buildParams($appId, 1000, $orderNo); // 1000分 = 10元
$params['sign'] = makeSign($params, $appSecret);
echo "商户订单号: {$orderNo}\n";
$res = httpPost($baseUrl . '/qrcode-pay', $params);
echo "响应: " . json_encode($res['json'], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
if ($res['json']['code'] === 200) {
$qrCodeUrl = $res['json']['data']['qrCodeUrl'];
echo "二维码: {$qrCodeUrl}\n";
}
// ────── 工具函数 ──────
function buildParams(string $appId, int $amount, string $merchantOrderNo): array
{
return [
'app_id' => $appId,
'merchant_order_no' => $merchantOrderNo,
'amount' => $amount,
'notify_url' => 'https://your-domain.com/pay/callback',
'extra' => json_encode(['user_id' => 12345]),
'note' => '在线支付',
'timestamp' => time(),
'nonce' => bin2hex(random_bytes(16)),
];
}
function makeSign(array $params, string $secret): string
{
unset($params['sign']);
$params = array_filter($params, fn($v) => $v !== '' && $v !== null);
ksort($params);
$pairs = [];
foreach ($params as $k => $v) {
$pairs[] = "{$k}={$v}";
}
$signStr = implode('&', $pairs);
return hash_hmac('sha256', $signStr, $secret);
}
function httpPost(string $url, array $data): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'],
CURLOPT_POSTFIELDS => json_encode($data),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
]);
$body = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
return [
'http_code' => $httpCode,
'body' => $body,
'error' => $error,
'json' => json_decode($body, true),
];
}