微信支付 统一下单 签名错误

签名注意点

你遇到的是微信支付统一下单接口返回签名错误的问题,这是对接微信支付时最常见的问题之一,核心原因是签名生成的规则没有严格遵守微信支付的要求。

一、签名错误的核心原因分析

微信支付签名采用 HMAC-SHA256(或 MD5,取决于配置)算法,任何一个细节不符合规则都会导致签名错误,常见原因包括:

  1. 参数名大小写错误(微信要求全部小写,如appid而非AppId);
  2. 参数值包含多余空格 / 特殊字符(如金额total_fee带千分位、参数值前后有空格);
  3. 签名参数未按ASCII 码升序排列
  4. 密钥错误(使用了公众号 AppSecret 而非 API 密钥,或密钥有空格);
  5. 漏传 / 多传参数(如统一下单必须传nonce_str,但不能传sign本身);
  6. 编码问题(参数值含中文时未用 UTF-8 编码);
  7. 金额单位错误(total_fee要求为分,而非元)。

二、Python实现

正确的签名生成步骤

下面提供可直接运行的代码,严格遵循微信支付签名规则,解决统一下单签名错误问题:

前置条件
  1. 已获取微信支付的appidmch_id(商户号)、api_key(API 密钥,在微信商户平台 - 账户中心 - API 安全中设置);
  2. 安装依赖(仅需内置库,无需额外安装)。
完整代码示例

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}")

三、关键细节解释

  1. 参数过滤:空值参数(如attach为空)必须剔除,否则会导致签名错误;
  2. 排序规则:严格按参数名 ASCII 码升序(如appidmch_id前),而非字母顺序;
  3. 编码问题:所有字符串拼接前必须用 UTF-8 编码,中文参数(如body)需确保无乱码;
  4. 签名后大写:微信要求签名结果全部大写(如A1B2C3而非a1b2c3);
  5. API 密钥:必须使用微信商户平台设置的API密钥,而非公众号的AppSecret

四、排查签名错误的实用技巧

  1. 使用微信官方签名校验工具:微信提供了在线签名校验工具(点击进入),将你拼接的参数串和密钥输入,对比生成的签名是否与你的一致,快速定位问题;
  2. 打印拼接后的签名串:在代码中打印sign_str(拼接后的 key=value&key=value&key = 密钥),手动核对是否符合规则;
  3. 核对参数完整性:统一下单必填参数:appidmch_idnonce_strbodyout_trade_nototal_feespbill_create_ipnotify_urltrade_type(JSAPI 需加openid),缺一不可。

总结

  1. 签名错误的核心是参数规则不匹配,需严格遵守:参数小写、空值过滤、ASCII 升序、UTF-8 编码、密钥正确;
  2. 优先使用微信官方校验工具核对签名,比手动排查更高效;
  3. 确保total_fee单位为分、参数无多余空格、sign字段最后添加且不参与自身签名。

JAVA实现

一、核心实现思路

Java 实现微信支付签名的核心逻辑与 Python 一致,需严格遵循:

  1. 过滤空值参数,仅保留有值的参数;
  2. 按参数名 ASCII 码升序排序;
  3. 拼接成 key=value&key=value 格式,最后拼接 &key=API密钥
  4. 使用 HMAC-SHA256(或 MD5)算法生成签名,结果转大写。

二、完整 Java 代码实现

前置条件
  1. 确保项目引入 commons-codec(用于加密),Maven 依赖如下:

xml

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.15</version>
</dependency>
  1. 准备好微信支付的 appidmch_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());
        }
    }
}

三、关键细节解释(解决签名错误的核心)

  1. 参数名大小写:所有参数名必须是小写(如 mch_id 而非 mchId/MCH_ID),这是最常见的错误点;
  2. 空值过滤:代码中通过 value != null && !value.trim().isEmpty() 过滤空值,微信要求空参数不参与签名;
  3. 排序规则:使用 Collections.sort(sortedKeys) 对参数名升序排序,严格遵循 ASCII 码顺序;
  4. 编码问题:所有字符串拼接和加密均使用 UTF-8StandardCharsets.UTF_8),避免中文乱码导致签名错误;
  5. 金额格式total_fee 必须是为单位的字符串(如 1 分对应 "1"),不能传小数(如 "0.01");
  6. 签名大写:加密结果通过 .toUpperCase() 转大写,符合微信支付的签名规范。

四、排查签名错误的额外技巧

  1. 打印拼接后的签名串:在 generateSign 方法中添加 System.out.println("签名拼接串:" + signStr.toString());,将拼接后的字符串复制到微信官方签名校验工具,对比生成的签名是否一致;
  2. 核对 API 密钥:确认使用的是微信商户平台(https://pay.weixin.qq.com)→ 账户中心 → API 安全 → API 密钥,而非公众号的 AppSecret;
  3. 参数完整性:统一下单必填参数(appid、mch_id、nonce_str、body、out_trade_no、total_fee、spbill_create_ip、notify_url、trade_type)缺一不可,JSAPI 支付还需 openid。

总结

  1. Java 实现微信支付签名的核心是参数小写、空值过滤、ASCII 升序、UTF-8 编码、签名大写
  2. 优先通过打印签名拼接串 + 官方校验工具定位签名错误,比逐行排查更高效;
  3. 确保 total_fee 单位为分、参数值无多余空格、API 密钥正确。

问题定位

1)先比对信息,看看有没有多输空格啊,大小写,关键词等。

2)如果是一个新的项目,那么就要去检测是否在线测试签名正确。

3)如果签名正确,说明程序基本上是没有问题,那么我们就要去查看设置,绑定的公众号等等。

问题解决

在线测试,签名一切正常,但是就是程序里报签名错误。

1)给的32位的API V2密码不正确。特别是客户有多个商户号的时候,就会出现这种情况。

2)商户号不正确,特别是有二个商户号或者多个商户号的时候,客户给了其中一样,然后商户号没有跟服务号对应。这种情况在线测试签名正确,然后就是在程序里报签名错误。

上面的二种情况,都是没有对应的,在线测试的地址,只是测试你给的参数进行处理后,是不是签名就是这个,如果是那么检测通过。但不会帮你测试你的商户号是否对应你的服务号,这种情况比较难查,只有通过让商户去扫码,登进去查看,突然会发现,商户号给错了。

如果商户号是对的,那么可以尝试重新设置一下API密钥。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值