收藏
回答

【已解决】代码没错,但仍然弹出”签名错误,请检查后再试“?

简述问题:前端后端都无问题,通过自带的例子尝试生成的签名值与程序生成的签名值一致。检查数据格式和内容没有任何非法字符,但仍然弹出签名错误的401回复。

后端JS:

// PerpayController.java
@RestController
@RequestMapping("/api/perpay")
public class PerpayController {

    @Autowired
    private PayService payService;

    private static final Logger log = LoggerFactory.getLogger(PayService.class);
    // POST
    @PostMapping
    public String handlePostRequest(@RequestBody PayRequest payRequest) throws Exception {
        log.info("Received POST request with body: " + payRequest);
        return payService.generateAuthorizationHeader("POST", "/v3/pay/transactions/jsapi", payRequest);
    }
}
// PayService.java
@Service
public class PayService {

    private static final Logger log = LoggerFactory.getLogger(PayService.class);
    // 生成Authorization头
    public String generateAuthorizationHeader(String method, String url, PayRequest payRequest) throws Exception {
        log.info("开始生成Authorization头, method: {}, url: {}", method, url);
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);  
        String nonceStr = SignatureUtil.generateRandomString();  
        log.info("生成的时间戳: {}", timestamp);
        log.info("生成的随机字符串: {}", nonceStr);
        String requestBody = (payRequest != null) ? payRequest.toString() : ""; // GET为空
        log.info("请求体: {}", requestBody);
        String signString = SignatureUtil.createSignString(method, url, timestamp, nonceStr, requestBody);
        String signature = SignatureUtil.signWithRSA(signString);  
        String authorizationHeader = SignatureUtil.createAuthorizationHeader(signature, timestamp, nonceStr);

        return authorizationHeader; 
    }
}
// SignatureUtil.java
public class SignatureUtil {
    private static final Logger log = LoggerFactory.getLogger(SignatureUtil.class);
    // 随机字符串
    public static String generateRandomString() {
        String randomString = UUID.randomUUID().toString().replace("-", "").toUpperCase();
        return randomString;
    }
    // 构造签名串
    public static String createSignString(String method, String url, String timestamp, String nonceStr, String requestBody) {
        String signString = method + "\n" +
                url + "\n" +
                timestamp + "\n" +
                nonceStr + "\n" +
                requestBody + "\n";
        return signString;
    }
    // RSA计算签名
    public static String signWithRSA(String data) throws Exception {
        log.info("RSA私钥计算签名, 计算数据: {}", data);
        PrivateKey privateKey = loadPrivateKey("/home/resource/16800000_20250120_cert/apiclient_key.pem");
        Signature signature = Signature.getInstance("SHA256withRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes());
        byte[] signedBytes = signature.sign();
        String base64Signature = Base64.getEncoder().encodeToString(signedBytes);
        log.info("计算的签名: {}", base64Signature);
        Files.write(Paths.get("/tmp/java_input.txt"), data.getBytes(StandardCharsets.UTF_8));
        return base64Signature; 
    }
    private static PrivateKey loadPrivateKey(String privateKeyPath) throws Exception {
        log.info("加载私钥, 路径: {}", privateKeyPath);
        Path path = Paths.get(privateKeyPath);
        byte[] keyBytes = Files.readAllBytes(path);
        String privateKeyPEM = new String(keyBytes)
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s", "");
        byte[] decoded = Base64.getDecoder().decode(privateKeyPEM);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decoded));
        log.info("私钥加载成功");
        return privateKey;
    }
    // 生成Authorization头
    public static String createAuthorizationHeader(String signature, String timestamp, String nonceStr) {
        String mchid = "168000000";
        String serialNo = "4850000000000000000000000000000000000";  // cert.pem序列号
        String authorizationHeader = "WECHATPAY2-SHA256-RSA2048 " +
                "mchid=\"" + mchid + "\"," + // 注意这里没有空格
                "nonce_str=\"" + nonceStr + "\"," + "signature=\"" + signature + "\"," +
                "timestamp=\"" + timestamp + "\"," + "serial_no=\"" + serialNo + "\"";
        log.info("Authorization:{}", authorizationHeader);
        return authorizationHeader;
    }
}

前端JS(我觉得应该没啥问题):

