微信JSAPI支付踩坑记录

目录

1.报如下异常。

2.下单成功后签名验证失败。

2.1 关于Base64编码

2.2 计算签名值

2.3 paySign生成规则

3. 实现代码

3.1 创建订单

3.2 生成paySign签名


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;
        }
    }
   

}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值