在接入微信公众号支付时,商户后台需要生成一个签名,用于向微信支付统一订单接口发起请求。这个签名算法的核心步骤并不复杂,但细节之处如果处理不当,就会导致签名校验失败。本文会完整走一遍从参数收集、排序、拼接、加密到最终生成签名的流程,并给出可直接运行的代码片段。

签名前的准备工作与参数收集
JSAPI签名的输入是一组键值对,这些参数包含了订单号、金额、商品描述、用户标识等信息。第一步需要明确哪些字段参与签名。微信支付官方文档规定,除了sign字段本身,其余所有发送给微信支付的参数都要参与签名。需要注意的是,参数值为空的字段不应参与签名,否则会导致加密结果不一致。开发中常见的做法是先构建一个字典或关联数组,然后移除值为空字符串或null的项,再进入排序步骤。
商户密钥是签名的关键材料。签名算法要求商户在拼接字符串的最后追加一个key参数,即商户密钥。这个密钥需要在微信商户平台中设置,长度通常为32位。务必保证商户密钥不会泄露,更不能硬编码在客户端代码中。服务端获取密钥后,直接参与MD5运算,不需要进行URL编码,也不需要做任何大小写转换。
下面是一段JavaScript示例,演示如何收集并过滤参数:
// 假设这是需要参与签名的参数对象
let params = {
appid: 'wx1234567890abcdef',
mch_id: '1234567890',
nonce_str: '随机字符串',
body: '测试商品',
out_trade_no: '202401010001',
total_fee: 1,
spbill_create_ip: '127.0.0.1',
notify_url: 'https://ipipp.com/notify',
trade_type: 'JSAPI',
openid: '用户openid'
};
// 过滤空值
let filteredParams = {};
for (let key in params) {
if (params[key] !== '' && params[key] !== null && params[key] !== undefined) {
filteredParams[key] = params[key];
}
}
参数排序与URL键值对拼接
参数过滤完成后,需要对所有键按照ASCII码从小到大排序。排序依据是键名的字典序,即字符的ASCII值。排序方式与编程语言的默认字符串排序一致,但要注意大小写:大写字母的ASCII值小于小写字母,因此排序时大写字母会排在小写字母前面。使用排序函数时要确保是稳定的、基于字符编码的顺序排序,而非自然语言排序。
排序完成后,将参数拼接成类似URL查询字符串的格式,即key1=value1&key2=value2...。拼接时不需要对值进行URL编码,直接使用原始值即可。如果值中包含中文或特殊字符,保留原样,不做转义。所有键值对用&符号连接,最后一个参数后面不要加&。最终字符串末尾再拼接上&key=商户密钥,形成待签名字符串。
下面的代码展示了排序和拼接的完整过程:
// 对过滤后的参数按key的ASCII码排序
let sortedKeys = Object.keys(filteredParams).sort();
let stringA = '';
for (let i = 0; i < sortedKeys.length; i++) {
let key = sortedKeys[i];
stringA += key + '=' + filteredParams[key];
if (i !== sortedKeys.length - 1) {
stringA += '&';
}
}
// 拼接商户密钥
let merchantKey = 'abcdefghijklmnopqrstuvwxyz123456';
let stringSignTemp = stringA + '&key=' + merchantKey;
console.log(stringSignTemp);
MD5加密与签名生成
对待签名字符串进行MD5运算,得到32位十六进制字符串。MD5的结果通常是小写字母,但微信支付要求签名使用大写形式。因此计算完成后需要将字符串转换为大写。不同编程语言提供的MD5库可能直接返回二进制数据或十六进制字符串,注意转换方式。例如在PHP中md5函数返回32位小写十六进制字符串,直接使用strtoupper转换成大写即可;在Node.js中使用crypto模块的md5,需要指定digest为hex并转为大写。
经过MD5加密并转为大写后,得到的字符串就是最终的sign值。这个值将作为sign参数添加到请求体中,与之前参与签名的其他字段一起发送给微信支付服务器。服务器会使用相同的算法重新计算签名,如果一致则验证通过,否则返回签名错误提示。
下面是一个完整的签名函数示例(Node.js环境):
const crypto = require('crypto');
function createSign(params, merchantKey) {
// 过滤空值
let filtered = {};
for (let key in params) {
if (params[key] !== '' && params[key] !== null && params[key] !== undefined) {
filtered[key] = params[key];
}
}
// 排序
let sortedKeys = Object.keys(filtered).sort();
let stringA = sortedKeys.map(key => key + '=' + filtered[key]).join('&');
// 拼接密钥
let stringSignTemp = stringA + '&key=' + merchantKey;
// MD5计算并转大写
let sign = crypto.createHash('md5').update(stringSignTemp, 'utf8').digest('hex').toUpperCase();
return sign;
}
常见错误与排查建议
签名错误是接入微信支付时最常见的问题之一。一个典型的错误是参数排序时使用了错误的排序规则,比如按照插入顺序或是忽略ASCII大小写规则。另一个问题是参数值包含空格或特殊字符,在传输过程中被意外编码,导致服务器端计算的源串与商户端不一致。建议在调试时打印出待签名字符串,与微信支付官方提供的签名调试工具比对,能够快速定位差异。
商户密钥位置错误也是高频失败原因。务必确认拼接的是&key=商户密钥,而不是直接拼接商户密钥或者使用其他key名。另外,签名结果必须转为大写,即使MD5库返回的字符串本身是大写也要检查是否有额外字符,比如某些语言返回的字符串可能带换行符。最后,注意参与签名的字段要完整,遗漏任何一个非空字段都会导致签名不一致。
如果使用Java、PHP或Python等语言实现签名,原理完全一致,只需注意语言内置排序和加密函数的细节。例如Java中使用TreeMap自动排序,PHP中使用ksort,Python中使用sorted。只要严格遵循以上流程,就可以稳定生成正确的签名。