下载账单
更新时间:2024.12.11一、下载账单描述
当商户调用申请交易账单/申请资金账单接口,获取到下载账单链接download_url后,需按照V3接口规则生成签名,然后请求下载账单链接download_url获取对应的账单文件。
账单详细字段请参考附录:交易账单详细说明、资金账单详细说明。
二、具体下载步骤
1、接口说明:
请求方式: 【GET】
请求URL: 调用申请账单接口,返回参数“download_url”对应的URL
2、请求下载账单:
以V3接口规则生成签名,并对“download_url”发起请求,获取账单文件,具体示例如下:
申请账单返回参数示例
1{ 2 "hash_type": "SHA1", 3 "hash_value": "79bb0f45fc4c42234a918000b2668d689e2bde04", 4 "download_url": "https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx" 5}
|
2.1、示例代码
curl
Java
GET
1$ curl https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx 2-H 'Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900000001",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJxxxxxxxxxanN+zU+lTgIcH/84nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98xxxxxxx996DE7B47773A8C"'
需配合微信支付工具库 WXPayUtility 使用,请参考 Java
1package com.java.demo; 2 3import com.google.gson.annotations.Expose; 4import com.google.gson.annotations.SerializedName; 5import com.java.utils.WXPayUtility; // 引用微信支付工具库 参考:https://pay.weixin.qq.com/doc/v3/merchant/4014931831 6import java.io.IOException; 7import java.io.UncheckedIOException; 8import java.io.InputStream; 9import java.io.FileOutputStream; 10import java.io.FileInputStream; 11import java.io.IOException; 12import java.security.PrivateKey; 13import java.security.PublicKey; 14import okhttp3.OkHttpClient; 15import okhttp3.Request; 16import okhttp3.RequestBody; 17import okhttp3.ResponseBody; 18import okhttp3.Response; 19import java.net.URI; 20import java.net.URISyntaxException; 21import org.apache.commons.codec.digest.DigestUtils; 22import java.util.zip.GZIPInputStream; 23 24/** 25 * 下载账单 26 */ 27public class DownloadBill { 28 private static String METHOD = "GET"; 29 30 public static void main(String[] args) { 31 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756 32 DownloadBill client = new DownloadBill( 33 "19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 34 "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 35 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 36 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 37 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 38 ); 39 40 DownloadBillRequest request = new DownloadBillRequest(); 41 request.downloadUrl = "https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx"; 42 request.localFilePath = "downloaded_bill.csv"; 43 request.expectedHashType = HashType.SHA1; 44 request.expectedHashValue = "79bb0f45fc4c42234a918000b2668d689e2bde04"; 45 request.tarType = TarType.GZIP; 46 try { 47 client.run(request); 48 49 // TODO: 请求成功,继续业务逻辑 50 System.out.println("File downloaded successfully! Local file path: " + request.localFilePath); 51 } catch (WXPayUtility.ApiException e) { 52 // TODO: 请求失败,根据状态码执行不同的逻辑 53 e.printStackTrace(); 54 } 55 } 56 57 public void run(DownloadBillRequest request) { 58 Request.Builder reqBuilder = new Request.Builder().url(request.downloadUrl); 59 60 String uri = getPathQueryFromUrl(request.downloadUrl); 61 reqBuilder.addHeader("Accept", "application/json"); 62 reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId); 63 reqBuilder.addHeader("Authorization", 64 WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null)); 65 reqBuilder.method(METHOD, null); 66 Request httpRequest = reqBuilder.build(); 67 68 // 发送HTTP请求 69 OkHttpClient client = new OkHttpClient.Builder().build(); 70 try (Response httpResponse = client.newCall(httpRequest).execute()) { 71 if (httpResponse.code() < 200 || httpResponse.code() > 300) { 72 String respBody = WXPayUtility.extractBody(httpResponse); 73 throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers()); 74 } 75 76 // 2XX 成功,流式下载文件 77 ResponseBody body = httpResponse.body(); 78 if (body == null) { 79 throw new IOException("Response body is null"); 80 } 81 // 读取流 82 try (InputStream inputStream = (request.tarType == DownloadBill.TarType.GZIP) 83 ? new GZIPInputStream(body.byteStream()) 84 : body.byteStream(); 85 FileOutputStream outputStream = new FileOutputStream(request.localFilePath)) { 86 87 byte[] buffer = new byte[8096]; 88 int bytesRead; 89 while ((bytesRead = inputStream.read(buffer)) != -1) { 90 outputStream.write(buffer, 0, bytesRead); 91 } 92 outputStream.flush(); 93 } 94 // 下载成功后校验文件SHA1 95 if (request.expectedHashType == HashType.SHA1) { 96 String sha1 = DigestUtils.sha1Hex(new FileInputStream(request.localFilePath)); 97 if (!sha1.equals(request.expectedHashValue)) { 98 throw new IOException("SHA1 checksum mismatch"); 99 } 100 } 101 } catch (IOException e) { 102 throw new UncheckedIOException("Sending request to " + uri + " failed.", e); 103 } 104 } 105 106 private String getPathQueryFromUrl(String url) { 107 try { 108 URI uri = new URI(url); 109 String path = uri.getRawPath(); // /v3/billdownload/file 110 String query = uri.getRawQuery(); // token=xxx&tartype=gzip 111 return (query == null || query.isEmpty()) ? path : path + "?" + query; 112 } catch (URISyntaxException e) { 113 e.printStackTrace(); 114 return ""; 115 } 116 } 117 118 private final String mchid; 119 private final String certificateSerialNo; 120 private final PrivateKey privateKey; 121 private final String wechatPayPublicKeyId; 122 private final PublicKey wechatPayPublicKey; 123 124 public DownloadBill(String mchid, String certificateSerialNo, String privateKeyFilePath, 125 String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) { 126 this.mchid = mchid; 127 this.certificateSerialNo = certificateSerialNo; 128 this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath); 129 this.wechatPayPublicKeyId = wechatPayPublicKeyId; 130 this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath); 131 } 132 133 public enum HashType { 134 @SerializedName("SHA1") 135 SHA1 136 } 137 138 public static class DownloadBillRequest { 139 @SerializedName("download_url") 140 @Expose(serialize = false) 141 public String downloadUrl; 142 143 @SerializedName("local_file_path") 144 @Expose(serialize = false) 145 public String localFilePath; 146 147 @SerializedName("expected_hash_type") 148 @Expose(serialize = false) 149 public HashType expectedHashType; 150 151 @SerializedName("expected_hash_value") 152 @Expose(serialize = false) 153 public String expectedHashValue; 154 155 @SerializedName("tar_type") 156 @Expose(serialize = false) 157 public TarType tarType; 158 } 159 160 public enum TarType { 161 @SerializedName("GZIP") 162 GZIP 163 } 164}
3、常见错误码:
3.1、公共错误码:
状态码 | 错误码 | 描述 | 解决方案 |
---|---|---|---|
400 | PARAM_ERROR | 参数错误 | 请根据错误提示正确传入参数 |
400 | INVALID_REQUEST | HTTP 请求不符合微信支付 APIv3 接口规则 | 请参阅接口规则 |
401 | SIGN_ERROR | 验证不通过 | 请参阅签名常见问题 |
500 | SYSTEM_ERROR | 系统异常,请稍后重试 | 请稍后重试 |
3.2、业务错误码:
状态码 | 错误码 | 描述 | 解决方案 |
---|---|---|---|
400 | INVALID_REQUEST | 参数错误 | 请按第一步申请账单的API指引,重新获取账单地址后再请求 |
403 | NO_AUTH | 权限异常 | 请检查本次请求的商户是否与第一步申请账单API的请求商户一致 |
文档是否有帮助