简述问题:前端后端都无问题,通过自带的例子尝试生成的签名值与程序生成的签名值一致。检查数据格式和内容没有任何非法字符,但仍然弹出签名错误的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。
还请参照:https://pay.weixin.qq.com/doc/v3/merchant/4012365347 进行排查
我尝试重新求了一份签名值然后手动尝试执行post程序,结果一就是401签名错误。