签名注意点
你遇到的是微信支付统一下单接口返回签名错误的问题,这是对接微信支付时最常见的问题之一,核心原因是签名生成的规则没有严格遵守微信支付的要求。
一、签名错误的核心原因分析
微信支付签名采用 HMAC-SHA256(或 MD5,取决于配置)算法,任何一个细节不符合规则都会导致签名错误,常见原因包括:
- 参数名大小写错误(微信要求全部小写,如
appid而非AppId); - 参数值包含多余空格 / 特殊字符(如金额
total_fee带千分位、参数值前后有空格); - 签名参数未按ASCII 码升序排列;
- 密钥错误(使用了公众号 AppSecret 而非 API 密钥,或密钥有空格);
- 漏传 / 多传参数(如统一下单必须传
nonce_str,但不能传sign本身); - 编码问题(参数值含中文时未用 UTF-8 编码);
- 金额单位错误(
total_fee要求为分,而非元)。
二、Python实现
正确的签名生成步骤
下面提供可直接运行的代码,严格遵循微信支付签名规则,解决统一下单签名错误问题:
前置条件
- 已获取微信支付的
appid、mch_id(商户号)、api_key(API 密钥,在微信商户平台 - 账户中心 - API 安全中设置); - 安装依赖(仅需内置库,无需额外安装)。
完整代码示例
python
运行
import hashlib
import hmac
import random
import string
from urllib.parse import urlencode
def generate_nonce_str(length=32):
"""生成随机字符串(nonce_str),避免重复"""
chars = string.ascii_letters + string.digits
return ''.join(random.choice(chars) for _ in range(length))
def generate_wechat_sign(params, api_key, sign_type="HMAC-SHA256"):
"""
生成微信支付签名
:param params: 待签名的参数字典(不含sign字段)
:param api_key: 微信支付API密钥
:param sign_type: 签名类型,默认HMAC-SHA256,可选MD5
:return: 签名结果
"""
# 1. 过滤空值参数(微信要求空值参数不参与签名)
filtered_params = {k: v for k, v in params.items() if v is not None and v != ""}
# 2. 按参数名ASCII码升序排序
sorted_params = sorted(filtered_params.items(), key=lambda x: x[0])
# 3. 拼接成key=value&key=value格式
sign_str = urlencode(sorted_params) + f"&key={api_key}"
# 4. 生成签名(注意编码为UTF-8)
if sign_type == "HMAC-SHA256":
sign = hmac.new(api_key.encode('utf-8'), sign_str.encode('utf-8'), hashlib.sha256).hexdigest().upper()
else: # MD5
sign = hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper()
return sign
def create_unified_order_params():
"""构造统一下单接口的参数(示例)"""
# 基础参数(替换为你的实际值)
appid = "你的APPID"
mch_id = "你的商户号"
api_key = "你的API密钥"
out_trade_no = "自定义订单号(唯一)" # 如"20251226001"
total_fee = 1 # 订单金额,单位:分(示例为1分)
spbill_create_ip = "客户端IP" # 如"127.0.0.1"
notify_url = "你的支付结果通知地址" # 公网可访问
trade_type = "JSAPI" # 支付类型,如JSAPI/APP/NATIVE
openid = "用户openid" # trade_type为JSAPI时必填
# 构造待签名参数(全部小写,无sign字段)
params = {
"appid": appid,
"mch_id": mch_id,
"nonce_str": generate_nonce_str(), # 随机字符串
"body": "测试商品", # 商品描述
"out_trade_no": out_trade_no,
"total_fee": total_fee,
"spbill_create_ip": spbill_create_ip,
"notify_url": notify_url,
"trade_type": trade_type,
"openid": openid
}
# 生成签名并添加到参数中
params["sign"] = generate_wechat_sign(params, api_key)
return params
# 测试生成参数(可打印出来核对)
if __name__ == "__main__":
order_params = create_unified_order_params()
print("统一下单参数(含正确签名):")
for k, v in order_params.items():
print(f"{k}: {v}")
三、关键细节解释
- 参数过滤:空值参数(如
attach为空)必须剔除,否则会导致签名错误; - 排序规则:严格按参数名 ASCII 码升序(如
appid在mch_id前),而非字母顺序; - 编码问题:所有字符串拼接前必须用 UTF-8 编码,中文参数(如
body)需确保无乱码; - 签名后大写:微信要求签名结果全部大写(如
A1B2C3而非a1b2c3); - API 密钥:必须使用微信商户平台设置的
API密钥,而非公众号的AppSecret。
四、排查签名错误的实用技巧
- 使用微信官方签名校验工具:微信提供了在线签名校验工具(点击进入),将你拼接的参数串和密钥输入,对比生成的签名是否与你的一致,快速定位问题;
- 打印拼接后的签名串:在代码中打印
sign_str(拼接后的 key=value&key=value&key = 密钥),手动核对是否符合规则; - 核对参数完整性:统一下单必填参数:
appid、mch_id、nonce_str、body、out_trade_no、total_fee、spbill_create_ip、notify_url、trade_type(JSAPI 需加openid),缺一不可。
总结
- 签名错误的核心是参数规则不匹配,需严格遵守:参数小写、空值过滤、ASCII 升序、UTF-8 编码、密钥正确;
- 优先使用微信官方校验工具核对签名,比手动排查更高效;
- 确保
total_fee单位为分、参数无多余空格、sign字段最后添加且不参与自身签名。
JAVA实现
一、核心实现思路
Java 实现微信支付签名的核心逻辑与 Python 一致,需严格遵循:
- 过滤空值参数,仅保留有值的参数;
- 按参数名 ASCII 码升序排序;
- 拼接成
key=value&key=value格式,最后拼接&key=API密钥; - 使用 HMAC-SHA256(或 MD5)算法生成签名,结果转大写。
二、完整 Java 代码实现
前置条件
- 确保项目引入
commons-codec(用于加密),Maven 依赖如下:
xml
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
- 准备好微信支付的
appid、mch_id(商户号)、apiKey(API 密钥,商户平台设置)。
核心工具类代码
java
运行
import org.apache.commons.codec.digest.DigestUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;
/**
* 微信支付签名工具类(解决统一下单签名错误问题)
*/
public class WxPaySignUtil {
// 签名类型:HMAC-SHA256(推荐)
public static final String SIGN_TYPE_HMAC_SHA256 = "HMAC-SHA256";
// 签名类型:MD5(兼容旧版本)
public static final String SIGN_TYPE_MD5 = "MD5";
/**
* 生成随机字符串(nonce_str),避免重复
* @param length 字符串长度,默认32位
* @return 随机字符串
*/
public static String generateNonceStr(int length) {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < length; i++) {
int index = random.nextInt(chars.length());
sb.append(chars.charAt(index));
}
return sb.toString();
}
/**
* 生成微信支付签名
* @param params 待签名参数(不含sign字段,key为小写)
* @param apiKey 微信支付API密钥
* @param signType 签名类型(HMAC-SHA256/MD5)
* @return 签名结果(大写)
* @throws NoSuchAlgorithmException 算法不存在异常
* @throws InvalidKeyException 密钥无效异常
*/
public static String generateSign(Map<String, String> params, String apiKey, String signType)
throws NoSuchAlgorithmException, InvalidKeyException {
// 1. 过滤空值参数(空字符串/NULL不参与签名)
Map<String, String> filteredParams = new HashMap<>();
for (Map.Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (value != null && !value.trim().isEmpty()) {
filteredParams.put(key, value.trim()); // 去除参数值前后空格
}
}
// 2. 按参数名ASCII码升序排序(核心!排序错误必出签名错误)
List<String> sortedKeys = new ArrayList<>(filteredParams.keySet());
Collections.sort(sortedKeys);
// 3. 拼接成key=value&key=value格式
StringBuilder signStr = new StringBuilder();
for (String key : sortedKeys) {
signStr.append(key).append("=").append(filteredParams.get(key)).append("&");
}
// 最后拼接API密钥(注意:key=你的密钥,无多余空格)
signStr.append("key=").append(apiKey);
// 4. 生成签名并转大写
if (SIGN_TYPE_HMAC_SHA256.equals(signType)) {
return hmacSha256(signStr.toString(), apiKey).toUpperCase();
} else { // MD5
return DigestUtils.md5Hex(signStr.toString().getBytes(StandardCharsets.UTF_8)).toUpperCase();
}
}
/**
* HMAC-SHA256加密
*/
private static String hmacSha256(String data, String key) throws NoSuchAlgorithmException, InvalidKeyException {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKeySpec);
byte[] bytes = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 转16进制字符串
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
/**
* 构造统一下单接口参数(示例)
*/
public static Map<String, String> createUnifiedOrderParams(String apiKey) throws NoSuchAlgorithmException, InvalidKeyException {
// 替换为你的实际参数
String appid = "你的APPID";
String mchId = "你的商户号";
String outTradeNo = "20251226001"; // 自定义唯一订单号
int totalFee = 1; // 金额,单位:分(1分)
String spbillCreateIp = "127.0.0.1"; // 客户端IP
String notifyUrl = "你的支付结果通知地址(公网可访问)";
String tradeType = "JSAPI"; // 支付类型:JSAPI/APP/NATIVE
String openid = "用户的openid"; // JSAPI支付必填
// 1. 构造待签名参数(所有key必须小写,无sign字段)
Map<String, String> params = new HashMap<>();
params.put("appid", appid);
params.put("mch_id", mchId);
params.put("nonce_str", generateNonceStr(32)); // 随机字符串
params.put("body", "测试商品"); // 商品描述(UTF-8编码)
params.put("out_trade_no", outTradeNo);
params.put("total_fee", String.valueOf(totalFee)); // 金额转字符串,避免类型问题
params.put("spbill_create_ip", spbillCreateIp);
params.put("notify_url", notifyUrl);
params.put("trade_type", tradeType);
params.put("openid", openid);
// 2. 生成签名并添加到参数中(签名不参与自身生成)
String sign = generateSign(params, apiKey, SIGN_TYPE_HMAC_SHA256);
params.put("sign", sign);
return params;
}
// 测试方法
public static void main(String[] args) {
try {
// 替换为你的API密钥
String apiKey = "你的微信支付API密钥";
Map<String, String> orderParams = createUnifiedOrderParams(apiKey);
// 打印参数,可用于微信官方签名工具校验
System.out.println("统一下单参数(含正确签名):");
for (Map.Entry<String, String> entry : orderParams.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("签名生成失败:" + e.getMessage());
}
}
}
三、关键细节解释(解决签名错误的核心)
- 参数名大小写:所有参数名必须是小写(如
mch_id而非mchId/MCH_ID),这是最常见的错误点; - 空值过滤:代码中通过
value != null && !value.trim().isEmpty()过滤空值,微信要求空参数不参与签名; - 排序规则:使用
Collections.sort(sortedKeys)对参数名升序排序,严格遵循 ASCII 码顺序; - 编码问题:所有字符串拼接和加密均使用
UTF-8(StandardCharsets.UTF_8),避免中文乱码导致签名错误; - 金额格式:
total_fee必须是分为单位的字符串(如 1 分对应"1"),不能传小数(如"0.01"); - 签名大写:加密结果通过
.toUpperCase()转大写,符合微信支付的签名规范。
四、排查签名错误的额外技巧
- 打印拼接后的签名串:在
generateSign方法中添加System.out.println("签名拼接串:" + signStr.toString());,将拼接后的字符串复制到微信官方签名校验工具,对比生成的签名是否一致; - 核对 API 密钥:确认使用的是微信商户平台(https://pay.weixin.qq.com)→ 账户中心 → API 安全 → API 密钥,而非公众号的 AppSecret;
- 参数完整性:统一下单必填参数(appid、mch_id、nonce_str、body、out_trade_no、total_fee、spbill_create_ip、notify_url、trade_type)缺一不可,JSAPI 支付还需 openid。
总结
- Java 实现微信支付签名的核心是参数小写、空值过滤、ASCII 升序、UTF-8 编码、签名大写;
- 优先通过打印签名拼接串 + 官方校验工具定位签名错误,比逐行排查更高效;
- 确保
total_fee单位为分、参数值无多余空格、API 密钥正确。
问题定位
1)先比对信息,看看有没有多输空格啊,大小写,关键词等。
2)如果是一个新的项目,那么就要去检测是否在线测试签名正确。
3)如果签名正确,说明程序基本上是没有问题,那么我们就要去查看设置,绑定的公众号等等。
问题解决
在线测试,签名一切正常,但是就是程序里报签名错误。
1)给的32位的API V2密码不正确。特别是客户有多个商户号的时候,就会出现这种情况。
2)商户号不正确,特别是有二个商户号或者多个商户号的时候,客户给了其中一样,然后商户号没有跟服务号对应。这种情况在线测试签名正确,然后就是在程序里报签名错误。
上面的二种情况,都是没有对应的,在线测试的地址,只是测试你给的参数进行处理后,是不是签名就是这个,如果是那么检测通过。但不会帮你测试你的商户号是否对应你的服务号,这种情况比较难查,只有通过让商户去扫码,登进去查看,突然会发现,商户号给错了。
如果商户号是对的,那么可以尝试重新设置一下API密钥。

1515

被折叠的 条评论
为什么被折叠?



