业务实践系列(7):对账-支付宝交易账单数据解析

支付系统就一定需要对账,需要下载支付平台侧的账单与自己业务系统的交易数据时行对账。

支付宝账单:为方便商户快速查账,支持商户通过本接口获取商户离线账单下载地址。

交易账单

支付宝的对账API 是在 财务 API 分类里面。

下载支付宝交易对单账分两步:

  1. 查询对账单下载地址的接口发请求,返回数据中包含下载账单的 URL 地址。

  2. 这个 URL 是一个账单压缩文件的下载地址,可直接在浏览器访问下载。

  3. 下载下来的是一个以 账号_交易日期 为名的压缩包。

    压缩包中有 2 个以 csv 后缀的文件,实际就是文本表格文件,可用文本编辑器打开。一个是 业务明细,另一个是 **业务明细(汇总)**。

    文本表格内容包含了 \r\n 换行符,明细数据行首字母不是 #号。见下方的账单数据示例

账单数据

账单业务明细示例:

1
2
3
4
5
6
7
8
9
10
#支付宝业务明细查询
#账号:[208xxxxxxxxxxxx156]
#起始日期:[2020年07月22日 00:00:00] 终止日期:[2020年07月23日 00:00:00]
#-----------------------------------------业务明细列表----------------------------------------
支付宝交易号,商户订单号,业务类型,商品名称,创建时间,完成时间,门店编号,门店名称,操作员,终端号,对方账户,订单金额(元),商家实收(元),支付宝红包(元),集分宝(元),支付宝优惠(元),商家优惠(元),券核销金额(元),券名称,商家红包消费金额(元),卡消费金额(元),退款批次号/请求号,服务费(元),分润(元),备注
20200xxxxxxxxxxxxxx44491 ,411735xxxxxxxxxxxxxx720 ,交易 ,院内充值,2020-07-22 16:13:11,2020-07-22 16:14:24, , , , ,**佳(yan***@163.com) ,1.00,1.00,0.96,0.00,0.00,0.00,0.00,花呗mau生活费实体店红包,0.00 ,0.00, ,-0.01,0.00,
#-----------------------------------------业务明细列表结束------------------------------------
#交易合计:1笔,商家实收共1.00元,商家优惠共0.00元
#退款合计:0笔,商家实收退款共0.00元,商家优惠退款共0.00元
#导出时间:[2020年07月23日 04:40:08]

账单解析

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
/**
* @desc: 支付宝账单明细
*/
@Data
@SuperBuilder
@Accessors(chain = true)
public class AliPayBillDetail implements Serializable {
private static final long serialVersionUID = 9122205774630735303L;

/**
* 支付宝交易号
*/
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;
}

数据解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
/**
* @desc: 下载支付宝账单业务
*/
@Service
public class AliPayBillServiceImpl implements AliPayBillService {
private static final Logger logger = LogManager.getLogger(AliPayBillServiceImpl.class);

private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

@Autowired
private PayAccountService payAccountService;
@Autowired
private PayProperties payProperties;

private static final String aliPayDataFormat = "JSON";
private static final String defaultCharset = "utf-8";
private static final String defaultSignType = "RSA2";

/**
* 下载支付宝账单
*
* @param downloadVO
*/
@Override
public HashMap<String, List<AliPayBillDetail>> downloadAliPayBill(DownloadVO downloadVO) {
List<PayAccountBO> payAccountList = getOrgPayAccount(downloadVO);
HashMap<String, List<AliPayBillDetail>> hashMap = new HashMap<>();
payAccountList.parallelStream().forEach(payAccount -> {
String downloadBillUrl = this.getBillFileUrl(downloadVO, payAccount);
logger.info("获取 {} 支付宝账单下载的URL:{}", downloadVO.getBillDate(), downloadBillUrl);
if (StringUtils.isNotBlank(downloadBillUrl)) {
List<AliPayBillDetail> aliPayBillDetails = downloadBillFile(downloadBillUrl);
hashMap.put(payAccount.getAppId(), aliPayBillDetails);
}
});
return hashMap;
}

/**
* @desc: 获取下载账单的URL
* @param: [downloadVO, payAccount]
*/
private String getBillFileUrl(DownloadVO downloadVO, PayAccountBO payAccount) {
AlipayClient alipayClient = getByAccount(payAccount);
AlipayDataDataserviceBillDownloadurlQueryRequest request = new AlipayDataDataserviceBillDownloadurlQueryRequest();

HashMap<String, String> paramsMap = new HashMap<>();
paramsMap.put("bill_type", "trade");
paramsMap.put("bill_date", downloadVO.getBillDate());
request.setBizContent(JSON.toJSONString(paramsMap));
AlipayDataDataserviceBillDownloadurlQueryResponse response = null;
try {
response = alipayClient.execute(request);
if (response.isSuccess()) {
return response.getBillDownloadUrl();
}
} catch (AlipayApiException e) {
throw new BusinessException(e.getMessage());
}
return StringUtils.EMPTY;
}

/**
* AlipayClient
*
* @param payAccount
* @return
*/
public AlipayClient getByAccount(PayAccount payAccount) {
return new DefaultAlipayClient(payProperties.getAliPayGateway(), payAccount.getAppId(),
payAccount.getAliPrivateKey(), aliPayDataFormat, defaultCharset, payAccount.getAliPublicKey(), defaultSignType);
}

/**
* 下载账单文件
*
* @param downloadUrl
* @return
*/
public List<AliPayBillDetail> downloadBillFile(String downloadUrl) {
List<AliPayBillDetail> aliPayBillDetails = 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();
logger.info("对账单解析,输出文件名称:{}", fileName);
// 压缩包有业务明细和业务明细汇总2个文件,对账用到明细数据,所以排除汇总文件
if (!Objects.isNull(fileName) && fileName.contains(".") && !fileName.contains("汇总")) {
String line;
int i = 0;
// 按行读取数据
while ((line = br.readLine()) != null) {
// 数据行不以 # 为开头
if (!line.startsWith("#")) {
logger.info("解析数据行:{}", line);
// 数据从第2行开始读,第1行是表头
if (i > 0) {
String[] lines = line.split(",", -1);
AliPayBillDetail aliPayBillDetail = AliPayBillDetail.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())
.remark(lines[24].trim())
.build();
aliPayBillDetails.add(aliPayBillDetail);
}
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 aliPayBillDetails;
}
}

业务实践系列(7):对账-支付宝交易账单数据解析

http://blog.gxitsky.com/2020/07/27/Business-07-ali-pay-bill/

作者

光星

发布于

2020-07-27

更新于

2023-03-07

许可协议

评论