目录
1.报如下异常。
java.lang.IllegalArgumentException: java.security.InvalidKeyException: Illegal key size
at com.wechat.pay.contrib.apache.httpclient.util.AesUtil.decryptToString(AesUtil.java:42)
at com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier.deserializeToCerts(AutoUpdateCertificatesVerifier.java:138)
at com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier.autoUpdateCert(AutoUpdateCertificatesVerifier.java:108)
at com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier.<init>(AutoUpdateCertificatesVerifier.java:62)
at com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier.<init>(AutoUpdateCertificatesVerifier.java:52)
解决方案参考:微信 AES 解密报错 Illegal key size 三种解决办法
我是直接将jdk1.8.0_60 升级到 jdk1.8.0_161,更新环境变量,该异常修复。
2.下单成功后签名验证失败。
2.1 关于Base64编码
根据RFC822规定,BASE64Encoder编码每76个字符,还需要加上一个回车换行
部分Base64编码的java库还按照这个标准实行。实际上是不要加换行符的。
比如:
sun.misc.BASE64Encoder bASE64Encoder = new BASE64Encoder();
bASE64Encoder.encode(bytes);
使用上面代码,会导致base64编码后出现了换行符。而最新标准应该是不包含换行符的。
如:
//需要引入commons-codec-1.15.jar
org.apache.commons.codec.binary.Base64.encodeBase64String(bytes);
//使用jdk8自带的Base64编码
java.util.Base64.Encoder encode1 = java.util.Base64.getEncoder();
encode1.encodeToString(signature.sign());
使用上面2种方式base64编码后不会出现了换行符。
2.2 计算签名值
开发指引上是这这么说的:
绝大多数编程语言提供的签名函数支持对签名数据 进行签名。
强烈建议商户调用该类函数,使用商户私钥对待签名串进行SHA256 with RSA签名,
并对签名结果进行Base64编码得到签名值。
然后,我后台用的java,找了好久没有发现关于微信支付SHA256 with RSA签名的代码例子。
找了些RSA签名的,自己研究,又不知道对不对,还有base64上面换行的影响,导致不确定在获取paySign签名时写的对不对。反正就是不停的调试,开发指引中要是有个类似3.2.1. 【服务端】JSAPI下单的例子就好了。直接拷贝代码生成paySign。为了生成paySign头的大了。好在最后终于试出来了。后面会贴上代码。
2.3 paySign生成规则
在paySign生成规则页面中构造字符串,是如下描述的:
签名串一共有四行,每一行为一个参数。
行尾以\n(换行符,ASCII编码值为0x0A)结束,包括最后一行。
如果参数本身以\n结束,也需要附加一个\n
但是我在构造字符串时,最后一行没有加上换行符,导致总是提示支付验证失败。这个坑也浪费我很多时间。
3. 实现代码
3.1 创建订单
package com.xpl.wx.util;
import java.io.*;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Date;
import java.util.List;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import com.xpl.util.DateUtils;
import com.xpl.util.FileUtils;
import com.xpl.util.JsonUtil;
import com.xpl.util.PathUtil;
import com.xpl.util.ResponseMap;
import com.xpl.util.SignUtil;
/***
* 演示wechatpay-apache-httpclient.jar包使用。
* @author xpl
* @createTime 2021-08-26
*
*/
public class WxJsapiUtil {
private static final Logger logger = Logger.getLogger("");
private static final WxJsapiUtil demo = new WxJsapiUtil();
/**商户号*/
String mchId;
/**商户证书序列号*/
String mchSerialNo;
/**V3密钥*/
String apiV3Key;
/**公众号appid*/
String appid;
/**商户私匙*/
String priKeyPath;
/**支付回调通知URL,该地址必须为直接可访问的URL,不允许携带查询串*/
String notify_url;
public WxJsapiUtil(){
mchId = "********";
mchSerialNo = "********";
apiV3Key = "********";
appid = "********";
priKeyPath = "********\\apiclient_key.pem";
notify_url = "********";
}
public WxJsapiUtil(String mchId, String mchSerialNo, String apiV3Key, String appid, String priKeyPath){
}
public HttpClient createHttpClient(){
String privateKey;
HttpClient httpClient = null;
try {
privateKey = FileUtils.readFile(priKeyPath);
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
List<X509Certificate> wechatpayCertificates = null;
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withWechatpay(wechatpayCertificates);
// ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
// 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签
httpClient = builder.build();
} catch (IOException e) {
e.printStackTrace();
}
return httpClient;
// 后面跟使用Apache HttpClient一样
// HttpResponse response = httpClient.execute(...);
}
/**
* 创建加载商户私钥、加载平台证书、初始化httpClient的通用方法
*/
public CloseableHttpClient createHttpClientV3() {
CloseableHttpClient httpClient = null;
// 加载商户私钥(privateKey:私钥字符串)
String privateKey;
try {
privateKey = FileUtils.readFile(priKeyPath);
PrivateKey merchantPrivateKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
// 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
// 初始化httpClient
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(mchId, mchSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier)).build();
// CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create()
// .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
// .withValidator(response -> true) // NOTE: 设置一个空的应答签名验证器,**不要**用在业务请求
// .build();
} catch (IOException e) {
e.printStackTrace();
}
return httpClient;
}
/***
* 【服务端】JSAPI下单
* @param rm
* @param openid
* @param total
* @param description
* @param bizId
* @return
*/
public String createOrder(ResponseMap rm, String openid, int total, String description, String bizId) {
String prepay_id = null;
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
// 请求body参数
String reqdata = "{"
+ "\"amount\": {"
+ "\"total\": "+ total +","
+ "\"currency\": \"CNY\""
+ "},"
+ "\"mchid\": \""+ mchId +"\","
+ "\"description\": \""+description+"\","
+ "\"notify_url\": \""+notify_url+"\","
+ "\"payer\": {"
+ "\"openid\": \""+openid+"\"" + "},"
+ "\"out_trade_no\": \""+bizId+"\","
+ "\"goods_tag\": \"\","
+ "\"appid\": \""+ appid +"\"" + "}";
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");
CloseableHttpClient httpClient = createHttpClientV3();
//完成签名并执行请求
CloseableHttpResponse response = null;
try {
response = httpClient.execute(httpPost);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
String res = EntityUtils.toString(response.getEntity());
prepay_id = (String) JsonUtil.toMap(res).get("prepay_id");
logger.info("下单成功!return body = " + res);
} else if (statusCode == 204) {
logger.info("下单成功!statusCode == 204 " );
} else {
String errMsg = "failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity());
logger.error(errMsg);
logger.error("openid = " + openid);
rm.addErrMsg(errMsg);
}
} catch (IOException e) {
logger.error("下单失败!", e);
rm.addErrMsg("下单失败!" + e.getMessage());
} finally {
close(response);
close(httpClient);
}
return prepay_id;
}
/**
* 获取签名
* @param rm
* @param timestamp
* @param nonceStr
* @param prepay_id
* @return
*/
public String getPaySign(ResponseMap rm, String timestamp, String nonceStr, String prepay_id) {
String source = appid + "\n";
source += timestamp + "\n";
source += nonceStr + "\n";
source += "prepay_id=" + prepay_id + "\n";
String paySign = "";
try {
// 加载商户私钥(privateKey:私钥字符串)
String privateKey = FileUtils.readFile(priKeyPath);
paySign = SignUtil.signBySHA256WithRSA(source, privateKey, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return paySign;
}
public String getAppid(){
return appid;
}
public static void close(Closeable obj){
if(obj != null){
try {
obj.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.2 生成paySign签名
package com.xpl.util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Date;
import org.apache.commons.lang3.StringUtils;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
/***
* 签名助手
* @author xpl
* @createTime 2021-09-09
*
*/
public class SignUtil {
/**
* SHA256withRSA签名
* @author xpl
* @param content
* @param privateKey
* @param charset
* @return
*/
public static String signBySHA256WithRSA(String content, String privateKey, String charset){
if(StringUtils.isBlank(privateKey)){
//缺少签名私钥
return null;
}
try {
PrivateKey priKey = PemUtil
.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes(charset)));
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(priKey);
signature.update(content.getBytes(charset));
return org.apache.commons.codec.binary.Base64.encodeBase64String(signature.sign());
} catch (Exception e) {
//签名失败
return null;
}
}
}