diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/BillRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/BillRequest.java new file mode 100644 index 000000000..247b2a7d0 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/BillRequest.java @@ -0,0 +1,86 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +/** + * 账单请求 + * @author: f00lish + * @date: 2020/09/28 + */ +@Data +@Builder +@ToString +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class BillRequest { + + /** + *
+   * 字段名:账单日期
+   * 变量名:bill_date
+   * 是否必填:是
+   * 类型:string(10)
+   * 描述:
+   *  格式YYYY-MM-DD
+   *  仅支持三个月内的账单下载申请。
+   *  示例值:2019-06-11
+   * 
+ */ + @SerializedName(value = "bill_date") + private String billDate; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:否
+   * 类型:string(12)
+   * 描述:
+   *  1、若商户是直连商户:无需填写该字段。
+   *  2、若商户是服务商:
+   *  ● 不填则默认返回服务商下的交易或退款数据。
+   *  ● 如需下载某个子商户下的交易或退款数据,则该字段必填。
+   *  特殊规则:最小字符长度为8
+   *  注意:仅适用于电商平台 服务商
+   *  示例值:1900000001
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:账单类型
+   * 变量名:bill_type
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  不填则默认是ALL
+   *  枚举值:
+   *  ALL:返回当日所有订单信息(不含充值退款订单)
+   *  SUCCESS:返回当日成功支付的订单(不含充值退款订单)
+   *  REFUND:返回当日退款订单(不含充值退款订单)
+   *  示例值:ALL
+   * 
+ */ + @SerializedName(value = "bill_type") + private String billType; + + /** + *
+   * 字段名:压缩类型
+   * 变量名:tar_type
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  不填则默认是数据流
+   *  枚举值:
+   *  GZIP:返回格式为.gzip的压缩包账单
+   *  示例值:GZIP
+   * 
+ */ + @SerializedName(value = "tar_type") + private String tarType; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/BillResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/BillResult.java new file mode 100644 index 000000000..4a5bdb952 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/BillResult.java @@ -0,0 +1,60 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +/** + * 账单结果 + * @author: f00lish + * @date: 2020/09/28 + */ +@Data +@Builder +@ToString +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class BillResult { + + /** + *
+   * 字段名:哈希类型
+   * 变量名:hash_type
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  原始账单(gzip需要解压缩)的摘要值,用于校验文件的完整性。
+   *  示例值:SHA1
+   * 
+ */ + @SerializedName(value = "hash_type") + private String hashType; + + /** + *
+   * 字段名:哈希值
+   * 变量名:hash_value
+   * 是否必填:是
+   * 类型:string(1024)
+   * 描述:
+   *  原始账单(gzip需要解压缩)的摘要值,用于校验文件的完整性。
+   *  示例值:79bb0f45fc4c42234a918000b2668d689e2bde04
+   * 
+ */ + @SerializedName(value = "hash_value") + private String hashValue; + + /** + *
+   * 字段名:账单下载地址
+   * 变量名:download_url
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  供下一步请求账单文件的下载地址,该地址30s内有效。
+   *  示例值:https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx
+   * 
+ */ + @SerializedName(value = "download_url") + private String downloadUrl; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/BillTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/BillTypeEnum.java new file mode 100644 index 000000000..598ee4be4 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/BillTypeEnum.java @@ -0,0 +1,30 @@ +package com.github.binarywang.wxpay.bean.ecommerce.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 账单类型 + * @author: f00lish + * @date: 2020/09/28 + */ +@Getter +@AllArgsConstructor +public enum BillTypeEnum { + + /** + * 交易账单 + */ + TRADE_BILL("%s/v3/bill/tradebill?%s"), + /** + * 资金账单 + */ + FUND_FLOW_BILL("%s/v3/bill/fundflowbill?%s"); + + + /** + * url + */ + private final String url; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java index c0bc6444f..6e536bb39 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java @@ -1,10 +1,13 @@ package com.github.binarywang.wxpay.service; import com.github.binarywang.wxpay.bean.ecommerce.*; +import com.github.binarywang.wxpay.bean.ecommerce.enums.BillTypeEnum; import com.github.binarywang.wxpay.bean.ecommerce.enums.SpAccountTypeEnum; import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum; import com.github.binarywang.wxpay.exception.WxPayException; +import java.io.InputStream; + /** *
  *  电商收付通相关服务类.
@@ -360,4 +363,29 @@ public interface EcommerceService {
    */
   SettlementResult querySettlement(String subMchid) throws WxPayException;
 
+  /**
+   * 
+   * 请求账单API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/bill.shtml
+   * 
+ * + * @param billType 账单类型。 + * @param request 二级商户号。 + * @return 返回数据 return bill result + * @throws WxPayException the wx pay exception + */ + BillResult applyBill(BillTypeEnum billType, BillRequest request) throws WxPayException; + + /** + *
+   * 下载账单API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/bill.shtml
+   * 
+ * + * @param url 微信返回的账单地址。 + * @return 返回数据 return inputStream + * @throws WxPayException the wx pay exception + */ + InputStream downloadBill(String url) throws WxPayException; + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java index 27d86548c..11e624224 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java @@ -13,6 +13,7 @@ import com.github.binarywang.wxpay.exception.WxPayException; import org.apache.http.client.methods.HttpPost; import java.io.File; +import java.io.InputStream; import java.net.URI; import java.util.Date; import java.util.Map; @@ -97,6 +98,15 @@ public interface WxPayService { */ String getV3(URI url) throws WxPayException; + /** + * 发送下载 V3请求,得到响应流. + * + * @param url 请求地址 + * @return 返回请求响应流 + * @throws WxPayException the wx pay exception + */ + InputStream downloadV3(URI url) throws WxPayException; + /** * 获取企业付款服务类. * diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java index eefc7a264..193d6a20e 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java @@ -1,6 +1,7 @@ package com.github.binarywang.wxpay.service.impl; import com.github.binarywang.wxpay.bean.ecommerce.*; +import com.github.binarywang.wxpay.bean.ecommerce.enums.BillTypeEnum; import com.github.binarywang.wxpay.bean.ecommerce.enums.SpAccountTypeEnum; import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum; import com.github.binarywang.wxpay.exception.WxPayException; @@ -8,15 +9,21 @@ import com.github.binarywang.wxpay.service.EcommerceService; import com.github.binarywang.wxpay.service.WxPayService; import com.github.binarywang.wxpay.v3.util.AesUtils; import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil; +import com.google.common.base.CaseFormat; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import lombok.RequiredArgsConstructor; +import org.apache.commons.beanutils.BeanMap; import java.io.IOException; +import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.util.Iterator; +import java.util.Map; import java.util.Objects; +import java.util.Set; @RequiredArgsConstructor public class EcommerceServiceImpl implements EcommerceService { @@ -273,6 +280,18 @@ public class EcommerceServiceImpl implements EcommerceService { return GSON.fromJson(response, SettlementResult.class); } + @Override + public BillResult applyBill(BillTypeEnum billType, BillRequest request) throws WxPayException { + String url = String.format(billType.getUrl(), this.payService.getPayBaseUrl(), this.parseURLPair(request)); + String response = this.payService.getV3(URI.create(url)); + return GSON.fromJson(response, BillResult.class); + } + + @Override + public InputStream downloadBill(String url) throws WxPayException { + return this.payService.downloadV3(URI.create(url)); + } + /** * 校验通知签名 * @param header 通知头信息 @@ -287,4 +306,24 @@ public class EcommerceServiceImpl implements EcommerceService { return payService.getConfig().getVerifier().verify(header.getSerialNo(), beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned()); } -} + + /** + * 对象拼接到url + * @param o 转换对象 + * @return 拼接好的string + */ + private String parseURLPair(Object o) { + Map map = new BeanMap(o); + Set> set = map.entrySet(); + Iterator> it = set.iterator(); + StringBuilder sb = new StringBuilder(); + while (it.hasNext()) { + Map.Entry e = it.next(); + if ( !"class".equals(e.getKey()) && e.getValue() != null) + sb.append(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, String.valueOf(e.getKey()))).append("=").append(e.getValue()).append("&"); + } + return sb.deleteCharAt(sb.length() - 1).toString(); + } + + + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java index fe25bffc6..6b9adf289 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java @@ -26,6 +26,7 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; +import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -207,6 +208,31 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl { } } + @Override + public InputStream downloadV3(URI url) throws WxPayException { + CloseableHttpClient httpClient = this.createApiV3HttpClient(); + HttpGet httpGet = new HttpGet(url); + httpGet.addHeader("Accept", ContentType.WILDCARD.getMimeType()); + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + //v3已经改为通过状态码判断200 204 成功 + int statusCode = response.getStatusLine().getStatusCode(); + if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) { + this.log.info("\n【请求地址】:{}\n", url); + return response.getEntity().getContent(); + } else { + //有错误提示信息返回 + String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + JsonObject jsonObject = GsonParser.parse(responseString); + throw new WxPayException(jsonObject.get("message").getAsString()); + } + } catch (Exception e) { + this.log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage()); + throw new WxPayException(e.getMessage(), e); + } finally { + httpGet.releaseConnection(); + } + } + private CloseableHttpClient createApiV3HttpClient() throws WxPayException { CloseableHttpClient apiV3HttpClient = this.getConfig().getApiV3HttpClient(); if (null == apiV3HttpClient) { diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java index 65f5e869b..129c60a29 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java @@ -13,6 +13,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.http.client.methods.HttpPost; import javax.net.ssl.SSLContext; +import java.io.InputStream; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -80,6 +81,11 @@ public class WxPayServiceJoddHttpImpl extends BaseWxPayServiceImpl { return null; } + @Override + public InputStream downloadV3(URI url) throws WxPayException { + return null; + } + private HttpRequest buildHttpRequest(String url, String requestStr, boolean useKey) throws WxPayException { HttpRequest request = HttpRequest .post(url) diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java index 9b9b0ad4d..e14d8b5b1 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java @@ -1,15 +1,16 @@ package com.github.binarywang.wxpay.v3.auth; -import java.io.IOException; - import com.github.binarywang.wxpay.v3.Validator; import lombok.extern.slf4j.Slf4j; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.entity.ContentType; import org.apache.http.util.EntityUtils; +import java.io.IOException; + @Slf4j public class WxPayValidator implements Validator { private Verifier verifier; @@ -20,6 +21,9 @@ public class WxPayValidator implements Validator { @Override public final boolean validate(CloseableHttpResponse response) throws IOException { + if (!ContentType.APPLICATION_JSON.getMimeType().equals(ContentType.parse(String.valueOf(response.getFirstHeader("Content-Type").getValue())).getMimeType())) { + return true; + } Header serialNo = response.getFirstHeader("Wechatpay-Serial"); Header sign = response.getFirstHeader("Wechatpay-Signature"); Header timestamp = response.getFirstHeader("Wechatpay-TimeStamp");