微信支付V3/签名验证失败

78 阅读3分钟

作者遇到的一些问题,分享。

image.png

发起预支付的时候时间戳和随机字符串(32位以内)要和后续返回前端时paySin加密签名保持一致,v3只支持RSA。

其他问题导致签名验证失败,可以使用官方签名工具进行验签。

image.png

使用工具时要注意换行符n/否则会展示验签失败 (或使用官方文档示例以及test.pem生成) 如果和官方文档所示一致,则证明加密过程没有问题,为加密时所使用的参数问题。

其余注意,引入包要确认与相关程序一致(partnerpayments包为子商户(特约商户 / 二级商户)payments包为直连商户等),如app,小程序,公众号,所使用的包不一致。加密参数也不一致。

小程序为

"package":"prepay_id=wx00000000000000000000000000",

具体对应官方文档。

微信沙箱问题

93af5076e8cebd95c8a98314981cd5d9.png

相关技术论坛说v3有沙箱。不确定。

作者直接申请金额测试,不想研究沙箱了。

推荐方式。(无需关注加密等情况。)


// 调用prepayWithRequestPayment方法,直接返回包含调起支付所需的所有参数
com.wechat.pay.java.service.payments.jsapi.model.PrepayWithRequestPaymentResponse response = 
    service.prepayWithRequestPayment(request);

// 从response中获取SDK生成的所有参数(包含timeStamp和nonceStr)
String timeStamp = response.getTimeStamp();
String nonceStr = response.getNonceStr();
String packageVal = response.getPackageVal();
String signType = response.getSignType();
String paySign = response.getPaySign();  // SDK已经完成签名,无需二次签名

加密具体为

/**
 * 构造微信支付V3签名串
 * @param appId 应用ID
 * @param timeStamp 10位秒级时间戳
 * @param nonceStr 随机字符串(建议32位)
 * @param prepayId 预支付交易会话标识
 * @return 符合要求的签名串
 */
public static String buildSignStr(String appId, String timeStamp, String nonceStr, String prepayId) {
    // 校验核心参数
    ...略过

    // 严格按格式拼接(末尾保留\n)
    String signStr = String.format("%s\n%s\n%s\nprepay_id=%s\n",
            appId.trim(), timeStamp.trim(), nonceStr.trim(), prepayId.trim());
    log.info("构造完成的签名串:\n{}", signStr);
    return signStr;
}

/**
 * 生成支付签名(满足SHA256withRSA加密条件)
 * @param signStr 待签名串
 * @param privateKeyPath 商户私钥文件绝对路径(PKCS#8格式)
 * @return Base64编码后的签名
 * @throws Exception 签名过程异常
 */
public static String generatePaySign(String signStr, String privateKeyPath) throws Exception {
    // 1. 读取并清理私钥(核心:兼容PKCS#8格式)
    String privateKeyContent = new String(Files.readAllBytes(Paths.get(privateKeyPath)), StandardCharsets.UTF_8);
    String cleanPrivateKey = cleanPrivateKey(privateKeyContent);
    log.info("清理后私钥长度:{}", cleanPrivateKey.length());

    // 2. 校验私钥格式(Base64合法性)
    validateBase64(cleanPrivateKey);

    // 3. 解析私钥(PKCS#8格式)
    PrivateKey rsaPrivateKey = getPrivateKey(cleanPrivateKey);

    // 4. 执行SHA256withRSA签名
    byte[] signBytes = signWithSHA256RSA(signStr, rsaPrivateKey);

    // 5. Base64编码返回
    String paySign = Base64.getEncoder().encodeToString(signBytes);
    log.info("生成的支付签名:{}", paySign);
    return paySign;
}

/**
 * 清理私钥字符串(移除标识头、尾、空白字符)
 */
private static String cleanPrivateKey(String privateKeyContent) {
    return privateKeyContent
            .replace("-----BEGIN PRIVATE KEY-----", "")
            .replace("-----END PRIVATE KEY-----", "")
            .replaceAll("\r\n", "")
            .replaceAll("\n", "")
            .replaceAll("\s+", "");
}

/**
 * 清理私钥字符串(移除标识头、尾、空白字符)
 */
private static String cleanPublicKey(String publicKeyContent) {
    return publicKeyContent
            .replace("-----BEGIN PUBLIC KEY-----", "")
            .replace("-----END PUBLIC KEY-----", "")
            .replaceAll("\r\n", "")
            .replaceAll("\n", "")
            .replaceAll("\s+", "");
}



/**
 * 执行SHA256withRSA签名
 */
private static byte[] signWithSHA256RSA(String signStr, PrivateKey privateKey) throws Exception {
    Signature signature = Signature.getInstance(SIGN_ALGORITHM);
    signature.initSign(privateKey);
    // 必须使用UTF-8编码,微信支付固定要求
    signature.update(signStr.getBytes(StandardCharsets.UTF_8));
    byte[] signBytes = signature.sign();
    log.info("SHA256withRSA签名完成,签名字节长度:{}", signBytes.length);
    return signBytes;
}

具体使用maven依赖为

<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-java</artifactId>
    <version>0.2.14</version>
</dependency>

题外话,感觉对接微信支付要比支付宝要麻烦一些(起码支付宝沙箱环境很明确)。官方微信文档v2/v3比较穿插。

注:本文只是接入v3注意事项。