1、查询对账单下载地址
https://opendocs.alipay.com/apis/api_15/alipay.data.dataservice.bill.downloadurl.query
2、对账说明
https://opendocs.alipay.com/open/204/106262
1、支付宝下载对账单,不保存文件、不解压直接解析。
2、实例实现的是(trade)支付宝交易收单的业务账单。
1、请求实体、响应实体
DownloadBillRequest.java
@Data
public class DownloadBillRequest implements Serializable {
private static final long serialVersionUID = -9016422214082575601L;
// 账单日期 (yyyy-MM-dd)
private String billDate;
// 账单类型(trade、signcustomer)
private String billType;
}
DownloadBillResponse.java
@Data
public class DownloadBillResponse implements Serializable {
private static final long serialVersionUID = 5344850998806040582L;
// 返回状态码
private String returnCode;
// 返回信息
private String returnMsg;
// 错误代码
private String errCode;
// 账单明细
List billInfos;
}
2、获取zip下载地址
public DownloadBillResponse downloadBill(DownloadBillRequest downloadBillRequest) {
DownloadBillResponse response = new DownloadBillResponse();
try {
AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();
if (Objects.isNull(downloadBillRequest.getBillType())) {
downloadBillRequest.setBillType("trade");
}
Map param = HumpConversionUtils.transform(downloadBillRequest);
request.setBizContent(JSON.toJSONString(param));
AlipayDataDataserviceBillDownloadurlQueryResponse result = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
"你的AppId",
"你的应用私钥",
"json",
"utf-8",
"你的支付公钥",
"RSA2"
).execute(request);
if (Objects.equals(PayStatus.ALI_SUCCESS, result.getCode())) {
List billInfos = downloadBill(result.getBillDownloadUrl())
response.setReturnCode(result.getCode());
response.setBillInfos(billInfos);
return response;
}
response.setReturnCode(result.getCode());
} catch (AlipayApiException e)
e.printStackTrace();
}
return response;
}
3、解析文件获取数据(重点)
1、这里只解析明细数据,没有做汇总数据的解析。
2、文件不做保存、不做解压直接解析获取数据。
public List downloadBill(String downloadUrl) {
List aliBillInfos = new ArrayList<>();
HttpURLConnection conn = null;
ZipInputStream in = null;
BufferedReader br = null;
try {
URL url = new URL(downloadUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5 * 1000);
conn.setRequestMethod("GET");
conn.connect();
// 不解压直接读取,加上GBK解决乱码问题
in = new ZipInputStream(conn.getInputStream(), Charset.forName("GBK"));
br = new BufferedReader(new InputStreamReader(in, "GBK"));
ZipEntry zipFile;
// 循环读取zip中的cvs文件,无法使用jdk自带,因为文件名中有中文
while ((zipFile = in.getNextEntry()) != null) {
if (zipFile.isDirectory()) {
// 目录不处理
}
// 获得cvs名字,检测文件是否存在
String fileName = zipFile.getName();
log.info("对账单解析,输出文件名称:{}", fileName);
if (!Objects.isNull(fileName) && fileName.indexOf(".") != -1 && !fileName.contains("汇总")) {
String line;
int i = 0;
// 按行读取数据
while ((line = br.readLine()) != null) {
if (!line.startsWith("#")) {
log.info("解析数据:{}", line);
if (i > 0) {
String[] lines = line.split(",",-1);
BillInfo aliBillInfo = BillInfo.builder()
.tradeNo(lines[0].trim())
.outTradeNo(lines[1].trim())
.businessType(lines[2].trim())
.tradeName(lines[3].trim())
.createTime(lines[4].trim())
.finishTime(lines[5].trim())
.storeNumber(lines[6].trim())
.storeName(lines[7].trim())
.operator(lines[8].trim())
.terminalNumber(lines[9].trim())
.clientAccount(lines[10].trim())
.orderAmount(lines[11].trim())
.realAmount(lines[12].trim())
.redPaperAmount(lines[13].trim())
.jifenbaoAmount(lines[14].trim())
.zfbDiscountAmount(lines[15].trim())
.merchantOffersAmount(lines[16].trim())
.CouponWriteOffAmount(lines[17].trim())
.couponName(lines[18].trim())
.merchantRedAmount(lines[19].trim())
.cardAmount(lines[20].trim())
.refundNo(lines[21].trim())
.serviceFee(lines[22].trim())
.fenrun(lines[23].trim())
.build();
aliBillInfos.add(aliBillInfo);
}
i++;
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (br != null) br.close();
if (in != null) in.close();
if (conn != null) conn.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
}
return aliBillInfos;
}
4、账单实体
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
public class BillInfo implements Serializable {
private static final long serialVersionUID = -3947062662460225647L;
/**
* 支付宝交易号
*/
private String tradeNo;
/**
* 商户订单号
*/
private String outTradeNo;
/**
* 业务类型
*/
private String businessType;
/**
* 商品名称
*/
private String tradeName;
/**
* 创建时间
*/
private String createTime;
/**
* 完成时间
*/
private String finishTime;
/**
* 门店编号
*/
private String storeNumber;
/**
* 门店名称
*/
private String storeName;
/**
* 操作员
*/
private String operator;
/**
* 终端号
*/
private String terminalNumber;
/**
* 对方账户
*/
private String clientAccount;
/**
* 订单金额(元)
*/
private String orderAmount;
/**
* 商家实收(元)
*/
private String realAmount;
/**
* 支付宝红包(元)
*/
private String redPaperAmount;
/**
* 集分宝(元)
*/
private String jifenbaoAmount;
/**
* 支付宝优惠(元)
*/
private String zfbDiscountAmount;
/**
* 商家优惠(元)
*/
private String merchantOffersAmount;
/**
* 券核销金额(元)
*/
private String CouponWriteOffAmount;
/**
* 券名称
*/
private String couponName;
/**
* 商家红包消费金额(元)
*/
private String merchantRedAmount;
/**
* 卡消费金额(元)
*/
private String cardAmount;
/**
* 退款批次号/请求号
*/
private String refundNo;
/**
* 服务费(元)
*/
private String serviceFee;
/**
* 分润(元)
*/
private String fenrun;
/**
* 备注
*/
private String remark;
}
5、驼峰规范属性转下划线工具类
/**
* 驼峰规范属性转下划线 (Bean 》 Map)
* 支持递归转换,如属性为JavaBean时
*/
public class HumpConversionUtils {
private static String compile = "[A-Z]";
private HumpConversionUtils() {
}
/**
* @methodName:transform
* @description:驼峰转下划线(对象
*/
public static Map transform(T object) {
if (object == null) {
return null;
}
Map map = new HashMap<>();
Field[] fields = object.getClass().getDeclaredFields();
try {
for (int i = 0; i < fields.length; i++) {
String fieldName = fields[i].getName();
if ("serialVersionUID".equalsIgnoreCase(fieldName)) {
continue;
}
// 转换驼峰形式属性名称成下划线风格,获取map的key 例:fieldName 》 field_name
String transformFieldName = HumpConversionUtils.getTransformFieldName(fieldName);
Object FieldValue = null;
String name = fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
String type = fields[i].getGenericType().toString();
Method m = object.getClass().getMethod("get" + name);
switch (type) {
// 如果有需要,可以仿照下面继续进行扩充,再增加对其它类型的判断
case "class java.lang.String":
case "class java.lang.Boolean":
case "class java.util.Date":
case "class java.lang.Integer":
case "class java.lang.Long":
// 调用getter方法获取属性值
FieldValue = m.invoke(object);
break;
default:
// 属性类型为bean,则递归
Object obj = m.invoke(object);
FieldValue = HumpConversionUtils.transform(obj);
}
map.put(transformFieldName, FieldValue);
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/**
* @methodName:getTransformFieldName
* @description:驼峰转下划线(字符串
*/
private static String getTransformFieldName(String fieldName) {
Pattern humpPattern = Pattern.compile(compile);
Matcher matcher = humpPattern.matcher(fieldName);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
}
matcher.appendTail(sb);
return sb.toString();
}
}