Page({
  data: {
    formData: {}
  },
  onLoad: function(options) {
    if (options.product) {
      const product = JSON.parse(options.product);
      this.setData({
        formData: {
          description: `NO.${product.id}-${product.name}`,
          out_trade_no: `${new Date().getFullYear()}${('0' + (new Date().getMonth() + 1)).slice(-2)}${('0' + new Date().getDate()).slice(-2)}${('0' + new Date().getHours()).slice(-2)}${('0' + new Date().getMinutes()).slice(-2)}${('0' + new Date().getSeconds()).slice(-2)}${('000000' + wx.getStorageSync('userid')).slice(-6)}${('000000' + product.id).slice(-6)}000000`, 
          // 由于代码只能显示UTC时间,所以原本+5分钟改成+485分钟,懒了之后再想办法修,先这么用
          time_expire: new Date(Date.now() + 490 * 60 * 1000).toISOString().replace('Z', '+08:00').split('.')[0] + '+08:00',
          attach: `${product.id}-${product.name}-${wx.getStorageSync('userid')}`,
          notify_url: 'https://www.qqsh.com.cn:8443/api/notify', 
          amount_total: product.price,
          amount_currency: 'CNY',
          payer_openid: wx.getStorageSync('openid'),
        }
      });
    }
  },
  createWeChatPayOrder: function (e) {
    const formData = e.detail.value; 
    const data = { 
      appid: 'wx********', 
      mchid: '16800000000', 
      description: formData.description, 
      out_trade_no: formData.out_trade_no, 
      time_expire: formData.time_expire, 
      attach: formData.attach, 
      notify_url: formData.notify_url, 
      amount: { 
        total: parseInt(formData.amount_total, 10) * 100, // 订单总金额(分)
        currency: formData.amount_currency 
      },
      payer: { 
        openid: formData.payer_openid 
      },
    };
    console.log('data', data);  
    wx.request({
      url: 'https://www.qqsh.com.cn:8443/api/perpay', // 获取授权信息的API
      method: 'POST',
      header: {
        'Content-Type': 'application/json',
        'Authorization': `${wx.getStorageSync('userToken')}` // 替换为实际的 token
      },
      data: JSON.stringify(data),
      success: (res) => {
        console.log('data:', JSON.stringify(data));
        console.log('Response from perpay API:', res.data);
        const authorizationHeader = `${res.data}`;  
        console.log('Authorization:', authorizationHeader);
        console.log('Form Data:', data);    
        // 微信支付请求
        wx.request({
          url: 'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi', // 设置请求URL
          method: 'POST',
          header: { 
            'Authorization': authorizationHeader, // 使用从 api/perpay 中获得的 Authorization 头
            'Accept': 'application/json', // 设置接受的数据类型
            'Content-Type': 'application/json' // 设置请求的数据类型
          },
          data: JSON.stringify(data), // 设置请求数据
          success: function (res) {
            // console.log('Header sent in the request:', {
            //   'Authorization': authorizationHeader, // 使用从 api/perpay 中获得的 Authorization 头
            //   'Accept': 'application/json', // 设置接受的数据类型
            //   'Content-Type': 'application/json' // 设置请求的数据类型
            // });
            const paymentData = res.data; // 获取返回的支付数据
            console.log('paymentData:', paymentData); // 打印支付数据
            // 获取timeStamp
            const timeStamp = `${Math.floor(new Date().getTime() / 1000)}`;
            console.log('时间戳:', timeStamp); 
          },
          fail: function(res) {
            console.error('支付订单创建失败', res);
          }
        });
      },
      fail: (err) => {
        console.error('请求perpay接口失败', err); // 打印请求perpay失败信息
      }
    });
    
  }
});

前端返回(片段):

