数据签名
微信支付目前现行两大类数据签名方法,对称算法基于MD5及HMAC-SHA256数据摘要算法,非对称算法基于RSA-OAEP(模数2048)算法。
对称算法
签名生成的通用步骤如下:
第一步:
设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
- 参数名ASCII码从小到大排序(字典序);
- 如果参数的值为空不参与签名;
- 参数名区分大小写;
- 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验;
- 微信支付接口可能增加字段,验证签名时必须支持增加的扩展字段;
第二步:
在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5/HMAC-SHA256运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
特别注意:
- 密钥key的长度为32个字节;
- key设置路径:微信商户平台(pay.weixin.qq.com)-->账户中心-->账户设置-->API安全-->设置API密钥;
微信支付API接口协议中包含字段nonce_str,主要保证签名不可预测。微信支付官方推荐生成随机数算法如下:调用随机数函数生成,将得到的值转换为字符串。
以官方文档举的例子,用NodeJS实现:
后台数据交换
设传送的参数集合M如下:
{
appid: 'wxd930ea5d5a258f4f',
mch_id: '10000100',
device_info: '1000',
body: 'test',
nonce_str: 'ibuaiVcKdpRxkhJA',
}
第一步:按照参数名ASCII字典序并对参数按照URL键值对的格式拼接如下:
stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA"
第二步:拼接API密钥key:
//注:key为商户平台设置的密钥key
stringSignTemp=stringA+"&key=192006250b4c09247ec02edce69f6a2d"
//注:MD5签名方式
sign=MD5(stringSignTemp).toUpperCase()
//样本值="9A0A8659F005D6984697E2CA0A9CF3B7"
//注:HMAC-SHA256签名方式,部分语言的hmac方法生成结果二进制结果,需要调对应函数转化为十六进制字符串。
sign=HASH_HMAC(stringA, key, "sha256").toUpperCase()
//样本值="6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6"
则传输的数据即为:
{
appid: 'wxd930ea5d5a258f4f',
mch_id: '10000100',
device_info: '1000',
body: 'test',
nonce_str: 'ibuaiVcKdpRxkhJA',
//以MD5为例
sign: '9A0A8659F005D6984697E2CA0A9CF3B7',
}
APIv2是以XML
格式作为数据交换方式,则需转换上述数据为XML
文本格式如下:
<xml>
<appid>wxd930ea5d5a258f4f</appid>
<mch_id>10000100</mch_id>
<device_info>1000</device_info>
<body>test</body>
<nonce_str>ibuaiVcKdpRxkhJA</nonce_str>
<sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign>
</xml>
以上签名及数据格式转换过程,用本开发包即如下:
const { Formatter, Hash, Transformer } = require('wechatpay-axios-plugin')
// XML格式转换
Transformer.toXml({
...M,
// 数据签名并把值大写
sign: Hash.md5(
// URL风格拼接数据
Formatter.queryStringLike(
// 按参数名字典序排列
Formatter.ksort(M)
),
key
).toUpperCase()
})
// 或者使用封装的`Hash.sign`方法简化如下
Transformer.toXml({
...M,
// 对集合M按参数名字典序排列 -> URL风格拼接数据 -> 数据签名并把值大写
sign: Hash.sign(signType, M, key)
})
前台数据交换
此种方式也遵循通用步骤原则,只是集合M按业务表现不同而不同,归纳如下:
现金支付
JSAPI 唤起微信支付收银台场景
const { Formatter, Hash, Transformer } = require('wechatpay-axios-plugin')
const nonceStr = Formatter.nonce();
const timeStamp = '' + Formatter.timestamp();
const packageStr = 'prepay_id=' + prepay_id;
// previousSignType 即后台调用 unifiedorder 接口时的签名方法
const signType = previousSignType || 'MD5';
const M = { appId, timeStamp, nonceStr, package: packageStr, signType };
const data = {
...M,
paySign: Hash.sign(signType, M, apiv2Secret)
}
APP 唤起微信支付收银台场景
const { Formatter, Hash, Transformer } = require('wechatpay-axios-plugin')
const noncestr = Formatter.nonce();
const timestamp = '' + Formatter.timestamp();
const packageStr = 'Sign=WXPay';
// previousSignType 即后台调用 unifiedorder 接口时的签名方法
const signType = previousSignType || 'MD5';
const M = { appid, partnerid, prepayid, package: packageStr, timestamp, noncestr };
const data = {
...M,
sign: Hash.sign(signType, M, apiv2Secret)
}
小程序红包
微信小程序 唤起发放小程序红包场景
const { Formatter, Hash, Transformer } = require('wechatpay-axios-plugin')
const nonceStr = Formatter.nonce();
const timeStamp = '' + Formatter.timestamp();
// 签名方法为固定值
const signType = 'MD5';
const data = {
appId,
timeStamp,
nonceStr,
// packageStr 即后台调用 sendminiprogramhb 接口的 package 返回字符串
package: encodeURIComponent(packageStr),
signType,
paySign: Hash.sign(
signType,
{ appId, timeStamp, nonceStr, package: packageStr },
apiv2Secret
)
}
代金券/商家券
小程序发券插件
const { Hash } = require('wechatpay-axios-plugin')
// 签名方法为固定值
const signType = 'HMAC-SHA256'
const send_coupon_params = [{
stock_id,
out_request_no,
},{
stock_id,
out_request_no,
}]
const flat4sign = send_coupon_params.reduce((x, y, i) => {
Object.entries(y).map(([k, v]) => x[`${k}${i}`] = v)
return x
}, { send_coupon_merchant })
const sign = Hash.sign(signType, flat4sign, apiv2Secret)
通过Url跳转,由商户H5重定向至指定微信支付H5页面
const { Hash } = require('wechatpay-axios-plugin')
// 签名方法为固定值
const signType = 'HMAC-SHA256'
const input = {
stock_id,
out_request_no,
send_coupon_merchant,
open_id,
coupon_code,
}
const sign = Hash.sign(signType, input, apiv2Secret)
const nextUrl = `${actionUrl}?${new URLSearchParams({...input, sign})}#wechat_redirect`
微信支付分
APP 唤起微信支付分小程序订单详情场景
const { Formatter, Hash } = require('wechatpay-axios-plugin')
const nonce_str = Formatter.nonce()
const timestamp = '' + Formatter.timestamp()
const out_order_no = encodeURIComponent(outOrderNo)
// 签名方法为固定值
const signType = 'HMAC-SHA256'
const input = {
mch_id,
service_id,
out_order_no,
timestamp,
nonce_str,
sign_type: signType,
}
const sign = Hash.sign(signType, input, apiv2Secret)
const data = {
businessType: 'wxpayScoreDetail',
query: Formatter.queryStringLike(input) + '&sign=' + sign,
extInfo,
}
JSAPI 唤起微信支付分小程序订单详情场景
const { Formatter, Hash } = require('wechatpay-axios-plugin')
const nonce_str = Formatter.nonce()
const timestamp = '' + Formatter.timestamp()
const out_order_no = encodeURIComponent(outOrderNo)
// 签名方法为固定值
const signType = 'HMAC-SHA256'
const input = {
mch_id,
service_id,
out_order_no,
timestamp,
nonce_str,
sign_type: signType,
}
const sign = Hash.sign(signType, input, apiv2Secret)
const data = {
businessType: 'wxpayScoreDetail',
queryString: Formatter.queryStringLike(input) + '&sign=' + sign,
}
微信小程序 唤起微信支付分小程序订单详情场景
const { Formatter, Hash } = require('wechatpay-axios-plugin')
const nonce_str = Formatter.nonce()
const timestamp = '' + Formatter.timestamp()
// 签名方法为固定值
const signType = 'HMAC-SHA256'
const input = {
mch_id,
service_id,
out_order_no,
timestamp,
nonce_str,
sign_type: signType,
}
const sign = Hash.sign(signType, input, apiv2Secret)
const data = {
businessType: 'wxpayScoreDetail',
extraData: { ...input, sign },
}
非对称算法
此种签名方法,官方文档介绍 已经很明晰,这里不再细述规则,仅做实现介绍如下:
后台数据交换
请求数据签名,用本开发包如下:
const { Formatter, Rsa } = require('wechatpay-axios-plugin')
Rsa.sign(
Formatter.request(
httpMethod,
uri,
Formatter.timestamp(),
Formatter.nonce(),
body
),
merchantPrivateKeyInstance
)
返回值数据验签,用本开发包如下:
const { Formatter, Rsa } = require('wechatpay-axios-plugin')
Rsa.verify(
Formatter.response(
timestamp,
nonce,
body
),
signature,
platformCertificates[serial],
)
前台数据交换
JSAPI 唤起微信支付收银台场景
const { Formatter, Rsa } = require('wechatpay-axios-plugin')
const nonceStr = Formatter.nonce();
const timeStamp = '' + Formatter.timestamp();
const packageStr = 'prepay_id=' + prepay_id;
const data = {
appId,
timeStamp,
nonceStr,
package: packageStr,
signType: 'RSA',
paySign: Rsa.sign(
Formatter.joinedByLineFeed(appId, timeStamp, nonceStr, packageStr),
merchantPrivateKeyInstance
)
}
APP 唤起微信支付收银台场景
const { Formatter, Rsa } = require('wechatpay-axios-plugin')
const noncestr = Formatter.nonce();
const timestamp = '' + Formatter.timestamp();
const data = {
appid,
partnerid,
prepayid,
package: 'Sign=WXPay',
timestamp,
noncestr,
sign: Rsa.sign(
Formatter.joinedByLineFeed(appid, timestamp, noncestr, prepayid),
merchantPrivateKeyInstance
)
}