Skip to content

协议与签名 ​

通用请求参数 ​

商户开放接口支持 JSON 与 application/x-www-form-urlencoded。本文统一推荐 POST JSON;转账查询额外支持 GET 查询参数,退款查询不应使用 GET 参数。

字段类型必填说明
mchNostring是商户号
appIdstring是属于该商户且已启用的应用编号
signstring是应用私钥生成的 MD5 签名
signTypestring否默认 MD5,目前只支持 MD5
reqTimestring / integer否毫秒时间戳,建议传入;当前认证器未强制校验时间窗口

除 sign 自身外,实际发送的全部非空字段均参与签名,包括 signType、reqTime 和扩展字段。新增字段后应重新计算签名。

计算规则 ​

  1. 删除 sign,过滤 null 和空字符串 "",保留数字 0。
  2. 按参数名忽略大小写排序,字段名自身大小写保持原样。避免传入仅大小写不同的重复参数名。
  3. 将每项拼成 参数名=参数值&,末尾追加 key=appSecret。
  4. 对完整文本计算 MD5,转为大写十六进制字符串。

签名原文不进行 URL 编码。签名后再序列化 JSON 或编码表单。布尔值按 true / false;数组、对象按不转义中文和斜杠的紧凑 JSON 序列化,保留原键顺序。建议 channelExtra 直接传 JSON 字符串,使用同一原文参与签名及请求,避免不同语言的序列化差异。

text
amount=100&appId=APP_DEMO&...&wayCode=ALI_QR&key=DEMO_SECRET

上例含省略号,仅说明拼接结构,不是可计算的签名向量。完整可运行示例:

js
import { createHash } from 'node:crypto'

export function sign(params, appSecret) {
  const values = Object.entries(params)
    .filter(([key, value]) => key !== 'sign' && value !== null && value !== undefined && value !== '')
    .sort(([a], [b]) => {
      const x = a.toLowerCase(), y = b.toLowerCase()
      return x < y ? -1 : x > y ? 1 : 0
    })
    .map(([key, value]) => {
      const text = typeof value === 'object' ? JSON.stringify(value) : String(value)
      return `${key}=${text}&`
    }).join('')
  return createHash('md5').update(values + 'key=' + appSecret, 'utf8').digest('hex').toUpperCase()
}

此代码仅在 Node.js 服务端使用,完整文件位于项目 examples/sign.mjs。

响应与验签 ​

成功响应的 sign 仅对 data 内字段签名,不要把外层 code 加入验签。

json
{
  "code": 0,
  "data": { "payOrderId": "P_DEMO", "mchOrderNo": "ORDER_DEMO", "state": 2 },
  "sign": "对data计算的签名"
}

成功响应当前不含 msg,data 为空数组时不含签名。错误响应通常仍使用 HTTP 200,应判断业务 code:

json
{ "code": 1, "msg": "验签失败" }

错误响应不签名。code=0 表示接口调用成功,资金结果还需判断 state 或 orderState。收银台使用独立协议,见 收银台接口。

接口行为以当前 payment 实现为准。示例商户号与密钥均为虚构。