paynew.js:300 data {appid: "wx****************", mchid: "1680000000", description: "NO.172-啤酒-扬子集团黑松露听装", out_trade_no: "20250205064658000001000172000000", time_expire: "2025-02-05T06:56:58+08:00", …}amount: {total: 100, currency: "CNY"}appid: "wx****************"attach: "172-啤酒-扬子集团黑松露听装-1"description: "NO.172-啤酒-扬子集团黑松露听装"mchid: "1680000000"notify_url: "https://www.qqsh.com.cn:8443/api/notify"out_trade_no: "20250205064658000001000172000000"payer: {openid: "oionx61-*******************"}time_expire: "2025-02-05T06:56:58+08:00"__proto__: Object
paynew.js:312 data: {"appid":"wx****************","mchid":"1680000000","description":"NO.172-啤酒-扬子集团黑松露听装","out_trade_no":"20250205064658000001000172000000","time_expire":"2025-02-05T06:56:58+08:00","attach":"172-啤酒-扬子集团黑松露听装-1","notify_url":"https://www.qqsh.com.cn:8443/api/notify","amount":{"total":100,"currency":"CNY"},"payer":{"openid":"oionx61-*******************"}}
paynew.js:313 Response from perpay API: WECHATPAY2-SHA256-RSA2048 mchid="1680000000",nonce_str="464516C9AFB848DC80B21063BD6DA53A",signature="pfC21Yj3eNOTFvw6eys0PsxCS5egYvlXcRRPiKieUTTHUOBFYAwrmmem6TB/AjuBvemjI3xTgGjy+PZkcvk/hJ6703zC+Kv9TntAsbPDcTGWZH9tSMWxu9x0cREBC/q3ZI5GwzG1R5Zw0Hr1VHLzFmqjGyRju2SQvQIPhBKW9mc9OaDqzG2vdwpWWvV66lbhOeqpcd+UzvTq3h6GIxhCpo5nZ+sIMYMb1S/1eRkqQUHhq4/VX9JM/xdFXpqi2eXRsmjQX+rHYudx3q8vrbzgHLfYsCwC+UGdWXc6pwalUz4428gN+2HKSluDF73KYi0/ScDmCTrj/tJ0dos+zQDPZw==",timestamp="1738709223",serial_no="485773B800000000000000000"
paynew.js:322 Generated Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1680000000",nonce_str="464516C9AFB848DC80B21063BD6DA53A",signature="pfC21Yj3eNOTFvw6eys0PsxCS5egYvlXcRRPiKieUTTHUOBFYAwrmmem6TB/AjuBvemjI3xTgGjy+PZkcvk/hJ6703zC+Kv9TntAsbPDcTGWZH9tSMWxu9x0cREBC/q3ZI5GwzG1R5Zw0Hr1VHLzFmqjGyRju2SQvQIPhBKW9mc9OaDqzG2vdwpWWvV66lbhOeqpcd+UzvTq3h6GIxhCpo5nZ+sIMYMb1S/1eRkqQUHhq4/VX9JM/xdFXpqi2eXRsmjQX+rHYudx3q8vrbzgHLfYsCwC+UGdWXc6pwalUz4428gN+2HKSluDF73KYi0/ScDmCTrj/tJ0dos+zQDPZw==",timestamp="1738709223",serial_no="485773B800000000000000000"
paynew.js:324 Form Data: {amount: {total: 100, currency: "CNY"},appid: "wx****************",attach: "172-啤酒-扬子集团黑松露听装-1",description: "NO.172-啤酒-扬子集团黑松露听装",mchid: "1680000000",notify_url: "https://www.qqsh.com.cn:8443/api/notify",out_trade_no: "20250205064658000001000172000000",payer: {openid: "oionx61-*******************"},time_expire: "2025-02-05T06:56:58+08:00",__proto__: Object}
paynew.js:347 支付数据: {code: "SIGN_ERROR", detail: {…}, message: "签名错误,请检查后再试"}
paynew.js:351 生成的时间戳: 1738709219

后端返回(片段):

com.miniprogram.service.PayService       : Received POST request with body: {"appid":"wx****************","mchid":"1680000000","description":"NO.172-啤酒-扬子集团黑松露听装","out_trade_no":"20250205064658000001000172000000","notify_url":"https://www.qqsh.com.cn:8443/api/notify","time_expire":"2025-02-05T06:56:58+08:00","attach":"172-啤酒-扬子集团黑松露听装-1","amount":{"total":100,"currency":"CNY"},"payer":{"openid":"oionx61-*******************"}}
com.miniprogram.service.PayService       : 开始生成Authorization头, method: POST, url: /v3/pay/transactions/jsapi
com.miniprogram.service.PayService       : 生成的时间戳: 1738709223
com.miniprogram.service.PayService       : 生成的随机字符串: 464516C9AFB848DC80B21063BD6DA53A
com.miniprogram.service.PayService       : 请求体: {"appid":"wx****************","mchid":"1680000000","description":"NO.172-啤酒-扬子集团黑松露听装","out_trade_no":"20250205064658000001000172000000","notify_url":"https://www.qqsh.com.cn:8443/api/notify","time_expire":"2025-02-05T06:56:58+08:00","attach":"172-啤酒-扬子集团黑松露听装-1","amount":{"total":100,"currency":"CNY"},"payer":{"openid":"oionx61-*******************"}}
com.miniprogram.util.SignatureUtil       : 开始使用RSA私钥计算签名, 计算数据: POST
/v3/pay/transactions/jsapi
1738709223
464516C9AFB848DC80B21063BD6DA53A
{"appid":"wx****************","mchid":"1680000000","description":"NO.172-啤酒-扬子集团黑松露听装","out_trade_no":"20250205064658000001000172000000","notify_url":"https://www.qqsh.com.cn:8443/api/notify","time_expire":"2025-02-05T06:56:58+08:00","attach":"172-啤酒-扬子集团黑松露听装-1","amount":{"total":100,"currency":"CNY"},"payer":{"openid":"oionx61-*******************"}}


