概述

PayHub 支付平台为第三方商户提供统一收单 API。商户通过简单的 HTTP 接口即可快速接入多渠道支付能力。

支付方式 接口 适用场景
聚合扫码支付 POST /api/cashier/qrcode-pay PC / H5 展示二维码,用户扫码
微信 JSAPI POST /api/cashier/wechat-js-pay 微信公众号 / 小程序内唤起支付
订单查询 POST /api/cashier/order-query 按平台单号或商户单号查询订单状态

接入准备

获取凭证

在 PayHub 管理后台完成商户入驻后,获取以下信息:

凭证 说明
app_id 商户应用标识(唯一)
app_secret 应用密钥,用于 HMAC-SHA256 签名
callback_url 支付结果回调地址
⚠ 重要app_secret 仅创建时展示一次,请妥善保管。遗失可在管理后台重置。

环境地址

环境 Base URL
生产 https://payment1.qiquan18.cn
测试 https://payment1.qiquan18.cn

安全机制

机制 说明
HMAC-SHA256 签名 每个请求必须携带签名,防篡改
IP 白名单 商户可配置允许请求的服务器 IP 列表,非白名单 IP 直接返回 401
提单限频 同一 IP 3 秒内仅允许提交 1 次订单(基于 Redis),防止恶意刷单
时间戳校验 timestamp 与服务器时间偏差超过 ±5 分钟则拒绝请求
随机串防重放 nonce 随机字符串,防止请求被截获后重放

签名算法(HMAC-SHA256)

签名步骤

  1. 取所有请求参数(不含 sign),去掉值为空的参数
  2. 按 key 的 ASCII 升序排列
  3. 拼接成 key1=value1&key2=value2&... 格式
  4. 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&notify_url=https://example.com/notify&timestamp=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 场景可直跳唤起)完成支付。

POST /api/cashier/qrcode-pay

请求参数

参数 类型 必填 说明
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 字符,默认 "在线支付"
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}

微信 JSAPI 支付

用于微信公众号内唤起微信支付。采用两阶段下单模式:

  1. 商户调用接口获取 pay_url
  2. pay_url 引导用户在微信浏览器中打开,系统自动完成 OAuth 授权并唤起微信支付
商户无需自行集成微信 OAuth 和 WeixinJSBridge,开箱即用。

需使用 HMAC-SHA256 签名认证。

POST /api/cashier/wechat-js-pay

请求参数

参数 类型 必填 说明
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 位(公共参数,参与签名)
sign string HMAC-SHA256 签名(公共参数)
💡 注意公共参数 app_idtimestampnoncesign 与其他业务参数一起参与签名,所有参数平级放在 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,
  "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://payment1.qiquan18.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 支付页面流程

  1. 用户在微信中打开 pay_url
  2. 系统检测无 OpenID → 自动跳转微信 OAuth 静默授权
  3. 微信回调返回 code → 后端换取 openid
  4. 用 token + openid 调银盛网关正式下单
  5. 自动调用 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}

订单查询

查询指定订单的当前状态。通过 order_nomerchant_order_no 均可查询,二选一。

POST /api/cashier/order-query

请求参数

参数 类型 必填 说明
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

错误码

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"

步骤说明

  1. 商户服务器 → PayHub:POST /api/cashier/qrcode-pay(携带签名)
  2. PayHub:验签 → 商户状态 → 时间戳 → IP 白名单 → 订单防重 → 限频检查
  3. PayHub → 支付渠道:调用渠道下单接口
  4. 支付渠道 → PayHub:返回二维码 URL
  5. PayHub → 商户服务器:返回 qrCodeUrl + order_no
  6. 商户前端 → 用户:展示二维码
  7. 用户:扫码完成支付
  8. 支付渠道 → PayHub:异步回调通知
  9. PayHub:更新订单状态
  10. PayHub → 商户 notify_url:Webhook 推送(含 merchant_order_no + extra)
  11. 商户服务器 → 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'            =&gt; $appId,
                    'merchant_order_no' =&gt; $merchantOrderNo,
                    'amount'            =&gt; $amount,
                    'notify_url'        =&gt; 'https://your-domain.com/pay/callback',
                    'extra'             =&gt; json_encode(['user_id' =&gt; 12345]),
                    'note'              =&gt; '在线支付',
                    'timestamp'         =&gt; time(),
                    'nonce'             =&gt; bin2hex(random_bytes(16)),
                    ];
}

                    function makeSign(array $params, string $secret): string
                    {
                      unset($params['sign']);
                    $params = array_filter($params, fn($v) =&gt; $v !== '' &amp;&amp; $v !== null);
                    ksort($params);

                    $pairs = [];
                    foreach ($params as $k =&gt; $v) {
                      $pairs[] = "{$k}={$v}";
    }
                    $signStr = implode('&amp;', $pairs);

                    return hash_hmac('sha256', $signStr, $secret);
}

                    function httpPost(string $url, array $data): array
                    {
                      $ch = curl_init($url);
                    curl_setopt_array($ch, [
                    CURLOPT_POST           =&gt; true,
                    CURLOPT_HTTPHEADER     =&gt; ['Content-Type: application/json', 'Accept: application/json'],
                    CURLOPT_POSTFIELDS     =&gt; json_encode($data),
                    CURLOPT_RETURNTRANSFER =&gt; true,
                    CURLOPT_TIMEOUT        =&gt; 30,
                    ]);
                    $body = curl_exec($ch);
                    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
                    $error = curl_error($ch);
                    curl_close($ch);

                    return [
                    'http_code' =&gt; $httpCode,
                    'body'      =&gt; $body,
                    'error'     =&gt; $error,
                    'json'      =&gt; json_decode($body, true),
                    ];
}