服务端签名

目标

签名方案只验证 服务端签名前的支付数据tt.pay 发送到字节跳动后端的支付数据 的一致性(因而,具体字段有哪些,以及字段的值是否正确并不由签名验证)

生成签名规则

Key 筛选

去掉请求参数中的字节类型字段(如文件、字节流)、signrisk_info 字段、value 为空的字段(某些接口请求参数中特殊标明不参与签名的字段也需要去掉)。

K-V 排序和拼接

把筛选后的 k-v 集合按照 key 的 ASCII 码升序排序,例如:

a, a1, abc, abcd, abce, abd, b1, ba

k-v 按照 key=value 的格式链接,并且按照 key 排序使用 & 连接成一个字符串,生成待签名字符串

MD5 加签

MD5(待签名字符串 + app_secret); // 待签名字符串 + app_secret 是字符串直接拼接,待签名字符串在前,中间没有连接符。

完整流程

例如下面是我们待验签的 orderInfo:

{
  app_id: '800000000001',
  merchant_id: '1900000001',
  timestamp: 1570694312,
  sign_type: 'MD5',
  out_order_no: '201900000000000001',
  total_amount: 1,
  product_code: 'pay',
  payment_type: 'direct',
  trade_type: 'H5',
  version: '2.0',
  currency: 'CNY',
  subject: '测试订单',
  body: '测试订单',
  uid: '0000000000000001',
  trade_time: 1570585744,
  valid_time: 300,
  notify_url: '',
  risk_info: '{"ip":"120.230.0.0"}',
  wx_type: 'MWEB',
  wx_url: 'https://wx.tenpay.com/xxx',
  alipay_url: 'app_id=2019000000000006&biz_content=xxxx'
}

则待签名字符串 unsigned_str 为:

alipay_url=app_id=2019000000000006&biz_content=xxxx&app_id=800000000001&body=测试订单&currency=CNY&merchant_id=1900000001&out_order_no=201900000000000001&payment_type=direct&product_code=pay&sign_type=MD5&subject=测试订单&timestamp=1570694312&total_amount=1&trade_time=1570585744&trade_type=H5&uid=0000000000000001&valid_time=300&version=2.0&wx_type=MWEB&wx_url=https://wx.tenpay.com/xxx

若你的 app_secret 是 'a',那么带入上述 unsigned_str,最终 MD5(unsigned_str + app_secret) 后签名字符串 signed_str 应为:

0f1e3358a9898d7c4c6c23740251808a

验证:你可以在你的验签方法里用上面的 orderInfoapp_secret 数据测试,验证与我们给出的 unsigned_strsigned_str 是否一致

保持一致:通过上述方式生成签名 sign 之后,将该 sign 字段挂在 orderInfo 上并最终透传至前端 tt.pay,传递过程中请不要增删或修改任何字段(保持服务端签名前的支付数据和 tt.pay 发送到字节跳动后端的支付数据一致),否则会导致签名检验不通过

签名代码示例(以 Node 为例)

const crypto = require("crypto");

const createSign = (params, appSecret) => {
  const paramKeys = Object.keys(params).sort();
  let signStr = "";
  paramKeys.forEach(key => {
    let value = params[key];
    // 空值,risk_info, sign 不参与签名
    if (!value || ["risk_info", "sign"].indexOf(key) >= 0) {
      return;
    }
    if (signStr.length > 0) {
      signStr += "&";
    }
    if (typeof value === "object") {
      value = JSON.stringify(value);
    }
    signStr += key + "=" + value;
  });
  signStr += `${appSecret}`;
  console.log("signStr:", signStr);
  const md5 = crypto.createHash("md5");
  const sign = md5.update(signStr).digest("hex");
  return sign;
};

签名验证失败排查方案

  • 服务端:服务端生成签名方式可能有误,请参考上方「完整流程」中的 orderInfoapp_secret 代入你的签名方法,验证与我们给出的预期 unsigned_strsigned_str 是否一致,不一致将导致签名验证失败
  • 小程序端:请保证带有 sign 签名的 orderInfo 由服务端返回且与 tt.pay 中传入的 orderInfo 完全一致,不一致将导致签名验证失败
  • 反馈:若上面两者已经确认无误,但签名验证依然无法通过,请在开发者社区反馈,反馈中请提供线上报 CD0025 失败订单的 unsigned_str

各语言签名实现

可参考以下语言 Demo 中签名的实现

Tips

app_secret 不是指的 小程序后台的 secret, 而是第三方支付平台的 app_secret

点击纠错