---
url: "https://pay.weixin.qq.com/doc/v3/merchant/4013070401.md"
title: "下载账单"
doc_id: 4013070401
---

>更新时间：2024.11.25

## 一、下载账单描述

当商户调用[申请交易账单](https://pay.weixin.qq.com/doc/v3/merchant/4013071227.md)/[申请资金账单](https://pay.weixin.qq.com/doc/v3/merchant/4013071235.md)接口，获取到下载账单链接download\_url后，需按照[V3接口规则生成签名](https://pay.weixin.qq.com/doc/v3/merchant/4012365337.md)，然后请求下载账单链接download\_url获取对应的账单文件。

账单详细字段请参考附录：[交易账单详细说明](https://pay.weixin.qq.com/doc/v3/merchant/4013071246.md)、[资金账单详细说明](https://pay.weixin.qq.com/doc/v3/merchant/4013071249.md)。

## 二、具体下载步骤

### 1、接口说明

请求方式： 【GET】

请求URL： 调用申请账单接口，返回参数“download\_url”对应的URL

### 2、请求下载账单

以[V3接口规则](https://pay.weixin.qq.com/doc/v3/merchant/4012365337.md)生成签名，并对“download\_url”发起请求，获取账单文件，具体示例如下：

申请账单返回参数示例

```
{
	"hash_type": "SHA1",
	"hash_value": "79bb0f45fc4c42234a918000b2668d689e2bde04",
    "download_url": "https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx"
}
```

注意事项：

- 下载账单响应的请求头信息中不包含签名值，因此无需验签。

- 下载账单链接download\_url的有效时间为5min，请及时下载。

- 建议商户在下载完成后，比对实际文件的哈希值与申请账单接口返回的哈希值是否一致，以确认下载账单数据的完整性。

- 微信将在次日9点开始生成前一天的对账单，建议商户在次日10点后获取。


#### 2.1、示例代码

curl

Java

> GET

```
$ curl https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx
-H 'Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900000001",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJxxxxxxxxxanN+zU+lTgIcH/84nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98xxxxxxx996DE7B47773A8C"'
```

需配合微信支付工具库 WXPayUtility 使用，请参考 [Java](https://pay.weixin.qq.com/doc/v3/merchant/4014931831.md)

```
package com.java.demo;

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.java.utils.WXPayUtility; // 引用微信支付工具库 参考:https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import okhttp3.Response;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.commons.codec.digest.DigestUtils;
import java.util.zip.GZIPInputStream;

/**
 * 下载账单
 */
public class DownloadBill {
    private static String METHOD = "GET";

    public static void main(String[] args) {
        // TODO: 请准备商户开发必要参数，参考：https://pay.weixin.qq.com/doc/v3/merchant/4013070756
        DownloadBill client = new DownloadBill(
                "19xxxxxxxx", // 商户号，是由微信支付系统生成并分配给每个商户的唯一标识符，商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
                "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号，如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
                "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径，本地文件路径
                "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID，如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
                "/path/to/wxp_pub.pem" // 微信支付公钥文件路径，本地文件路径
        );

        DownloadBillRequest request = new DownloadBillRequest();
        request.downloadUrl = "https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx";
        request.localFilePath = "downloaded_bill.csv";
        request.expectedHashType = HashType.SHA1;
        request.expectedHashValue = "79bb0f45fc4c42234a918000b2668d689e2bde04";
        request.tarType = TarType.GZIP;
        try {
            client.run(request);

            // TODO: 请求成功，继续业务逻辑
            System.out.println("File downloaded successfully! Local file path: " + request.localFilePath);
        } catch (WXPayUtility.ApiException e) {
            // TODO: 请求失败，根据状态码执行不同的逻辑
            e.printStackTrace();
        }
    }

    public void run(DownloadBillRequest request) {
        Request.Builder reqBuilder = new Request.Builder().url(request.downloadUrl);

        String uri = getPathQueryFromUrl(request.downloadUrl);
        reqBuilder.addHeader("Accept", "application/json");
        reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
        reqBuilder.addHeader("Authorization",
                WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
        reqBuilder.method(METHOD, null);
        Request httpRequest = reqBuilder.build();

        // 发送HTTP请求
        OkHttpClient client = new OkHttpClient.Builder().build();
        try (Response httpResponse = client.newCall(httpRequest).execute()) {
            if (httpResponse.code() < 200 || httpResponse.code() > 300) {
                String respBody = WXPayUtility.extractBody(httpResponse);
                throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
            }

            // 2XX 成功，流式下载文件
            ResponseBody body = httpResponse.body();
            if (body == null) {
                throw new IOException("Response body is null");
            }
            // 读取流
            try (InputStream inputStream = (request.tarType == DownloadBill.TarType.GZIP)
                    ? new GZIPInputStream(body.byteStream())
                    : body.byteStream();
                    FileOutputStream outputStream = new FileOutputStream(request.localFilePath)) {

                byte[] buffer = new byte[8096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                outputStream.flush();
            }
            // 下载成功后校验文件SHA1
            if (request.expectedHashType == HashType.SHA1) {
                String sha1 = DigestUtils.sha1Hex(new FileInputStream(request.localFilePath));
                if (!sha1.equals(request.expectedHashValue)) {
                    throw new IOException("SHA1 checksum mismatch");
                }
            }
        } catch (IOException e) {
            throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
        }
    }

    private String getPathQueryFromUrl(String url) {
        try {
            URI uri = new URI(url);
            String path = uri.getRawPath(); // /v3/billdownload/file
            String query = uri.getRawQuery(); // token=xxx&tartype=gzip
            return (query == null || query.isEmpty()) ? path : path + "?" + query;
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return "";
        }
    }

    private final String mchid;
    private final String certificateSerialNo;
    private final PrivateKey privateKey;
    private final String wechatPayPublicKeyId;
    private final PublicKey wechatPayPublicKey;

    public DownloadBill(String mchid, String certificateSerialNo, String privateKeyFilePath,
            String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
        this.mchid = mchid;
        this.certificateSerialNo = certificateSerialNo;
        this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
        this.wechatPayPublicKeyId = wechatPayPublicKeyId;
        this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
    }

    public enum HashType {
        @SerializedName("SHA1")
        SHA1
    }

    public static class DownloadBillRequest {
        @SerializedName("download_url")
        @Expose(serialize = false)
        public String downloadUrl;

        @SerializedName("local_file_path")
        @Expose(serialize = false)
        public String localFilePath;

        @SerializedName("expected_hash_type")
        @Expose(serialize = false)
        public HashType expectedHashType;

        @SerializedName("expected_hash_value")
        @Expose(serialize = false)
        public String expectedHashValue;

        @SerializedName("tar_type")
        @Expose(serialize = false)
        public TarType tarType;
    }

    public enum TarType {
        @SerializedName("GZIP")
        GZIP
    }
}
```

### 3、常见错误码

#### 3.1、公共错误码

| 状态码 | 错误码 | 描述 | 解决方案 |
| --- | --- | --- | --- |
| 400 | PARAM\_ERROR | 参数错误 | 请根据错误提示正确传入参数 |
| 400 | INVALID\_REQUEST | HTTP 请求不符合微信支付 APIv3 接口规则 | 请参阅[接口规则](https://pay.weixin.qq.com/doc/v3/merchant/4012081709.md) |
| 401 | SIGN\_ERROR | 验证不通过 | 请参阅[签名常见问题](https://pay.weixin.qq.com/doc/v3/merchant/4012365347.md) |
| 500 | SYSTEM\_ERROR | 系统异常，请稍后重试 | 请稍后重试 |

#### 3.2、业务错误码

| 状态码 | 错误码 | 描述 | 解决方案 |
| --- | --- | --- | --- |
| 400 | INVALID\_REQUEST | 参数错误 | 请按第一步申请账单的API指引，重新获取账单地址后再请求 |
| 403 | NO\_AUTH | 权限异常 | 请检查本次请求的商户是否与第一步申请账单API的请求商户一致 |