记一次接入微信支付中服务端开发所踩得坑
因为业务需要,项目中需要对接微信支付,在此记录下在接入微信支付中所踩得坑
首先吐槽一波,讲道理微信支付的开发文档确实不如支付宝的文档整理的好,封装也确实要差点,所以开发中踩了不少坑。也可能是本人比较菜 @_@
废话不多说,详细的文档请移步链接: 微信支付开发者文档
新版本的微信支付已经从原来的版本升级为V3,请求从原来的xml格式转为了json格式。网上很多的教程和文章大部分都是老版本的微信支付,但是差别不是太大。
下面正式开始:
和老版本一样还是要申请商户号,详细流程不必细说,按照官方文档上的步骤一步步来就好了。
商户号等等乱七八糟的申请完后我们就可以拿到商户证书,除了商户证书我们还需要自己设置一个V3秘钥,这个也是在官网自己设置的32位字符串
主要参数
参数 | 描述 |
---|---|
AppID | 手机程序的appid,这个找安卓开发去要 |
privateKeyPath | 私钥的存储地址 |
merchantID | 商户号,通过注册商户号后拿到的证书解析获得 |
merchantSerialNO | 商户序列号,通过注册商户号后拿到的证书解析获得 |
V3Key | 自己设置的V3Key |
notifyURL | 执行后的异步回调地址 |
秉着能用封装过的jdk就不自己造轮子的理念,我们先下为敬 开发工具传送门
虽然已经有了官方帮忙封装后的工具类,在使用过程中我还是进行了简单的封装,方便开发中使用
注意:在加载私钥时,给的是一个inputSteam流,所以我们要设置一个秘钥文件的地址,用于获取、解析秘钥,当然中间这些繁琐的步骤微信提供的jdk已经帮我们封装好了,包括解析私钥,请求平台证书,解析平台证书、签名、验签
package org.jeecg.modules.kzw.pay.factory;
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 lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.jeecg.modules.kzw.constant.PayConstant;
import java.io.*;
import java.security.PrivateKey;
/**
* 微信支付工具类<br>
* 微信整个支付流程统一由微信管理<br>
* 我们只需要配置好httpClient,每次获取Client发起对应的请求就好了<br>
*/
@Slf4j
public class WxPayHttpClientFactory {
//商户id
private String merchantId;
//商户证书序列号
private String merchantSerialNo;
//V3秘钥
private String apiV3Key;
public WxPayHttpClientFactory() {
initFactory(PayConstant.WX_MERCHANT_ID, PayConstant.WX_MERCHANT_SERIAL_NO, PayConstant.WX_V3_PRIVATE_KEY);
}
/**
* 初始化工厂类参数
*
* @param merchantId 商户id
* @param merchantSerialNo 商户证书序列号
* @param apiV3Key V3秘钥
*/
private void initFactory(String merchantId, String merchantSerialNo, String apiV3Key) {
this.merchantId = merchantId;
this.merchantSerialNo = merchantSerialNo;
this.apiV3Key = apiV3Key;
}
/**
* 获取微信支付httpClient
*
* @return httpClient
*/
public CloseableHttpClient getHttpClient() {
return initHttpClient();
}
/**
* 初始化客户端
*
* @return httpClient
*/
public CloseableHttpClient initHttpClient() {
CloseableHttpClient httpClient = null;
try {
// 加载商户私钥(privateKey:私钥字符串)
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(PayConstant.WX_PRIVATE_KEY_PATH));
// 加载平台证书(merchantId:商户号,merchantSerialNo:商户证书序列号,apiV3Key:V3秘钥)
AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNo, merchantPrivateKey)), apiV3Key.getBytes("utf-8"));
//httpClient构造器,可以继续通过builder构造其他参数
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(merchantId, merchantSerialNo, merchantPrivateKey)
.withValidator(new WechatPay2Validator(verifier));
// .withValidator(response -> true);
// 初始化httpClient
httpClient = builder.build();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
log.error("初始化微信支付httpClient失败!");
} catch (FileNotFoundException e) {
e.printStackTrace();
log.error("加载秘钥文件失败!请检查秘钥文件是否存在!");
}
return httpClient;
}
}
经过封装后,只用在使用到的地方通过这个工厂类获取httpClient就好了
注意:每一个请求都要单独创建一个client,不可重复使用!
这只是拿到了httpclient,我又简单封装了一个WxPayUtil类,方便开发中直接调用相关接口
就拿关闭订单来说吧
/**
* 关闭订单
*
* @param orderId 订单id
* @return 成功[true],失败[false]
*/
public static boolean appCloseOrder(String orderId) {
CloseableHttpClient httpClient = factory.getHttpClient();
HttpPost post = new HttpPost(PayConstant.getWxAppCloseOrderUrl(orderId));
boolean result = false;
//封装传递参数
JSONObject requestJson = new JSONObject();
requestJson.put("mchid", PayConstant.WX_MERCHANT_ID); //商户号
StringEntity entity = new StringEntity(requestJson.toString(), "UTF-8");
//设置请求参数和请求头
entity.setContentType("application/json");
entity.setContentEncoding("UTF-8");
setHeaders(post);
post.setEntity(entity);
try {
CloseableHttpResponse response = httpClient.execute(post);
if (isSuccess(response)) {
result = true;
}
} catch (Exception e) {
e.printStackTrace();
log.error("微信支付-关闭下单-失败!");
} finally {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
自己定义的一个和支付相关的常量类,用于存储支付请求的接口和参数,因为部分接口是RestFul类型,所以会用到地址拼接
微信支付接口文档
如:https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}/close
所以我们采用了下面这种方式
/**
* app关闭订单接口地址 POST方式
*
* @param outTradeNo 商户订单号
* @return app关闭订单接口地址
*/
public static URI getWxAppCloseOrderUrl(String outTradeNo) {
return getUri(WX_APP_CLOSE_ORDER_URL + outTradeNo + "/close");
}
注意:因为new HttpPost()的时候里面的参数要么是String类型要么是URI类型,但是为了避免get请求参数使用拼接的方式,所以写了一个String转URI的方法
通过使用URL将字符串间接转为URI的方法
/**
* 将地址变成URI形式
*
* @param path 请求地址
* @return uri
*/
private static URI getUri(String path) {
URI uri = null;
try {
URL url = new URL(path);
uri = new URI(url.getProtocol(), url.getHost(), url.getPath(), url.getQuery(), null);
} catch (MalformedURLException | URISyntaxException e) {
e.printStackTrace();
}
return uri;
}
因为请求都是json格式,所以在每个请求都要设置header信息
为了省事,又封装了个方法 -_-
/**
* 设置请求头 设置Content-Type 和 Accept 为 application/json 格式
*
* @param httpRequest 请求
*/
private static void setHeaders(HttpRequestBase httpRequest) {
httpRequest.setHeader("Content-Type", "application/json;utf-8");
httpRequest.setHeader("Accept", "application/json");
}
到这里大概就已经完成了微信支付的一个简单封装。
整体的一个流程还是比较复杂的,但是还是要硬着头皮去仔细研究。