@@ -225,6 +225,13 @@ | |||
<version>${jetty.version}</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.assertj</groupId> | |||
<artifactId>assertj-guava</artifactId> | |||
<version>3.0.0</version> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>redis.clients</groupId> | |||
<artifactId>jedis</artifactId> | |||
@@ -27,7 +27,6 @@ | |||
<dependency> | |||
<groupId>org.jodd</groupId> | |||
<artifactId>jodd-http</artifactId> | |||
<scope>provided</scope> | |||
</dependency> | |||
<dependency> | |||
@@ -45,6 +44,11 @@ | |||
<artifactId>testng</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.assertj</groupId> | |||
<artifactId>assertj-guava</artifactId> | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>com.google.inject</groupId> | |||
<artifactId>guice</artifactId> | |||
@@ -36,7 +36,17 @@ public class WxPayConstants { | |||
} | |||
/** | |||
* 订单类型 | |||
* 压缩账单的类型 | |||
*/ | |||
public static class TarType { | |||
/** | |||
* 固定值:GZIP,返回格式为.gzip的压缩包账单 | |||
*/ | |||
public static final String GZIP = "GZIP"; | |||
} | |||
/** | |||
* 账单类型 | |||
*/ | |||
public static class BillType { | |||
/** | |||
@@ -11,21 +11,47 @@ import com.github.binarywang.wxpay.bean.order.WxPayNativeOrderResult; | |||
import com.github.binarywang.wxpay.bean.request.*; | |||
import com.github.binarywang.wxpay.bean.result.*; | |||
import com.github.binarywang.wxpay.config.WxPayConfig; | |||
import com.github.binarywang.wxpay.constant.WxPayConstants; | |||
import com.github.binarywang.wxpay.constant.WxPayConstants.BillType; | |||
import com.github.binarywang.wxpay.constant.WxPayConstants.SignType; | |||
import com.github.binarywang.wxpay.constant.WxPayConstants.TradeType; | |||
import com.github.binarywang.wxpay.exception.WxPayException; | |||
import com.github.binarywang.wxpay.service.WxPayService; | |||
import com.github.binarywang.wxpay.util.SignUtils; | |||
import com.google.common.base.Joiner; | |||
import com.google.common.collect.Maps; | |||
import jodd.io.ZipUtil; | |||
import jodd.util.Base64; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.http.auth.AuthScope; | |||
import org.apache.http.auth.UsernamePasswordCredentials; | |||
import org.apache.http.client.CredentialsProvider; | |||
import org.apache.http.client.config.RequestConfig; | |||
import org.apache.http.client.methods.CloseableHttpResponse; | |||
import org.apache.http.client.methods.HttpPost; | |||
import org.apache.http.conn.ssl.DefaultHostnameVerifier; | |||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; | |||
import org.apache.http.entity.StringEntity; | |||
import org.apache.http.impl.client.BasicCredentialsProvider; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import org.apache.http.impl.client.HttpClientBuilder; | |||
import org.apache.http.impl.client.HttpClients; | |||
import org.apache.http.util.EntityUtils; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
import javax.net.ssl.SSLContext; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.charset.StandardCharsets; | |||
import java.nio.file.Files; | |||
import java.nio.file.Path; | |||
import java.nio.file.Paths; | |||
import java.util.*; | |||
import java.util.zip.ZipException; | |||
import static com.github.binarywang.wxpay.constant.WxPayConstants.QUERY_COMMENT_DATE_FORMAT; | |||
import static com.github.binarywang.wxpay.constant.WxPayConstants.TarType; | |||
/** | |||
* <pre> | |||
@@ -61,7 +87,17 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService { | |||
} | |||
/** | |||
* 发送post请求 | |||
* 发送post请求,得到响应字节数组 | |||
* | |||
* @param url 请求地址 | |||
* @param requestStr 请求信息 | |||
* @param useKey 是否使用证书 | |||
* @return 返回请求结果字节数组 | |||
*/ | |||
protected abstract byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException; | |||
/** | |||
* 发送post请求,得到响应字符串 | |||
* | |||
* @param url 请求地址 | |||
* @param requestStr 请求信息 | |||
@@ -410,6 +446,10 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService { | |||
@Override | |||
public WxPayBillResult downloadBill(String billDate, String billType, String tarType, String deviceInfo) throws WxPayException { | |||
if (!BillType.ALL.equals(billType)) { | |||
throw new WxPayException("目前仅支持ALL类型的对账单下载"); | |||
} | |||
WxPayDownloadBillRequest request = new WxPayDownloadBillRequest(); | |||
request.setBillType(billType); | |||
request.setBillDate(billDate); | |||
@@ -419,15 +459,52 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService { | |||
request.checkAndSign(this.getConfig(), false); | |||
String url = this.getPayBaseUrl() + "/pay/downloadbill"; | |||
String responseContent = this.post(url, request.toXML(), false); | |||
if (responseContent.startsWith("<")) { | |||
throw WxPayException.from(WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class)); | |||
String responseContent; | |||
if (TarType.GZIP.equals(tarType)) { | |||
responseContent = this.handleGzipBill(url, request.toXML()); | |||
} else { | |||
responseContent = this.post(url, request.toXML(), false); | |||
if (responseContent.startsWith("<")) { | |||
throw WxPayException.from(WxPayBaseResult.fromXML(responseContent, WxPayCommonResult.class)); | |||
} | |||
} | |||
return this.handleBill(billType, responseContent); | |||
} | |||
private WxPayBillResult handleBill(String billType, String responseContent) { | |||
if (!BillType.ALL.equals(billType)) { | |||
return null; | |||
} | |||
return this.handleBillInformation(responseContent); | |||
return this.handleAllBill(responseContent); | |||
} | |||
private String handleGzipBill(String url, String requestStr) throws WxPayException { | |||
try { | |||
byte[] responseBytes = this.postForBytes(url, requestStr, false); | |||
Path tempDirectory = Files.createTempDirectory("bill"); | |||
Path path = Paths.get(tempDirectory.toString(), System.currentTimeMillis() + ".gzip"); | |||
Files.write(path, responseBytes); | |||
try { | |||
List<String> allLines = Files.readAllLines(ZipUtil.ungzip(path.toFile()).toPath(), StandardCharsets.UTF_8); | |||
return Joiner.on("\n").join(allLines); | |||
} catch (ZipException e) { | |||
if (e.getMessage().contains("Not in GZIP format")) { | |||
throw WxPayException.from(WxPayBaseResult.fromXML(new String(responseBytes, StandardCharsets.UTF_8), | |||
WxPayCommonResult.class)); | |||
} else { | |||
this.log.error("解压zip文件出错", e); | |||
} | |||
} | |||
} catch (IOException e) { | |||
e.printStackTrace(); | |||
} | |||
return null; | |||
} | |||
private WxPayBillResult handleBillInformation(String responseContent) { | |||
private WxPayBillResult handleAllBill(String responseContent) { | |||
WxPayBillResult wxPayBillResult = new WxPayBillResult(); | |||
String listStr = ""; | |||
@@ -444,11 +521,16 @@ public abstract class WxPayServiceAbstractImpl implements WxPayService { | |||
* 参考以上格式进行取值 | |||
*/ | |||
List<WxPayBillBaseResult> wxPayBillBaseResultLst = new LinkedList<>(); | |||
String newStr = listStr.replaceAll(",", " "); // 去空格 | |||
String[] tempStr = newStr.split("`"); // 数据分组 | |||
String[] t = tempStr[0].split(" ");// 分组标题 | |||
int j = tempStr.length / t.length; // 计算循环次数 | |||
int k = 1; // 纪录数组下标 | |||
// 去空格 | |||
String newStr = listStr.replaceAll(",", " "); | |||
// 数据分组 | |||
String[] tempStr = newStr.split("`"); | |||
// 分组标题 | |||
String[] t = tempStr[0].split(" "); | |||
// 计算循环次数 | |||
int j = tempStr.length / t.length; | |||
// 纪录数组下标 | |||
int k = 1; | |||
for (int i = 0; i < j; i++) { | |||
WxPayBillBaseResult wxPayBillBaseResult = new WxPayBillBaseResult(); | |||
@@ -2,6 +2,7 @@ package com.github.binarywang.wxpay.service.impl; | |||
import com.github.binarywang.wxpay.bean.WxPayApiData; | |||
import com.github.binarywang.wxpay.exception.WxPayException; | |||
import jodd.util.Base64; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.http.auth.AuthScope; | |||
import org.apache.http.auth.UsernamePasswordCredentials; | |||
@@ -19,11 +20,12 @@ import org.apache.http.impl.client.HttpClients; | |||
import org.apache.http.util.EntityUtils; | |||
import javax.net.ssl.SSLContext; | |||
import java.io.UnsupportedEncodingException; | |||
import java.nio.charset.StandardCharsets; | |||
/** | |||
* <pre> | |||
* 微信支付请求实现类,apache httpclient实现 | |||
* 微信支付请求实现类,apache httpclient实现. | |||
* Created by Binary Wang on 2016/7/28. | |||
* </pre> | |||
* | |||
@@ -32,40 +34,34 @@ import java.nio.charset.StandardCharsets; | |||
public class WxPayServiceApacheHttpImpl extends WxPayServiceAbstractImpl { | |||
@Override | |||
protected String post(String url, String requestStr, boolean useKey) throws WxPayException { | |||
protected byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException { | |||
try { | |||
HttpClientBuilder httpClientBuilder = HttpClients.custom(); | |||
if (useKey) { | |||
SSLContext sslContext = this.getConfig().getSslContext(); | |||
if (null == sslContext) { | |||
sslContext = this.getConfig().initSSLContext(); | |||
HttpClientBuilder httpClientBuilder = createHttpClientBuilder(useKey); | |||
HttpPost httpPost = this.createHttpPost(url, requestStr); | |||
try (CloseableHttpClient httpclient = httpClientBuilder.build()) { | |||
try (CloseableHttpResponse response = httpclient.execute(httpPost)) { | |||
final byte[] bytes = EntityUtils.toByteArray(response.getEntity()); | |||
final String responseData = Base64.encodeToString(bytes); | |||
this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseData); | |||
wxApiData.set(new WxPayApiData(url, requestStr, responseData, null)); | |||
return bytes; | |||
} | |||
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, | |||
new String[]{"TLSv1"}, null, new DefaultHostnameVerifier()); | |||
httpClientBuilder.setSSLSocketFactory(sslsf); | |||
} | |||
HttpPost httpPost = new HttpPost(url); | |||
httpPost.setConfig(RequestConfig.custom() | |||
.setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout()) | |||
.setConnectTimeout(this.getConfig().getHttpConnectionTimeout()) | |||
.setSocketTimeout(this.getConfig().getHttpTimeout()) | |||
.build()); | |||
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost()) | |||
&& this.getConfig().getHttpProxyPort() > 0) { | |||
// 使用代理服务器 需要用户认证的代理服务器 | |||
CredentialsProvider provider = new BasicCredentialsProvider(); | |||
provider.setCredentials( | |||
new AuthScope(this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort()), | |||
new UsernamePasswordCredentials(this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword())); | |||
httpClientBuilder.setDefaultCredentialsProvider(provider); | |||
} finally { | |||
httpPost.releaseConnection(); | |||
} | |||
} catch (Exception e) { | |||
this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); | |||
wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage())); | |||
throw new WxPayException(e.getMessage(), e); | |||
} | |||
} | |||
@Override | |||
protected String post(String url, String requestStr, boolean useKey) throws WxPayException { | |||
try { | |||
HttpClientBuilder httpClientBuilder = this.createHttpClientBuilder(useKey); | |||
HttpPost httpPost = this.createHttpPost(url, requestStr); | |||
try (CloseableHttpClient httpclient = httpClientBuilder.build()) { | |||
httpPost.setEntity(new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1))); | |||
try (CloseableHttpResponse response = httpclient.execute(httpPost)) { | |||
String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); | |||
this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString); | |||
@@ -82,4 +78,56 @@ public class WxPayServiceApacheHttpImpl extends WxPayServiceAbstractImpl { | |||
} | |||
} | |||
private StringEntity createEntry(String requestStr) { | |||
try { | |||
return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)); | |||
} catch (UnsupportedEncodingException e) { | |||
//cannot happen | |||
this.log.error(e.getMessage(),e); | |||
return null; | |||
} | |||
} | |||
private HttpClientBuilder createHttpClientBuilder(boolean useKey) throws WxPayException { | |||
HttpClientBuilder httpClientBuilder = HttpClients.custom(); | |||
if (useKey) { | |||
this.setKey(httpClientBuilder); | |||
} | |||
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost()) | |||
&& this.getConfig().getHttpProxyPort() > 0) { | |||
// 使用代理服务器 需要用户认证的代理服务器 | |||
CredentialsProvider provider = new BasicCredentialsProvider(); | |||
provider.setCredentials( | |||
new AuthScope(this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort()), | |||
new UsernamePasswordCredentials(this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword())); | |||
httpClientBuilder.setDefaultCredentialsProvider(provider); | |||
} | |||
return httpClientBuilder; | |||
} | |||
private HttpPost createHttpPost(String url, String requestStr) { | |||
HttpPost httpPost = new HttpPost(url); | |||
httpPost.setEntity(this.createEntry(requestStr)); | |||
httpPost.setConfig(RequestConfig.custom() | |||
.setConnectionRequestTimeout(this.getConfig().getHttpConnectionTimeout()) | |||
.setConnectTimeout(this.getConfig().getHttpConnectionTimeout()) | |||
.setSocketTimeout(this.getConfig().getHttpTimeout()) | |||
.build()); | |||
return httpPost; | |||
} | |||
private void setKey(HttpClientBuilder httpClientBuilder) throws WxPayException { | |||
SSLContext sslContext = this.getConfig().getSslContext(); | |||
if (null == sslContext) { | |||
sslContext = this.getConfig().initSSLContext(); | |||
} | |||
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, | |||
new String[]{"TLSv1"}, null, new DefaultHostnameVerifier()); | |||
httpClientBuilder.setSSLSocketFactory(sslsf); | |||
} | |||
} |
@@ -9,13 +9,14 @@ import jodd.http.ProxyInfo; | |||
import jodd.http.ProxyInfo.ProxyType; | |||
import jodd.http.net.SSLSocketHttpConnectionProvider; | |||
import jodd.http.net.SocketHttpConnectionProvider; | |||
import jodd.util.Base64; | |||
import org.apache.commons.lang3.StringUtils; | |||
import javax.net.ssl.SSLContext; | |||
import java.nio.charset.StandardCharsets; | |||
/** | |||
* 微信支付请求实现类,jodd-http实现 | |||
* 微信支付请求实现类,jodd-http实现. | |||
* Created by Binary Wang on 2016/7/28. | |||
* | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
@@ -23,34 +24,25 @@ import java.nio.charset.StandardCharsets; | |||
public class WxPayServiceJoddHttpImpl extends WxPayServiceAbstractImpl { | |||
@Override | |||
protected String post(String url, String requestStr, boolean useKey) throws WxPayException { | |||
protected byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException { | |||
try { | |||
HttpRequest request = HttpRequest | |||
.post(url) | |||
.timeout(this.getConfig().getHttpTimeout()) | |||
.connectionTimeout(this.getConfig().getHttpConnectionTimeout()) | |||
.bodyText(requestStr); | |||
if (useKey) { | |||
SSLContext sslContext = this.getConfig().getSslContext(); | |||
if (null == sslContext) { | |||
sslContext = this.getConfig().initSSLContext(); | |||
} | |||
final SSLSocketHttpConnectionProvider provider = new SSLSocketHttpConnectionProvider(sslContext); | |||
request.withConnectionProvider(provider); | |||
} | |||
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost()) && this.getConfig().getHttpProxyPort() > 0) { | |||
ProxyInfo httpProxy = new ProxyInfo(ProxyType.HTTP, this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort(), | |||
this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword()); | |||
HttpConnectionProvider provider = request.connectionProvider(); | |||
if (null == provider) { | |||
provider = new SocketHttpConnectionProvider(); | |||
} | |||
provider.useProxy(httpProxy); | |||
request.withConnectionProvider(provider); | |||
} | |||
HttpRequest request = this.buildHttpRequest(url, requestStr, useKey); | |||
byte[] responseBytes = request.send().bodyBytes(); | |||
final String responseString = Base64.encodeToString(responseBytes); | |||
this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseString); | |||
wxApiData.set(new WxPayApiData(url, requestStr, responseString, null)); | |||
return responseBytes; | |||
} catch (Exception e) { | |||
this.log.error("\n【请求地址】:{}\n【请求数据】:{}\n【异常信息】:{}", url, requestStr, e.getMessage()); | |||
wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage())); | |||
throw new WxPayException(e.getMessage(), e); | |||
} | |||
} | |||
@Override | |||
protected String post(String url, String requestStr, boolean useKey) throws WxPayException { | |||
try { | |||
HttpRequest request = this.buildHttpRequest(url, requestStr, useKey); | |||
String responseString = this.getResponseString(request.send()); | |||
this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据】:{}", url, requestStr, responseString); | |||
@@ -63,6 +55,35 @@ public class WxPayServiceJoddHttpImpl extends WxPayServiceAbstractImpl { | |||
} | |||
} | |||
private HttpRequest buildHttpRequest(String url, String requestStr, boolean useKey) throws WxPayException { | |||
HttpRequest request = HttpRequest | |||
.post(url) | |||
.timeout(this.getConfig().getHttpTimeout()) | |||
.connectionTimeout(this.getConfig().getHttpConnectionTimeout()) | |||
.bodyText(requestStr); | |||
if (useKey) { | |||
SSLContext sslContext = this.getConfig().getSslContext(); | |||
if (null == sslContext) { | |||
sslContext = this.getConfig().initSSLContext(); | |||
} | |||
final SSLSocketHttpConnectionProvider provider = new SSLSocketHttpConnectionProvider(sslContext); | |||
request.withConnectionProvider(provider); | |||
} | |||
if (StringUtils.isNotBlank(this.getConfig().getHttpProxyHost()) && this.getConfig().getHttpProxyPort() > 0) { | |||
ProxyInfo httpProxy = new ProxyInfo(ProxyType.HTTP, this.getConfig().getHttpProxyHost(), this.getConfig().getHttpProxyPort(), | |||
this.getConfig().getHttpProxyUsername(), this.getConfig().getHttpProxyPassword()); | |||
HttpConnectionProvider provider = request.connectionProvider(); | |||
if (null == provider) { | |||
provider = new SocketHttpConnectionProvider(); | |||
} | |||
provider.useProxy(httpProxy); | |||
request.withConnectionProvider(provider); | |||
} | |||
return request; | |||
} | |||
private String getResponseString(HttpResponse response) throws WxPayException { | |||
try { | |||
this.log.debug("【微信服务器响应头信息】:\n{}", response.toString(false)); | |||
@@ -26,6 +26,8 @@ import java.nio.file.Path; | |||
import java.util.Calendar; | |||
import java.util.Date; | |||
import static com.github.binarywang.wxpay.constant.WxPayConstants.*; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.testng.Assert.*; | |||
/** | |||
@@ -139,7 +141,13 @@ public class WxPayServiceAbstractImplTest { | |||
@DataProvider | |||
public Object[][] billingData() { | |||
return new Object[][]{ | |||
// {"20170831", BillType.ALL, null, "deviceInfo"}, | |||
{"20170831", BillType.ALL, TarType.GZIP, "deviceInfo"}, | |||
{"20170831", BillType.RECHARGE_REFUND, TarType.GZIP, "deviceInfo"}, | |||
{"20170831", BillType.REFUND, TarType.GZIP, "deviceInfo"}, | |||
{"20170831", BillType.SUCCESS, TarType.GZIP, "deviceInfo"}, | |||
{"20170831", BillType.ALL, null, "deviceInfo"}, | |||
{"20170831", BillType.RECHARGE_REFUND, null, "deviceInfo"}, | |||
{"20170831", BillType.REFUND, null, "deviceInfo"}, | |||
{"20170831", BillType.SUCCESS, null, "deviceInfo"} | |||
}; | |||
} | |||
@@ -148,11 +156,11 @@ public class WxPayServiceAbstractImplTest { | |||
public void testDownloadBill(String billDate, String billType, | |||
String tarType, String deviceInfo) throws Exception { | |||
WxPayBillResult billResult = this.payService.downloadBill(billDate, billType, tarType, deviceInfo); | |||
assertNotNull(billResult); | |||
assertThat(billResult).isNotNull(); | |||
this.logger.info(billResult.toString()); | |||
} | |||
@Test | |||
@Test(expectedExceptions = WxPayException.class) | |||
public void testDownloadBill_withNoParams() throws Exception { | |||
//必填字段为空时,抛出异常 | |||
this.payService.downloadBill("", "", "", null); | |||
@@ -247,7 +255,7 @@ public class WxPayServiceAbstractImplTest { | |||
.openid("ojOQA0y9o-Eb6Aep7uVTdbkJqrP4") | |||
.amount(1) | |||
.spbillCreateIp("10.10.10.10") | |||
.checkName(WxPayConstants.CheckNameOption.NO_CHECK) | |||
.checkName(CheckNameOption.NO_CHECK) | |||
.description("描述信息") | |||
.build(); | |||