---
url: "https://pay.weixin.qq.com/doc/v3/partner/4016062108.md"
title: "业务示例代码"
doc_id: 4016062108
---

>更新时间：2025.12.04

# 1\. 目标

通过本教程的学习，你应该可以：

- 可以[申请交易账单](https://pay.weixin.qq.com/doc/v3/partner/4012760667.md)

- 可以[申请资金账单](https://pay.weixin.qq.com/doc/v3/partner/4012760672.md)

- 可以[申请二级商户资金账单](https://pay.weixin.qq.com/doc/v3/partner/4012760697.md)

- 可以[申请单个子商户资金账单](https://pay.weixin.qq.com/doc/v3/partner/4012760249.md)

- 可以通过申请结果URL[下载账单](https://pay.weixin.qq.com/doc/v3/partner/4012124894.md)、[下载单个子商户/二级商户资金账单](https://pay.weixin.qq.com/doc/v3/partner/4014314390.md)

- 可以正确处理错误码

- 可以校验下载账单内容的摘要值是否正确


# 2\. 业务处理流程



## 2.1. 申请交易账单并下载账单

微信支付在每日10点后生成昨日交易账单文件，商户可通过接口获取账单下载链接。账单包含交易金额、时间及营销信息，利于订单核对、退款审查及银行到账确认。详细介绍参考： [产品介绍](https://pay.weixin.qq.com/doc/v3/partner/4013080592.md)。

```
package com.java.downloadbilldemo.ecommerce;

import com.java.demo.DownloadBill; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894
import com.java.demo.GetTradeBill; // 申请交易账单 https://pay.weixin.qq.com/doc/v3/partner/4012760667
import com.java.utils.WXPayUtility; // 引用微信支付工具库，参考：https://pay.weixin.qq.com/doc/v3/partner/4014985777

import static com.java.demo.DownloadBill.*; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894
import static com.java.demo.GetTradeBill.*; // 申请交易账单 https://pay.weixin.qq.com/doc/v3/partner/4012760667

public class DownloadTradeBillDemo {
    public static void main(String[] args) {
        GetTradeBill getTradeBill = new GetTradeBill(
                "19xxxxxxxx", // 商户号，是由微信支付系统生成并分配给每个商户的唯一标识符，商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
                "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4012081992
                "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径，本地文件路径
                "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
                "/path/to/wxp_pub.pem" // 微信支付公钥文件路径，本地文件路径
        );

        DownloadBill downloadBill = new DownloadBill(
                "19xxxxxxxx", // 商户号，是由微信支付系统生成并分配给每个商户的唯一标识符，商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
                "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4012081992
                "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径，本地文件路径
                "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
                "/path/to/wxp_pub.pem" // 微信支付公钥文件路径，本地文件路径
        );
        try {
            GetTradeBillRequest getTradeBillRequest = new GetTradeBillRequest();
            getTradeBillRequest.billDate = "2025-08-11"; // 期望申请下载的账单日期
            getTradeBillRequest.billType = BillType.ALL; // 期望申请下载的账单类型
            getTradeBillRequest.tarType = GetTradeBill.TarType.GZIP; // 期望申请下载的账单类型

            QueryBillEntity response = getTradeBill.run(getTradeBillRequest);

            System.out.println("Download URL: " + response.downloadUrl);

            DownloadBillRequest downloadBillRequest = new DownloadBillRequest();
            downloadBillRequest.downloadUrl = response.downloadUrl;
            downloadBillRequest.localFilePath = "test.csv"; // 期望下载的账单文件路径
            downloadBillRequest.tarType = DownloadBill.TarType.valueOf(getTradeBillRequest.tarType.name());
            downloadBillRequest.expectedHashType = DownloadBill.HashType.valueOf(response.hashType.name());
            downloadBillRequest.expectedHashValue = response.hashValue;

            downloadBill.run(downloadBillRequest);

            System.out.println("File downloaded successfully! Local file path: " + downloadBillRequest.localFilePath);
            // TODO: 请求成功，继续业务逻辑
        } catch (WXPayUtility.ApiException e) {
            // TODO: 请求失败，根据状态码执行不同的逻辑
            String errorCode = e.getErrorCode();
            if (errorCode != null && errorCode.equals("NO_STATEMENT_EXIST")) {
                // 错误：账单不存在
                // 解决方式：请检查当前商户号在指定日期内是否有成功的交易或退款
                // 描述：说明指定日期无账单文件生成。若无交易，则无需重试
            } else if (errorCode != null && errorCode.equals("STATEMENT_CREATING")) {
                // 错误：账单正在生成中
                // 解决方式：请检查当前商户号在指定日期内是否有成功的交易或退款
                // 描述：说明指定日期账单正在生成中。若无交易，则无需重试；若有交易，则在T+1日上午10点后再重新下载
            } else if (errorCode != null && errorCode.equals("SYSTEM_ERROR")) {
                // 错误：系统错误
                // 解决方式：稍后重试
                // 描述：微信支付系统失败，系统失败直接立即重试大概率还会是系统失败，建议等1分钟后再重试
            } else if (errorCode != null && errorCode.equals("FREQUENCY_LIMITED")) {
                // 错误：限频报错
                // 解决方式：稍后重试
                // 描述： 接口频率限制，直接立即重试大概率还会是系统失败，建议等1分钟后再重试
            } else if (errorCode != null && errorCode.equals("SIGN_ERROR")) {
                // 错误：签名错误
                // 解决方式：检查平台商户证书序列号，证书私钥文件，公钥ID，公钥文件，同时确认下签名过程是不是按照微信支付的签名方式
                // 描述：签名报错，需要确认签名材料和签名流程是否正确
            } else if (errorCode != null && errorCode.equals("PARAM_ERROR")) {
                // 错误：参数错误
                // 解决方式：按照报错返回的message，重新输入请求参数
                // 描述：参数的类型，长度，或者必填选项没有填写等
            } else if (errorCode != null && errorCode.equals("INVALID_REQUEST")) {
                // 错误：请求非法，请求参数正确，但是不符合下载账单业务规则
                // 解决方式：根据具体message查看具体哪里不符合业务规则，如果可以修改参数达到符合业务规则的修改请求符合业务规则之后再重试
                // 描述：不符合业务规则的场景如：
                // - 账单时间超过三个月，不允许申请下载账单
            } else {
                // 其他类型错误：稍等一会后重试
            }
        }
    }
}

```

## 2.2. 申请资金账单并下载账单

微信支付按天提供服务商各账户的资金流水账单文件，服务商可以通过该接口获取账单文件的下载地址。账单文件详细记录了服务商账户资金操作的相关信息，包括业务单号、收支金额及记账时间等，以便服务商进行核对与确认。详细介绍参考： [产品介绍](https://pay.weixin.qq.com/doc/v3/partner/4013080592.md)。

```
package com.java.downloadbilldemo.ecommerce;

import com.java.demo.DownloadBill; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894
import com.java.demo.DownloadBill.DownloadBillRequest;
import com.java.demo.GetFundFlowBill; // 申请资金账单 https://pay.weixin.qq.com/doc/v3/partner/4012760672
import com.java.demo.GetFundFlowBill.FundFlowBillAccountType;
import com.java.demo.GetFundFlowBill.GetFundFlowBillRequest;
import com.java.demo.GetFundFlowBill.QueryBillEntity;
import com.java.utils.WXPayUtility; // 引用微信支付工具库，参考：https://pay.weixin.qq.com/doc/v3/partner/4014985777

public class DownloadFundFlowBillDemo {

    public static void main(String[] args) {
        String mchCode = "19xxxxxxxx";  // 商户号，是由微信支付系统生成并分配给每个商户的唯一标识符，商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
        String mchSerialNo = "1DDE55AD98Exxxxxxxxxx"; // 商户API证书序列号，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4012081992
        String mchPrivateKeyPath = "/path/to/apiclient_key.pem"; // 商户API证书私钥文件路径，本地文件路径
        String mchPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; // 微信支付公钥ID，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
        String mchPublicKeyPath = "/path/to/wxp_pub.pem"; // 微信支付公钥文件路径，本地文件路径

        GetFundFlowBill getFundFlowBill = new GetFundFlowBill(
                mchCode,
                mchSerialNo,
                mchPrivateKeyPath,
                mchPublicKeyId,
                mchPublicKeyPath
        );

        DownloadBill downloadBill = new DownloadBill(
                mchCode,
                mchSerialNo,
                mchPrivateKeyPath,
                mchPublicKeyId,
                mchPublicKeyPath
        );

        try {
            GetFundFlowBillRequest getFundFlowBillRequest = new GetFundFlowBillRequest();
            getFundFlowBillRequest.billDate = "2025-11-25"; // 期望申请下载的账单日期
            getFundFlowBillRequest.accountType = FundFlowBillAccountType.BASIC; // 期望申请下载的账单类型
            getFundFlowBillRequest.tarType = GetFundFlowBill.TarType.GZIP; // 期望申请下载的账单压缩类型

            QueryBillEntity response = getFundFlowBill.run(getFundFlowBillRequest);

            System.out.println("Download URL: " + response.downloadUrl);

            DownloadBillRequest downloadBillRequest = new DownloadBillRequest();
            downloadBillRequest.downloadUrl = response.downloadUrl;
            // 期望下载的账单文件路径(当前目录下 mchCode+accountType+bill_date.csv)
            downloadBillRequest.localFilePath = mchCode + "_" + getFundFlowBillRequest.accountType.name() + "_" + getFundFlowBillRequest.billDate + ".csv";
            downloadBillRequest.tarType = DownloadBill.TarType.valueOf(getFundFlowBillRequest.tarType.name());
            downloadBillRequest.expectedHashType = DownloadBill.HashType.valueOf(response.hashType.name());
            downloadBillRequest.expectedHashValue = response.hashValue;

            downloadBill.run(downloadBillRequest);

            System.out.println("File downloaded successfully! Local file path: " + downloadBillRequest.localFilePath);

        } catch (WXPayUtility.ApiException e) {
            // TODO: 请求失败，根据状态码执行不同的逻辑
            String errorCode = e.getErrorCode();
            if (errorCode != null && errorCode.equals("NO_STATEMENT_EXIST")) {
                // 错误：账单不存在
                // 解决方式：请检查当前商户号在指定日期内是否有资金变动
                // 描述：说明指定日期无账单文件生成。若无资金变动，则无需重试
            } else if (errorCode != null && errorCode.equals("STATEMENT_CREATING")) {
                // 错误：账单正在生成中
                // 解决方式：请检查当前商户号在指定日期内是否有资金变动
                // 描述：说明指定日期账单正在生成中。若无资金变动，则无需重试；若有资金变动，则在T+1日上午10点后再重新下载
            } else if (errorCode != null && errorCode.equals("SYSTEM_ERROR")) {
                // 错误：系统错误
                // 解决方式：稍后重试
                // 描述：微信支付系统失败，系统失败直接立即重试大概率还会是系统失败，建议等1分钟后再重试
            } else if (errorCode != null && errorCode.equals("FREQUENCY_LIMITED")) {
                // 错误：限频报错
                // 解决方式：稍后重试
                // 描述：接口频率限制，直接立即重试大概率还会是系统失败，建议等1分钟后再重试
            } else if (errorCode != null && errorCode.equals("SIGN_ERROR")) {
                // 错误：签名错误
                // 解决方式：检查平台商户证书序列号，证书私钥文件，公钥ID，公钥文件，同时确认下签名过程是不是按照微信支付的签名方式
                // 描述：签名报错，需要确认签名材料和签名流程是否正确
            } else if (errorCode != null && errorCode.equals("PARAM_ERROR")) {
                // 错误：参数错误
                // 解决方式：按照报错返回的message，重新输入请求参数
                // 描述：参数的类型，长度，或者必填选项没有填写等
            } else if (errorCode != null && errorCode.equals("INVALID_REQUEST")) {
                // 错误：请求非法，请求参数正确，但是不符合下载账单业务规则
                // 解决方式：根据具体message查看具体哪里不符合业务规则，如果可以修改参数达到符合业务规则的修改请求符合业务规则之后再重试
                // 描述：不符合业务规则的场景如：
                // - 账单时间超过三个月，不允许申请下载账单
            } else {
                // 其他类型错误：稍等一会后重试
            }
        }
    }
}

```

## 2.3. 申请二级资金账单并下载账单

微信支付按天提供微信支付账户的资金流水账单文件，电商平台可以通过该接口获取二级商户账单文件的下载地址。文件内包含电商平台二级商户资金操作相关的业务单号、收支金额、记账时间等信息，供电商平台进行核对。

```
package com.java.downloadbilldemo.ecommerce;

import com.java.demo.DownloadBill; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894
import com.java.demo.GetAllSubMchFundFlowBill; // 申请二级商户资金账单 https://pay.weixin.qq.com/doc/v3/partner/4012760697
import com.java.demo.GetAllSubMchFundFlowBill.EncryptBillEntity;
import com.java.demo.GetAllSubMchFundFlowBill.GetAllSubMchFundFlowBillRequest;
import com.java.demo.GetAllSubMchFundFlowBill.QueryEncryptBillEntity;
import com.java.demo.GetAllSubMchFundFlowBill.SubMerchantFundFlowBillAccountType;
import com.java.utils.WXPayUtility; // 引用微信支付工具库，参考：https://pay.weixin.qq.com/doc/v3/partner/4014985777

public class DownloadAllSubMchFundFlowBill {

    public static void main(String[] args) {
        String billDate = "2025-11-26"; // 账单日期，格式为yyyy-MM-dd
        SubMerchantFundFlowBillAccountType accountType = SubMerchantFundFlowBillAccountType.ALL;

        try {
            // 初始化API客户端
            GetAllSubMchFundFlowBill billApi = createBillApi();
            DownloadBill downloadApi = createDownloadApi();

            // 申请并下载账单
            QueryEncryptBillEntity response = applyForBill(billApi, accountType, billDate);
            System.out.println("账单数量: " + response.downloadBillCount);

            // 处理每个账单
            for (int i = 0; i < response.downloadBillList.size(); i++) {
                EncryptBillEntity billEntity = response.downloadBillList.get(i);
                processBill(downloadApi, billEntity, billDate, i + 1, accountType);
            }

            System.out.println("\n所有账单下载并解密完成！");
        } catch (WXPayUtility.ApiException e) {
            handleApiException(e);
        } catch (Exception e) {
            System.err.println("程序执行出错：");
            e.printStackTrace();
        }
    }

    /**
     * 创建账单申请API客户端
     */
    private static GetAllSubMchFundFlowBill createBillApi() {
        return new GetAllSubMchFundFlowBill(
                EncryptedBillDownloadHelper.getMchCode(),
                EncryptedBillDownloadHelper.getMchSerialNo(),
                EncryptedBillDownloadHelper.getMchPrivateKeyPath(),
                EncryptedBillDownloadHelper.getMchPublicKeyId(),
                EncryptedBillDownloadHelper.getMchPublicKeyPath()
        );
    }

    /**
     * 创建账单下载API客户端
     */
    private static DownloadBill createDownloadApi() {
        return EncryptedBillDownloadHelper.createDownloadApi();
    }

    /**
     * 申请账单
     */
    private static QueryEncryptBillEntity applyForBill(
            GetAllSubMchFundFlowBill billApi,
            SubMerchantFundFlowBillAccountType accountType,
            String billDate) throws Exception {

        GetAllSubMchFundFlowBillRequest request = new GetAllSubMchFundFlowBillRequest();
        request.billDate = billDate;
        request.accountType = accountType;
        request.algorithm = GetAllSubMchFundFlowBill.Algorithm.AEAD_AES_256_GCM;
        request.tarType = null;

        return billApi.run(request);
    }

    /**
     * 处理单个账单：下载、解密、校验
     */
    private static void processBill(
            DownloadBill downloadApi,
            EncryptBillEntity billEntity,
            String billDate,
            int billIndex,
            SubMerchantFundFlowBillAccountType accountType) throws Exception {

        System.out.println("\n下载第 " + billIndex + " 个账单 (序号: " + billEntity.billSequence + ")");
        System.out.println("Download URL: " + billEntity.downloadUrl);

        // 生成文件路径
        String encryptedFilePath = generateFilePath(billDate, billEntity.billSequence, accountType, true);
        String decryptedFilePath = generateFilePath(billDate, billEntity.billSequence, accountType, false);

        // 下载加密账单
        downloadEncryptedBill(downloadApi, billEntity.downloadUrl, encryptedFilePath);

        // 解密账单
        decryptBill(encryptedFilePath, decryptedFilePath, billEntity);

        // 校验SHA1
        verifySha1(decryptedFilePath, billEntity);
    }

    /**
     * 生成文件路径
     */
    private static String generateFilePath(String billDate, long sequence,
                                          SubMerchantFundFlowBillAccountType accountType, boolean encrypted) {
        String suffix = encrypted ? "_encrypted.csv" : ".csv";
        return EncryptedBillDownloadHelper.getMchCode() + "_" + accountType.name() + "_" + billDate + "_" + sequence + suffix;
    }

    /**
     * 下载加密账单
     */
    private static void downloadEncryptedBill(
            DownloadBill downloadApi,
            String downloadUrl,
            String filePath) throws Exception {
        EncryptedBillDownloadHelper.downloadEncryptedBill(downloadApi, downloadUrl, filePath);
    }

    /**
     * 解密账单
     */
    private static void decryptBill(
            String encryptedFilePath,
            String decryptedFilePath,
            EncryptBillEntity billEntity) throws Exception {

        // 读取加密文件
        byte[] encryptedData = EncryptedBillDownloadHelper.readFile(encryptedFilePath);

        // 解密账单
        EncryptedBillDownloadHelper.decryptBill(
                encryptedFilePath,
                decryptedFilePath,
                billEntity.encryptKey,
                billEntity.nonce,
                encryptedData
        );
    }

    /**
     * 校验SHA1
     */
    private static void verifySha1(String filePath, EncryptBillEntity billEntity) throws Exception {
        EncryptedBillDownloadHelper.verifySha1(filePath, billEntity.hashValue, billEntity.hashType);
    }

    /**
     * 处理API异常
     */
    private static void handleApiException(WXPayUtility.ApiException e) {
        EncryptedBillDownloadHelper.handleApiException(e);
    }
}

```

## 2.4. 申请单个子商户资金账单并下载账单

微信支付按天提供微信支付账户的资金流水账单文件，服务商可以通过该接口获取子商户账单文件的下载地址。文件内包含子商户资金操作相关的业务单号、收支金额、记账时间等信息，供商户进行核对。

```
package com.java.downloadbilldemo.ecommerce;

import com.java.demo.DownloadBill; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894
import com.java.demo.GetSingleSubMchFundFlowBill; // 申请单个子商户资金账单 https://pay.weixin.qq.com/doc/v3/partner/4012760249
import com.java.demo.GetSingleSubMchFundFlowBill.EncryptBillEntity;
import com.java.demo.GetSingleSubMchFundFlowBill.GetSingleSubMchFundFlowBillRequest;
import com.java.demo.GetSingleSubMchFundFlowBill.QueryEncryptBillEntity;
import com.java.demo.GetSingleSubMchFundFlowBill.SubMchFundFlowBillAccountType;
import com.java.utils.WXPayUtility; // 引用微信支付工具库，参考：https://pay.weixin.qq.com/doc/v3/partner/4014985777

public class DownloadSingleSubMchFundFlowBillDemo {

    public static void main(String[] args) {
        String subMchid = "2600173132"; // 子商户号
        String billDate = "2025-11-25"; // 账单日期，格式为yyyy-MM-dd
        SubMchFundFlowBillAccountType accountType = SubMchFundFlowBillAccountType.BASIC;

        try {
            // 初始化API客户端
            GetSingleSubMchFundFlowBill billApi = createBillApi();
            DownloadBill downloadApi = createDownloadApi();

            // 申请并下载账单
            QueryEncryptBillEntity response = applyForBill(billApi, subMchid, accountType, billDate);
            System.out.println("账单数量: " + response.downloadBillCount);

            // 处理每个账单
            for (int i = 0; i < response.downloadBillList.size(); i++) {
                EncryptBillEntity billEntity = response.downloadBillList.get(i);
                processBill(downloadApi, billEntity, subMchid, billDate, i + 1, accountType);
            }

            System.out.println("\n所有账单下载并解密完成！");
        } catch (WXPayUtility.ApiException e) {
            handleApiException(e);
        } catch (Exception e) {
            System.err.println("程序执行出错：");
            e.printStackTrace();
        }
    }

    /**
     * 创建账单申请API客户端
     */
    private static GetSingleSubMchFundFlowBill createBillApi() {
        return new GetSingleSubMchFundFlowBill(
                EncryptedBillDownloadHelper.getMchCode(),
                EncryptedBillDownloadHelper.getMchSerialNo(),
                EncryptedBillDownloadHelper.getMchPrivateKeyPath(),
                EncryptedBillDownloadHelper.getMchPublicKeyId(),
                EncryptedBillDownloadHelper.getMchPublicKeyPath()
        );
    }

    /**
     * 创建账单下载API客户端
     */
    private static DownloadBill createDownloadApi() {
        return EncryptedBillDownloadHelper.createDownloadApi();
    }

    /**
     * 申请账单
     */
    private static QueryEncryptBillEntity applyForBill(
            GetSingleSubMchFundFlowBill billApi,
            String subMchid,
            SubMchFundFlowBillAccountType accountType,
            String billDate) throws Exception {

        GetSingleSubMchFundFlowBillRequest request = new GetSingleSubMchFundFlowBillRequest();
        request.subMchid = subMchid;
        request.billDate = billDate;
        request.accountType = accountType;
        request.algorithm = GetSingleSubMchFundFlowBill.Algorithm.AEAD_AES_256_GCM;
        request.tarType = null;

        return billApi.run(request);
    }

    /**
     * 处理单个账单：下载、解密、校验
     */
    private static void processBill(
            DownloadBill downloadApi,
            EncryptBillEntity billEntity,
            String subMchid,
            String billDate,
            int billIndex,
            SubMchFundFlowBillAccountType accountType) throws Exception {

        System.out.println("\n下载第 " + billIndex + " 个账单 (序号: " + billEntity.billSequence + ")");
        System.out.println("Download URL: " + billEntity.downloadUrl);

        // 生成文件路径
        String encryptedFilePath = generateFilePath(subMchid, billDate, billEntity.billSequence, accountType, true);
        String decryptedFilePath = generateFilePath(subMchid, billDate, billEntity.billSequence, accountType, false);

        // 下载加密账单
        downloadEncryptedBill(downloadApi, billEntity.downloadUrl, encryptedFilePath);

        // 解密、解压账单
        decryptBill(encryptedFilePath, decryptedFilePath, billEntity);

        // 校验SHA1
        verifySha1(decryptedFilePath, billEntity);
    }

    /**
     * 生成文件路径
     */
    private static String generateFilePath(String subMchid, String billDate, long sequence,
                                          SubMchFundFlowBillAccountType accountType, boolean encrypted) {
        String suffix = encrypted ? "_encrypted.csv" : ".csv";
        return subMchid + "_" + accountType.name() + "_" + billDate + "_" + sequence + suffix;
    }

    /**
     * 下载加密账单
     */
    private static void downloadEncryptedBill(
            DownloadBill downloadApi,
            String downloadUrl,
            String filePath) throws Exception {
        EncryptedBillDownloadHelper.downloadEncryptedBill(downloadApi, downloadUrl, filePath);
    }

    /**
     * 解密账单
     */
    private static void decryptBill(
            String encryptedFilePath,
            String decryptedFilePath,
            EncryptBillEntity billEntity) throws Exception {

        // 读取加密文件
        byte[] encryptedData = EncryptedBillDownloadHelper.readFile(encryptedFilePath);

        // 解密账单
        EncryptedBillDownloadHelper.decryptBill(
                encryptedFilePath,
                decryptedFilePath,
                billEntity.encryptKey,
                billEntity.nonce,
                encryptedData
        );
    }

    /**
     * 校验SHA1
     */
    private static void verifySha1(String filePath, EncryptBillEntity billEntity) throws Exception {
        EncryptedBillDownloadHelper.verifySha1(filePath, billEntity.hashValue, billEntity.hashType);
    }

    /**
     * 处理API异常
     */
    private static void handleApiException(WXPayUtility.ApiException e) {
        EncryptedBillDownloadHelper.handleApiException(e);
    }
}

```

## 2.5. 加密账单下载工具类

本工具类用于电商平台场景下的加密账单下载、解密和校验。参考 [下载单个子商户/二级商户资金账单](https://pay.weixin.qq.com/doc/v3/partner/4014314390.md)。被 类DownloadAllSubMchFundFlowBill 和 类DownloadSingleSubMchFundFlowBillDemo 使用。

```
package com.java.downloadbilldemo.ecommerce;

import com.java.demo.DownloadBill;
import com.java.demo.DownloadBill.DownloadBillRequest;
import com.java.utils.WXPayUtility;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;

/**
 * 加密账单下载通用工具类
 * <p>
 * 本工具类用于电商平台场景下的加密账单下载、解密和校验。
 * 主要功能包括：
 * <ul>
 *   <li>下载加密账单文件</li>
 *   <li>使用商户私钥解密账单加密密钥（encrypt_key）</li>
 *   <li>使用解密后的密钥解密账单内容（AEAD_AES_256_GCM算法）</li>
 *   <li>校验账单文件的SHA1哈希值</li>
 * </ul>
 * </p>
 * <p>
 * <b>使用位置：</b>
 * <ul>
 *   <li>适用于电商平台（ecommerce）场景下的账单下载</li>
 *   <li>被 {@link DownloadAllSubMchFundFlowBill} 和 {@link DownloadSingleSubMchFundFlowBillDemo} 使用</li>
 *   <li>用于处理需要加密的二级商户资金账单</li>
 * </ul>
 * </p>
 * <p>
 * <b>使用前准备：</b>
 * <ol>
 *   <li>修改类中的商户配置信息（MCH_CODE、MCH_SERIAL_NO等）</li>
 *   <li>确保证书文件路径正确（MCH_PRIVATE_KEY_PATH、MCH_PUBLIC_KEY_PATH）</li>
 *   <li>确保已正确配置微信支付公钥ID（MCH_PUBLIC_KEY_ID）</li>
 * </ol>
 * </p>
 * <p>
 * <b>参考文档：</b>
 * <ul>
 *   <li>申请二级商户资金账单：https://pay.weixin.qq.com/doc/v3/partner/4012760697</li>
 *   <li>申请单个子商户资金账单：https://pay.weixin.qq.com/doc/v3/partner/4012760249</li>
 * </ul>
 * </p>
 */
public class EncryptedBillDownloadHelper {

    // 配置属性
    private static final String MCH_CODE = "19xxxxxxxx"; // 商户号，是由微信支付系统生成并分配给每个商户的唯一标识符，商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340
    private static final String MCH_SERIAL_NO = "1DDE55AD98Exxxxxxxxxx";  // 商户API证书序列号，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4012081992
    private static final String MCH_PRIVATE_KEY_PATH = "/path/to/apiclient_key.pem"; // 商户API证书私钥文件路径，本地文件路径
    private static final String MCH_PUBLIC_KEY_ID = "PUB_KEY_ID_xxxxxxxxxxxxx"; // 微信支付公钥ID，如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589
    private static final String MCH_PUBLIC_KEY_PATH = "/path/to/wxp_pub.pem";  // 微信支付公钥文件路径，本地文件路径

    /**
     * 获取商户号
     */
    public static String getMchCode() {
        return MCH_CODE;
    }

    /**
     * 获取商户证书序列号
     */
    public static String getMchSerialNo() {
        return MCH_SERIAL_NO;
    }

    /**
     * 获取商户私钥路径
     */
    public static String getMchPrivateKeyPath() {
        return MCH_PRIVATE_KEY_PATH;
    }

    /**
     * 获取微信支付公钥ID
     */
    public static String getMchPublicKeyId() {
        return MCH_PUBLIC_KEY_ID;
    }

    /**
     * 获取微信支付公钥路径
     */
    public static String getMchPublicKeyPath() {
        return MCH_PUBLIC_KEY_PATH;
    }

    /**
     * 创建账单下载API客户端
     */
    public static DownloadBill createDownloadApi() {
        return new DownloadBill(
                MCH_CODE, MCH_SERIAL_NO, MCH_PRIVATE_KEY_PATH,
                MCH_PUBLIC_KEY_ID, MCH_PUBLIC_KEY_PATH
        );
    }

    /**
     * 下载加密账单
     */
    public static void downloadEncryptedBill(
            DownloadBill downloadApi,
            String downloadUrl,
            String filePath) throws Exception {

        DownloadBillRequest request = new DownloadBillRequest();
        request.downloadUrl = downloadUrl;
        request.localFilePath = filePath;
        request.tarType = null;
        request.expectedHashType = null; // 跳过SHA1校验，解密后再校验
        request.expectedHashValue = null;

        downloadApi.run(request);
        System.out.println("加密账单下载成功: " + filePath);
    }

    /**
     * 解密账单
     */
    public static void decryptBill(
            String encryptedFilePath,
            String decryptedFilePath,
            String encryptKey,
            String nonce,
            byte[] encryptedData) throws Exception {

        // 解密密钥
        String billEncryptKey = decryptEncryptKey(encryptKey);

        // 解密内容
        byte[] billKeyBytes = billEncryptKey.getBytes(StandardCharsets.UTF_8);
        byte[] nonceBytes = nonce.getBytes(StandardCharsets.UTF_8);
        String decryptedContent = WXPayUtility.aesAeadDecrypt(billKeyBytes, null, nonceBytes, encryptedData);

        // 保存明文文件
        writeFile(decryptedFilePath, decryptedContent);
        System.out.println("账单解密成功: " + decryptedFilePath);
    }

    /**
     * 解密encrypt_key
     */
    public static String decryptEncryptKey(String encryptKey) throws Exception {
        PrivateKey privateKey = WXPayUtility.loadPrivateKeyFromPath(MCH_PRIVATE_KEY_PATH);
        return WXPayUtility.rsaOaepDecrypt(privateKey, encryptKey);
    }

    /**
     * 校验SHA1
     * @param filePath 文件路径
     * @param expectedHashValue 期望的SHA1值
     * @param hashType 哈希类型（用于类型检查）
     */
    public static void verifySha1(String filePath, String expectedHashValue, Object hashType) throws Exception {
        // 检查hashType是否为SHA1类型
        if (hashType == null || !hashType.toString().equals("SHA1")) {
            return;
        }

        String actualSha1 = org.apache.commons.codec.digest.DigestUtils.sha1Hex(new FileInputStream(filePath));
        if (actualSha1.equals(expectedHashValue)) {
            System.out.println("SHA1 校验通过: " + actualSha1);
        } else {
            System.err.println("警告：SHA1 校验失败！");
            System.err.println("  期望值: " + expectedHashValue);
            System.err.println("  实际值: " + actualSha1);
        }
    }

    /**
     * 读取文件
     */
    public static byte[] readFile(String filePath) throws Exception {
        FileInputStream fis = new FileInputStream(filePath);
        byte[] data = fis.readAllBytes();
        fis.close();
        return data;
    }

    /**
     * 写入文件
     */
    public static void writeFile(String filePath, String content) throws Exception {
        FileOutputStream fos = new FileOutputStream(filePath);
        fos.write(content.getBytes(StandardCharsets.UTF_8));
        fos.close();
    }

    /**
     * 处理API异常
     * @param e API异常
     */
    public static void handleApiException(WXPayUtility.ApiException e) {
        // TODO: 请求失败，根据状态码执行不同的逻辑
        String errorCode = e.getErrorCode();
        if (errorCode == null) {
            // API异常，可通过 e.getStatusCode() 和 e.getErrorMessage() 获取详细信息
        } else if (errorCode.equals("NO_STATEMENT_EXIST")) {
            // 错误：账单不存在
            // 解决方式：请检查子商户是否在指定日期有资金变动
            // 描述：说明指定日期无账单文件生成。若无交易，则无需重试
        } else if (errorCode.equals("STATEMENT_CREATING")) {
            // 错误：账单正在生成中
            // 解决方式：请检查子商户是否在指定日期有资金变动
            // 描述：说明指定日期账单正在生成中。若无交易，则无需重试；若有交易，则在T+1日上午10点后再重新下载
        } else if (errorCode.equals("SYSTEM_ERROR")) {
            // 错误：系统错误
            // 解决方式：稍后重试
            // 描述：微信支付系统失败，系统失败直接立即重试大概率还会是系统失败，建议等1分钟后再重试
        } else if (errorCode.equals("FREQUENCY_LIMITED")) {
            // 错误：限频报错
            // 解决方式：稍后重试
            // 描述：接口频率限制，直接立即重试大概率还会是系统失败，建议等1分钟后再重试
        } else if (errorCode.equals("SIGN_ERROR")) {
            // 错误：签名错误
            // 解决方式：检查平台商户证书序列号，证书私钥文件，公钥ID，公钥文件，同时确认下签名过程是不是按照微信支付的签名方式
            // 描述：签名报错，需要确认签名材料和签名流程是否正确
        } else if (errorCode.equals("PARAM_ERROR")) {
            // 错误：参数错误
            // 解决方式：按照报错返回的message，重新输入请求参数
            // 描述：参数的类型，长度，或者必填选项没有填写等
        } else if (errorCode.equals("INVALID_REQUEST")) {
            // 错误：请求非法，请求参数正确，但是不符合下载账单业务规则
            // 解决方式：根据具体message查看具体哪里不符合业务规则，如果可以修改参数达到符合业务规则的修改请求符合业务规则之后再重试
            // 描述：不符合业务规则的场景如：
            // - 账单时间超过三个月，不允许申请下载账单
        } else {
            // 其他类型错误：稍等一会后重试
        }
    }
}

```