Browse Source

:new: #1789 微信支付电商收付通增加下载账单的接口

f00lish 4 years ago
parent
commit
429f1706a4

+ 86 - 0
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 {
+
+  /**
+   * <pre>
+   * 字段名:账单日期
+   * 变量名:bill_date
+   * 是否必填:是
+   * 类型:string(10)
+   * 描述:
+   *  格式YYYY-MM-DD
+   *  仅支持三个月内的账单下载申请。
+   *  示例值:2019-06-11
+   * </pre>
+   */
+  @SerializedName(value = "bill_date")
+  private String billDate;
+
+  /**
+   * <pre>
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:否
+   * 类型:string(12)
+   * 描述:
+   *  1、若商户是直连商户:无需填写该字段。
+   *  2、若商户是服务商:
+   *  ● 不填则默认返回服务商下的交易或退款数据。
+   *  ● 如需下载某个子商户下的交易或退款数据,则该字段必填。
+   *  特殊规则:最小字符长度为8
+   *  注意:仅适用于电商平台 服务商
+   *  示例值:1900000001
+   * </pre>
+   */
+  @SerializedName(value = "sub_mchid")
+  private String subMchid;
+
+  /**
+   * <pre>
+   * 字段名:账单类型
+   * 变量名:bill_type
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  不填则默认是ALL
+   *  枚举值:
+   *  ALL:返回当日所有订单信息(不含充值退款订单)
+   *  SUCCESS:返回当日成功支付的订单(不含充值退款订单)
+   *  REFUND:返回当日退款订单(不含充值退款订单)
+   *  示例值:ALL
+   * </pre>
+   */
+  @SerializedName(value = "bill_type")
+  private String billType;
+
+  /**
+   * <pre>
+   * 字段名:压缩类型
+   * 变量名:tar_type
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  不填则默认是数据流
+   *  枚举值:
+   *  GZIP:返回格式为.gzip的压缩包账单
+   *  示例值:GZIP
+   * </pre>
+   */
+  @SerializedName(value = "tar_type")
+  private String tarType;
+
+}

+ 60 - 0
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 {
+
+  /**
+   * <pre>
+   * 字段名:哈希类型
+   * 变量名:hash_type
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  原始账单(gzip需要解压缩)的摘要值,用于校验文件的完整性。
+   *  示例值:SHA1
+   * </pre>
+   */
+  @SerializedName(value = "hash_type")
+  private String hashType;
+
+  /**
+   * <pre>
+   * 字段名:哈希值
+   * 变量名:hash_value
+   * 是否必填:是
+   * 类型:string(1024)
+   * 描述:
+   *  原始账单(gzip需要解压缩)的摘要值,用于校验文件的完整性。
+   *  示例值:79bb0f45fc4c42234a918000b2668d689e2bde04
+   * </pre>
+   */
+  @SerializedName(value = "hash_value")
+  private String hashValue;
+
+  /**
+   * <pre>
+   * 字段名:账单下载地址
+   * 变量名:download_url
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  供下一步请求账单文件的下载地址,该地址30s内有效。
+   *  示例值:https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx
+   * </pre>
+   */
+  @SerializedName(value = "download_url")
+  private String downloadUrl;
+
+}

+ 30 - 0
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;
+
+}

+ 28 - 0
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;
+
 /**
  * <pre>
  *  电商收付通相关服务类.
@@ -360,4 +363,29 @@ public interface EcommerceService {
    */
   SettlementResult querySettlement(String subMchid) throws WxPayException;
 
+  /**
+   * <pre>
+   * 请求账单API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/bill.shtml
+   * </pre>
+   *
+   * @param billType 账单类型。
+   * @param request 二级商户号。
+   * @return 返回数据 return bill result
+   * @throws WxPayException the wx pay exception
+   */
+  BillResult applyBill(BillTypeEnum billType, BillRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 下载账单API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/bill.shtml
+   * </pre>
+   *
+   * @param url 微信返回的账单地址。
+   * @return 返回数据 return inputStream
+   * @throws WxPayException the wx pay exception
+   */
+  InputStream downloadBill(String url) throws WxPayException;
+
 }

+ 10 - 0
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;
@@ -98,6 +99,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;
+
+  /**
    * 获取企业付款服务类.
    *
    * @return the ent pay service

+ 40 - 1
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<Object, Object> map = new BeanMap(o);
+    Set<Map.Entry<Object, Object>> set = map.entrySet();
+    Iterator<Map.Entry<Object, Object>> it = set.iterator();
+    StringBuilder sb = new StringBuilder();
+    while (it.hasNext()) {
+      Map.Entry<Object, Object> 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();
+  }
+
+
+  }

+ 26 - 0
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) {

+ 6 - 0
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)

+ 6 - 2
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");