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

This commit is contained in:
f00lish
2020-09-29 12:40:11 +08:00
committed by GitHub
parent b797152199
commit 429f1706a4
9 changed files with 292 additions and 3 deletions

View File

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

View File

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

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
/**
* 获取企业付款服务类.
*

View File

@@ -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();
}
}

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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");