之前项目用的微信支付都是v2版本的,然后新项目我尝试下v3的,然后碰到很多坑,记录一下
我用的是微信支付的一个sdk
首先引入pom
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.2.1</version>
</dependency>
先做下准备工作,就是证书,因为v3版本是必须要带证书的,这里面有个坑,搞了半天才解决。
这里需要两个证书,一个是商户的证书,还一个是微信平台的证书,开始的时候以为只是商户证书,一直报签名错误。商户证书怎么获取不用说,都知道,来说说微信平台的证书,这里是命令行下载平台证书。
Certificate Downloader 是 Java 微信支付 APIv3 平台证书的命令行下载工具。该工具可从 https://api.mch.weixin.qq.com/v3/certificates
接口获取商户可用证书,并使用 APIv3 密钥 和 AES_256_GCM 算法进行解密,并把解密后证书下载到指定位置。该工具已经通过 Maven 打包成 CertificateDownloader.jar,可在 release 中下载。
这里,必需参数有:
- 商户的私钥文件,即
-f
- 证书解密的密钥,即
-k
- 商户号,即
-m
- 保存证书的路径,即
-o
- 商户证书的序列号,即
-s
非必需参数有:
- 微信支付证书,用于验签,即
-c
完整命令如:
java -jar CertificateDownloader.jar -k ${apiV3key} -m ${mchId} -f ${mchPrivateKeyFilePath} -s ${mchSerialNo} -o ${outputFilePath} -c ${wechatpayCertificateFilePath}
获取完证书,接下来就简单多了,我这里只展示二维码付款的示例,其他都差不多了
初始化参数
private void init() {
//商户私钥
PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(
new ByteArrayInputStream(ResourceUtil.readBytes(wxpayPty.getMchKeyPath())));
//微信平台证书
X509Certificate wechatpayCertificate = PemUtil.loadCertificate(
new ByteArrayInputStream(ResourceUtil.readBytes(wxpayPty.getWxCertPath())));
ArrayList<X509Certificate> listCertificates = new ArrayList<>();
listCertificates.add(wechatpayCertificate);
httpClient = WechatPayHttpClientBuilder.create()
.withMerchant(wxpayPty.getMchId(), wxpayPty.getMchCertNo(), merchantPrivateKey)
.withWechatpay(listCertificates)
.build();
}
获取二维码
public byte[] ftf(String orderNo, String ip) throws IOException {
init();
JSONObject reqdata = new JSONObject();
reqdata.set("appid", wxpayPty.getAppId());
reqdata.set("mchid", wxpayPty.getMchId());
reqdata.set("description", "xx");
reqdata.set("out_trade_no", order.getOrderNo());
reqdata.set("notify_url", wxpayPty.getNotifyUrl());
JSONObject amount = new JSONObject();
// amount.set("total", order.getTotalPrice().multiply(new BigDecimal(100)).intValue());
amount.set("total", new BigDecimal(0.01).multiply(new BigDecimal(100)).intValue());
reqdata.set("amount", amount);
HttpPost httpPost = new HttpPost(wxpayPty.getNativeUrl());
// NOTE: 建议指定charset=utf-8。低于4.4.6版本的HttpCore,不能正确的设置字符集,可能导致签名错误
StringEntity reqEntity = new StringEntity(
reqdata.toString(), ContentType.create("application/json", "utf-8"));
httpPost.setEntity(reqEntity);
httpPost.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpPost);
byte[] code = null;
//response.getStatusLine().getStatusCode() != 401
try {
HttpEntity entity = response.getEntity();
// do something useful with the response body
JSONObject resp = JSONUtil.parseObj(EntityUtils.toString(entity));
if (resp.get("code_url") != null) {
//业务处理
//生成二维码
code = QrCodeUtil.generatePng(resp.getStr("code_url"), QrConfig.create().setImg("static/image/log.jpg"));
}
// and ensure it is fully consumed
EntityUtils.consume(entity);
} finally {
response.close();
}
return code;
}
支付回调
public int notifyV3(HttpServletRequest request) throws IOException, GeneralSecurityException {
log.info("微信回调开始");
JSONObject result = getResource(request, "trade");
String orderNo = result.getStr("out_trade_no");
if ("SUCCESS".equals(result.getStr("code"))) {
// 业务处理
}
return 0;
}
获取回调结果,方法里面的type,是想代码重用,所以加了这个字段,就是根据type获取不同的值,看明白
private JSONObject getResource(HttpServletRequest request, String type) throws IOException, GeneralSecurityException {
JSONObject result = new JSONObject();
String respStr = RequestUtil.getRequestJsonString(request);
JSONObject resp = JSONUtil.parseObj(respStr);
log.info("微信回调:"+respStr);
if ("TRANSACTION.SUCCESS".equals(resp.getStr("event_type")) || "REFUND.SUCCESS".equals(resp.getStr("event_type"))) {
AesUtil aesUtil = new AesUtil(wxpayPty.getV3Key().getBytes("utf-8"));
JSONObject resource = resp.getJSONObject("resource");
String oriResourceStr = aesUtil.decryptToString(resource.getStr("associated_data").getBytes(), resource.getStr("nonce").getBytes(),
resource.getStr("ciphertext"));
JSONObject oriResource = JSONUtil.parseObj(oriResourceStr);
log.info("微信回调resource: "+oriResourceStr);
String status = oriResource.getStr(type+"_state");
if ("SUCCESS".equals(status)) {
result.set("code","SUCCESS");
String no = oriResource.getStr("out_"+type+"_no");
result.set("out_"+type+"_no",no);
log.info("回调状态" + no + "成功");
return result;
}
}
log.info("回调状态失败");
result.set("code","FAIL");
return result;
}
申请退款
public void refund(Long refundId) throws IOException {
init();
JSONObject reqdata = new JSONObject();
reqdata.set("out_trade_no", refund.getOrderNo());
reqdata.set("out_refund_no", refund.getRefundNo());
reqdata.set("notify_url", wxpayPty.getRefundNotifyUrl());
JSONObject amount = new JSONObject();
amount.set("refund", refund.getRefundAmount().multiply(new BigDecimal(100)).intValue());
amount.set("amount", refund.getAmount().intValue()*100);
amount.set("currency", "CNY");
reqdata.set("amount", amount);
HttpPost httpPost = new HttpPost(wxpayPty.getRefundUrl());
StringEntity reqEntity = new StringEntity(
reqdata.toString(), ContentType.create("application/json", "utf-8"));
httpPost.setEntity(reqEntity);
httpPost.addHeader("Accept", "application/json");
CloseableHttpResponse response = httpClient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
JSONObject resp = JSONUtil.parseObj(EntityUtils.toString(entity));
if ("SUCCESS".equals(resp.get("status")) || "PROCESSING".equals(resp.get("status"))) {
//业务处理
}
EntityUtils.consume(entity);
} finally {
response.close();
}
}
退款回调
public int refundNotify(HttpServletRequest request) throws IOException, GeneralSecurityException {
log.info("微信退款回调开始");
JSONObject result = getResource(request, "refund");
if ("SUCCESS".equals(result.getStr("code"))) {
log.error("退款:" + refundNo + "成功");
// 业务处理
} else {
// 业务处理
}
return 0;
}
到此就差不多了,这里我用到了几个工具类,都是hutool里的,想用的话可以引进来