服务端签名

目标

签名方案只验证 服务端签名前的支付数据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 是字符串直接拼接,待签名字符串在前,中间没有连接符。

注意:这里的 app_secret 是开发者后台支付设置页的支付secret

图片名称

完整流程

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

{
  "app_id": "800000040005",
  "sign_type": "MD5",
  "out_order_no": "MicroApp7075638135",
  "merchant_id": "1300000004",
  "timestamp": "1566720681",
  "product_code": "pay",
  "payment_type": "direct",
  "total_amount": 1,
  "trade_type": "H5",
  "uid": "2019012211",
  "version": "2.0",
  "currency": "CNY",
  "subject": "microapp test",
  "body": "microapp test",
  "trade_time": "1566720681",
  "valid_time": "300",
  "notify_url": "https://tp-pay.snssdk.com/cashdesk/test/paycallback",
  "wx_url": "https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx25161122572189727ea14cfd1832451500&package=2746219290",
  "wx_type": "MWEB",
  "alipay_url": "alipay_sdk=alipay-sdk-java-3.4.27.ALL&app_id=2018061460417275&biz_content=%7B%22body%22%3A%22%E6%B5%8B%E8%AF%95%E8%AE%A2%E5%8D%95%22%2C%22extend_params%22%3A%7B%7D%2C%22out_trade_no%22%3A%2211908250000028453790%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22seller_id%22%3A%222088721387102560%22%2C%22subject%22%3A%22%E6%B5%8B%E8%AF%95%E8%AE%A2%E5%8D%95%22%2C%22timeout_express%22%3A%22599m%22%2C%22total_amount%22%3A%220.01%22%7D&charset=utf-8&format=json&method=alipay.trade.app.pay&notify_url=http%3A%2F%2Fapi-test-pcs.snssdk.com%2Fgateway%2Fpayment%2Fcallback%2Falipay%2Fnotify%2Fpay&sign=D2A6ua51os2aIzIH907ppK7Bd9Q2Kk5h7AtKPdudP%2Be%2BNTtAkp0Lfojtgl4BMOIQ3Z7cWyYMx6nk4qbntSx7aZnBhWAcImLbVVr1cmaYAedmrmJG%2B3f8G5TfAZu53ESzUgk02%2FhU1XV0iXRyE8TdEJ97ufmxwsUEc7K0EvwEFDIBCJg73meQtyCRFgCqYRWvmxetQgL7pwfKXpFXjAYsvFrRBas2YGYt689XpBS321g%2BZ8SZ0JOtLPWqhROzEs3dnAtWBW15y3NzRiSNi5rPzah4cWd4SgT0LZHmNf3eDQEHEcPmofoWfnA4ao75JmP95aLUxerMumzo9OwqhiYOUw%3D%3D&sign_type=RSA2&timestamp=2019-08-25+16%3A11%3A22&version=1.0",
  "risk_info": "{\"ip\":\"127.0.0.1\"}"
}

则待签名字符串 unsigned_str 为:

alipay_url=alipay_sdk=alipay-sdk-java-3.4.27.ALL&app_id=2018061460417275&biz_content=%7B%22body%22%3A%22%E6%B5%8B%E8%AF%95%E8%AE%A2%E5%8D%95%22%2C%22extend_params%22%3A%7B%7D%2C%22out_trade_no%22%3A%2211908250000028453790%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22seller_id%22%3A%222088721387102560%22%2C%22subject%22%3A%22%E6%B5%8B%E8%AF%95%E8%AE%A2%E5%8D%95%22%2C%22timeout_express%22%3A%22599m%22%2C%22total_amount%22%3A%220.01%22%7D&charset=utf-8&format=json&method=alipay.trade.app.pay&notify_url=http%3A%2F%2Fapi-test-pcs.snssdk.com%2Fgateway%2Fpayment%2Fcallback%2Falipay%2Fnotify%2Fpay&sign=D2A6ua51os2aIzIH907ppK7Bd9Q2Kk5h7AtKPdudP%2Be%2BNTtAkp0Lfojtgl4BMOIQ3Z7cWyYMx6nk4qbntSx7aZnBhWAcImLbVVr1cmaYAedmrmJG%2B3f8G5TfAZu53ESzUgk02%2FhU1XV0iXRyE8TdEJ97ufmxwsUEc7K0EvwEFDIBCJg73meQtyCRFgCqYRWvmxetQgL7pwfKXpFXjAYsvFrRBas2YGYt689XpBS321g%2BZ8SZ0JOtLPWqhROzEs3dnAtWBW15y3NzRiSNi5rPzah4cWd4SgT0LZHmNf3eDQEHEcPmofoWfnA4ao75JmP95aLUxerMumzo9OwqhiYOUw%3D%3D&sign_type=RSA2&timestamp=2019-08-25+16%3A11%3A22&version=1.0&app_id=800000040005&body=microapp test&currency=CNY&merchant_id=1300000004&notify_url=https://tp-pay.snssdk.com/cashdesk/test/paycallback&out_order_no=MicroApp7075638135&payment_type=direct&product_code=pay&sign_type=MD5&subject=microapp test&timestamp=1566720681&total_amount=1&trade_time=1566720681&trade_type=H5&uid=2019012211&valid_time=300&version=2.0&wx_type=MWEB&wx_url=https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx25161122572189727ea14cfd1832451500&package=2746219290

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

07d5988aa5dc9f9f604711a118ad16cf

验证:你可以在你的验签方法里用上面的 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;
};

签名验证失败排查方案

可以判断 timestamp 字段在签名前后发生了改变

  • 社区反馈:若自助排查已经确认无误,但签名验证依然无法通过,请在开发者社区反馈,反馈中提供线上报 CD0025 失败订单的 unsigned_str

各语言签名实现

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

点击纠错