Skip to content
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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;

}
Original file line number Diff line number Diff line change
@@ -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>
* 电商收付通相关服务类.
Expand Down Expand Up @@ -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;

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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;
Expand Down Expand Up @@ -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;

/**
* 获取企业付款服务类.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
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;
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 {
Expand Down Expand Up @@ -273,6 +280,18 @@ public SettlementResult querySettlement(String subMchid) throws WxPayException {
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 通知头信息
Expand All @@ -287,4 +306,24 @@ private boolean verifyNotifySign(SignatureHeader header, String data) {
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();
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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;
Expand Down Expand Up @@ -207,6 +208,31 @@ public String getV3(URI url) throws WxPayException {
}
}

@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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
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;
Expand Down Expand Up @@ -80,6 +81,11 @@ public String getV3(URI url) throws WxPayException {
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)
Expand Down
Loading