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

star2017 1年前 ⋅ 85 阅读

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

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

交易账单

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

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

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

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

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

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

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

账单数据

账单业务明细示例:

#支付宝业务明细查询
#账号:[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]

账单解析

实体类

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

数据解析

/**
 * @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;
    }
}
更多内容请访问:IT源点

相关文章推荐

全部评论: 0

    我有话说: