申请退款
SDK
调用该https://api.mch.weixin.qq.com/secapi/pay/refund
接口需要应程序安装API证书才可以,否则会提示以下错误:
<head><title>400 No required SSL certificate was senttitle>head>
<body bgcolor="white">
<center><h1>400 Bad Requesth1>center>
<center>No required SSL certificate was sentcenter>
<hr><center>nginxcenter>
body>
html>
根据官方文档中介绍的证书下载及安装方式,由于本人是Windows环境下进行开发,所以按照它的介绍,下载下来apiclient_cert.p12证书之后,直接双击安装即可。但安装成功之后,请求还是报错。没办法,调试代码吧。具体调试过程如下:
第一步:接受客户端发送的退款请求
// 业务处理步骤省略……
// 发起微信退款
WXPay wxPay = new WXPay(wxPayConfig); // WXPay为微信官方提供的SDK所在类,官方SDK为com.github.wxpay.sdk,链接已在上面给出,需要的童鞋可以去下载。
Map<String, String> res = wxPay.refund(redundData); // redundData为退款申请数据
第二步:调试wxPay.refund方法,发现会走到这个方法requestWithCert
public Map<String, String> refund(Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String url;
if (this.useSandbox) {
url = "/sandboxnew/secapi/pay/refund";
} else {
url = "/secapi/pay/refund";
}
String respXml = this.requestWithCert(url, this.fillRequestData(reqData), connectTimeoutMs, readTimeoutMs);
return this.processResponseXml(respXml);
}
public String requestWithCert(String urlSuffix, Map<String, String> reqData, int connectTimeoutMs, int readTimeoutMs) throws Exception {
String msgUUID = (String)reqData.get("nonce_str");
String reqBody = WXPayUtil.mapToXml(reqData);
String resp = this.wxPayRequest.requestWithCert(urlSuffix, msgUUID, reqBody, connectTimeoutMs, readTimeoutMs, this.autoReport);
return resp;
}
第三步:调试wxPayRequest.requestWithCert
方法
public String requestWithCert(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean autoReport) throws Exception {
return this.request(urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, true, autoReport);
}
上面方法又调用了自己的request
方法
private String request(String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert, boolean autoReport) throws Exception {
Exception exception = null;
long elapsedTimeMillis = 0L;
long startTimestampMs = WXPayUtil.getCurrentTimestampMs();
boolean firstHasDnsErr = false;
boolean firstHasConnectTimeout = false;
boolean firstHasReadTimeout = false;
DomainInfo domainInfo = this.config.getWXPayDomain().getDomain(this.config);
if (domainInfo == null) {
throw new Exception("WXPayConfig.getWXPayDomain().getDomain() is empty or null");
} else {
try {
String result = this.requestOnce(domainInfo.domain, urlSuffix, uuid, data, connectTimeoutMs, readTimeoutMs, useCert);
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
this.config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, (Exception)null);
WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
return result;
} catch (UnknownHostException var18) {
exception = var18;
firstHasDnsErr = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
WXPayUtil.getLogger().warn("UnknownHostException for domainInfo {}", domainInfo);
WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
} catch (ConnectTimeoutException var19) {
exception = var19;
firstHasConnectTimeout = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
WXPayUtil.getLogger().warn("connect timeout happened for domainInfo {}", domainInfo);
WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
} catch (SocketTimeoutException var20) {
exception = var20;
firstHasReadTimeout = true;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
WXPayUtil.getLogger().warn("timeout happened for domainInfo {}", domainInfo);
WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
} catch (Exception var21) {
exception = var21;
elapsedTimeMillis = WXPayUtil.getCurrentTimestampMs() - startTimestampMs;
WXPayReport.getInstance(this.config).report(uuid, elapsedTimeMillis, domainInfo.domain, domainInfo.primaryDomain, connectTimeoutMs, readTimeoutMs, firstHasDnsErr, firstHasConnectTimeout, firstHasReadTimeout);
}
this.config.getWXPayDomain().report(domainInfo.domain, elapsedTimeMillis, (Exception)exception);
throw (Exception)exception;
}
}
上面方法又调用了同类的requestOnce
方法
private String requestOnce(String domain, String urlSuffix, String uuid, String data, int connectTimeoutMs, int readTimeoutMs, boolean useCert) throws Exception {
BasicHttpClientConnectionManager connManager;
if (useCert) {
char[] password = this.config.getMchID().toCharArray();
InputStream certStream = this.config.getCertStream();
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(certStream, password);
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), (TrustManager[])null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1"}, (String[])null, new DefaultHostnameVerifier());
connManager = new BasicHttpClientConnectionManager(RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", sslConnectionSocketFactory).build(), (HttpConnectionFactory)null, (SchemePortResolver)null, (DnsResolver)null);
} else {
connManager = new BasicHttpClientConnectionManager(RegistryBuilder.create().register("http", PlainConnectionSocketFactory.getSocketFactory()).register("https", SSLConnectionSocketFactory.getSocketFactory()).build(), (HttpConnectionFactory)null, (SchemePortResolver)null, (DnsResolver)null);
}
HttpClient httpClient = HttpClientBuilder.create().setConnectionManager(connManager).build();
String url = "https://" + domain + urlSuffix;
HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs).setConnectTimeout(connectTimeoutMs).build();
httpPost.setConfig(requestConfig);
StringEntity postEntity = new StringEntity(data, "UTF-8");
httpPost.addHeader("Content-Type", "text/xml");
httpPost.addHeader("User-Agent", WXPayConstants.USER_AGENT + " " + this.config.getMchID());
httpPost.setEntity(postEntity);
HttpResponse httpResponse = httpClient.execute(httpPost);
HttpEntity httpEntity = httpResponse.getEntity();
return EntityUtils.toString(httpEntity, "UTF-8");
}
可以发现,证书最终是通过该方法进行导入的。具体导入是由WXPayConfig类进行操作的,该类是在创建WXPay对象时传入的,一般我们都会根据自己的业务场景写一个该类的子类,由于在重写该类方法时,getCertStream方法直接返回了null,所以即使证书安装了,也会提示证书无法找到的错误。
问题解决:
将下载好的证书放入工程目录下,注意该证书文件应放在有访问权限控制的目录中,防止被他人下载。
重写WXPayConfig的getCertStream方法
private byte[] certData;
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return null;
}
public MyWXPayConfig() throws Exception {
URI uri = this.getClass().getClassLoader().getResource("cert/apiclient_cert.p12").toURI();
String certPath =uri.getPath();
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}