com.miniprogram.util.SignatureUtil       : 加载私钥, 路径: /home/resource/1680000000_20250120_cert/apiclient_key.pem
com.miniprogram.util.SignatureUtil       : 私钥加载成功
com.miniprogram.util.SignatureUtil       : 计算的签名: pfC21Yj3eNOTFvw6eys0PsxCS5egYvlXcRRPiKieUTTHUOBFYAwrmmem6TB/AjuBvemjI3xTgGjy+PZkcvk/hJ6703zC+Kv9TntAsbPDcTGWZH9tSMWxu9x0cREBC/q3ZI5GwzG1R5Zw0Hr1VHLzFmqjGyRju2SQvQIPhBKW9mc9OaDqzG2vdwpWWvV66lbhOeqpcd+UzvTq3h6GIxhCpo5nZ+sIMYMb1S/1eRkqQUHhq4/VX9JM/xdFXpqi2eXRsmjQX+rHYudx3q8vrbzgHLfYsCwC+UGdWXc6pwalUz4428gN+2HKSluDF73KYi0/ScDmCTrj/tJ0dos+zQDPZw==
com.miniprogram.util.SignatureUtil       : Authorization:WECHATPAY2-SHA256-RSA2048 mchid="1680000000",nonce_str="464516C9AFB848DC80B21063BD6DA53A",signature="pfC21Yj3eNOTFvw6eys0PsxCS5egYvlXcRRPiKieUTTHUOBFYAwrmmem6TB/AjuBvemjI3xTgGjy+PZkcvk/hJ6703zC+Kv9TntAsbPDcTGWZH9tSMWxu9x0cREBC/q3ZI5GwzG1R5Zw0Hr1VHLzFmqjGyRju2SQvQIPhBKW9mc9OaDqzG2vdwpWWvV66lbhOeqpcd+UzvTq3h6GIxhCpo5nZ+sIMYMb1S/1eRkqQUHhq4/VX9JM/xdFXpqi2eXRsmjQX+rHYudx3q8vrbzgHLfYsCwC+UGdWXc6pwalUz4428gN+2HKSluDF73KYi0/ScDmCTrj/tJ0dos+zQDPZw==",timestamp="1738709223",serial_no="485773B800000000000000000"


前端打包数据没错,传入后端没错,极端签名没错,传回签名没错,发送签名没错,我实在是不知道哪里出了问题了,这问题我查了好几天都没查到,特来问各位大佬有没有懂得帮我望一下看一眼我哪里出问题了,谢谢各位!



===========================================

2025-02-07 08:30 更新

在多微信公众号的情况下,一定要检查【微信开发者工具】的AppID。简单复制再导入一份工程文件然后再赋值新的AppID是没有用的。

一定一定一定要检查AppID。

最后一次编辑于  17小时前
回答关注问题邀请回答
收藏

1 个回答

  • 支付社区运营
    支付社区运营
    1天前

    还请参照:https://pay.weixin.qq.com/doc/v3/merchant/4012365347 进行排查

    1天前
    有用 1
    回复 2
    • 周
      1天前
      您好,我回去排查了一下,签名时的私钥没问题,用的不是子商户的API私钥,用的是API私钥不是API证书或平台证书,商户API证书与商户API序列号一致,商户号与API证书匹配,随机字符串与时间戳字段在与计算签名时的值一致,格式没有问题,代码没有转义。
      我尝试重新求了一份签名值然后手动尝试执行post程序,结果一就是401签名错误。
      1天前
      回复
    • 周
      17小时前
      不用了,是我这边不小心
      17小时前
      回复
登录 后发表内容