业务示例代码
更新时间:2025.12.041. 目标
通过本教程的学习,你应该可以:
可以申请交易账单
可以申请资金账单
可以通过申请结果URL下载账单、下载单个子商户/二级商户资金账单
可以正确处理错误码
可以校验下载账单内容的摘要值是否正确
2. 业务处理流程
2.1. 申请交易账单并下载账单
微信支付在每日10点后生成昨日交易账单文件,商户可通过接口获取账单下载链接。账单包含交易金额、时间及营销信息,利于订单核对、退款审查及银行到账确认。详细介绍参考: 产品介绍 。
1package com.java.downloadbilldemo.ecommerce; 2 3import com.java.demo.DownloadBill; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894 4import com.java.demo.GetTradeBill; // 申请交易账单 https://pay.weixin.qq.com/doc/v3/partner/4012760667 5import com.java.utils.WXPayUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4014985777 6 7import static com.java.demo.DownloadBill.*; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894 8import static com.java.demo.GetTradeBill.*; // 申请交易账单 https://pay.weixin.qq.com/doc/v3/partner/4012760667 9 10public class DownloadTradeBillDemo { 11 public static void main(String[] args) { 12 GetTradeBill getTradeBill = new GetTradeBill( 13 "19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 14 "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4012081992 15 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 16 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 17 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 18 ); 19 20 DownloadBill downloadBill = new DownloadBill( 21 "19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 22 "1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4012081992 23 "/path/to/apiclient_key.pem", // 商户API证书私钥文件路径,本地文件路径 24 "PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 25 "/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径 26 ); 27 try { 28 GetTradeBillRequest getTradeBillRequest = new GetTradeBillRequest(); 29 getTradeBillRequest.billDate = "2025-08-11"; // 期望申请下载的账单日期 30 getTradeBillRequest.billType = BillType.ALL; // 期望申请下载的账单类型 31 getTradeBillRequest.tarType = GetTradeBill.TarType.GZIP; // 期望申请下载的账单类型 32 33 QueryBillEntity response = getTradeBill.run(getTradeBillRequest); 34 35 System.out.println("Download URL: " + response.downloadUrl); 36 37 DownloadBillRequest downloadBillRequest = new DownloadBillRequest(); 38 downloadBillRequest.downloadUrl = response.downloadUrl; 39 downloadBillRequest.localFilePath = "test.csv"; // 期望下载的账单文件路径 40 downloadBillRequest.tarType = DownloadBill.TarType.valueOf(getTradeBillRequest.tarType.name()); 41 downloadBillRequest.expectedHashType = DownloadBill.HashType.valueOf(response.hashType.name()); 42 downloadBillRequest.expectedHashValue = response.hashValue; 43 44 downloadBill.run(downloadBillRequest); 45 46 System.out.println("File downloaded successfully! Local file path: " + downloadBillRequest.localFilePath); 47 // TODO: 请求成功,继续业务逻辑 48 } catch (WXPayUtility.ApiException e) { 49 // TODO: 请求失败,根据状态码执行不同的逻辑 50 String errorCode = e.getErrorCode(); 51 if (errorCode != null && errorCode.equals("NO_STATEMENT_EXIST")) { 52 // 错误:账单不存在 53 // 解决方式:请检查当前商户号在指定日期内是否有成功的交易或退款 54 // 描述:说明指定日期无账单文件生成。若无交易,则无需重试 55 } else if (errorCode != null && errorCode.equals("STATEMENT_CREATING")) { 56 // 错误:账单正在生成中 57 // 解决方式:请检查当前商户号在指定日期内是否有成功的交易或退款 58 // 描述:说明指定日期账单正在生成中。若无交易,则无需重试;若有交易,则在T+1日上午10点后再重新下载 59 } else if (errorCode != null && errorCode.equals("SYSTEM_ERROR")) { 60 // 错误:系统错误 61 // 解决方式:稍后重试 62 // 描述:微信支付系统失败,系统失败直接立即重试大概率还会是系统失败,建议等1分钟后再重试 63 } else if (errorCode != null && errorCode.equals("FREQUENCY_LIMITED")) { 64 // 错误:限频报错 65 // 解决方式:稍后重试 66 // 描述: 接口频率限制,直接立即重试大概率还会是系统失败,建议等1分钟后再重试 67 } else if (errorCode != null && errorCode.equals("SIGN_ERROR")) { 68 // 错误:签名错误 69 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 70 // 描述:签名报错,需要确认签名材料和签名流程是否正确 71 } else if (errorCode != null && errorCode.equals("PARAM_ERROR")) { 72 // 错误:参数错误 73 // 解决方式:按照报错返回的message,重新输入请求参数 74 // 描述:参数的类型,长度,或者必填选项没有填写等 75 } else if (errorCode != null && errorCode.equals("INVALID_REQUEST")) { 76 // 错误:请求非法,请求参数正确,但是不符合下载账单业务规则 77 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后再重试 78 // 描述:不符合业务规则的场景如: 79 // - 账单时间超过三个月,不允许申请下载账单 80 } else { 81 // 其他类型错误:稍等一会后重试 82 } 83 } 84 } 85} 86
2.2. 申请资金账单并下载账单
微信支付按天提供服务商各账户的资金流水账单文件,服务商可以通过该接口获取账单文件的下载地址。账单文件详细记录了服务商账户资金操作的相关信息,包括业务单号、收支金额及记账时间等,以便服务商进行核对与确认。详细介绍参考: 产品介绍 。
1package com.java.downloadbilldemo.ecommerce; 2 3import com.java.demo.DownloadBill; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894 4import com.java.demo.DownloadBill.DownloadBillRequest; 5import com.java.demo.GetFundFlowBill; // 申请资金账单 https://pay.weixin.qq.com/doc/v3/partner/4012760672 6import com.java.demo.GetFundFlowBill.FundFlowBillAccountType; 7import com.java.demo.GetFundFlowBill.GetFundFlowBillRequest; 8import com.java.demo.GetFundFlowBill.QueryBillEntity; 9import com.java.utils.WXPayUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4014985777 10 11public class DownloadFundFlowBillDemo { 12 13 public static void main(String[] args) { 14 String mchCode = "19xxxxxxxx"; // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 15 String mchSerialNo = "1DDE55AD98Exxxxxxxxxx"; // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4012081992 16 String mchPrivateKeyPath = "/path/to/apiclient_key.pem"; // 商户API证书私钥文件路径,本地文件路径 17 String mchPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 18 String mchPublicKeyPath = "/path/to/wxp_pub.pem"; // 微信支付公钥文件路径,本地文件路径 19 20 GetFundFlowBill getFundFlowBill = new GetFundFlowBill( 21 mchCode, 22 mchSerialNo, 23 mchPrivateKeyPath, 24 mchPublicKeyId, 25 mchPublicKeyPath 26 ); 27 28 DownloadBill downloadBill = new DownloadBill( 29 mchCode, 30 mchSerialNo, 31 mchPrivateKeyPath, 32 mchPublicKeyId, 33 mchPublicKeyPath 34 ); 35 36 try { 37 GetFundFlowBillRequest getFundFlowBillRequest = new GetFundFlowBillRequest(); 38 getFundFlowBillRequest.billDate = "2025-11-25"; // 期望申请下载的账单日期 39 getFundFlowBillRequest.accountType = FundFlowBillAccountType.BASIC; // 期望申请下载的账单类型 40 getFundFlowBillRequest.tarType = GetFundFlowBill.TarType.GZIP; // 期望申请下载的账单压缩类型 41 42 QueryBillEntity response = getFundFlowBill.run(getFundFlowBillRequest); 43 44 System.out.println("Download URL: " + response.downloadUrl); 45 46 DownloadBillRequest downloadBillRequest = new DownloadBillRequest(); 47 downloadBillRequest.downloadUrl = response.downloadUrl; 48 // 期望下载的账单文件路径(当前目录下 mchCode+accountType+bill_date.csv) 49 downloadBillRequest.localFilePath = mchCode + "_" + getFundFlowBillRequest.accountType.name() + "_" + getFundFlowBillRequest.billDate + ".csv"; 50 downloadBillRequest.tarType = DownloadBill.TarType.valueOf(getFundFlowBillRequest.tarType.name()); 51 downloadBillRequest.expectedHashType = DownloadBill.HashType.valueOf(response.hashType.name()); 52 downloadBillRequest.expectedHashValue = response.hashValue; 53 54 downloadBill.run(downloadBillRequest); 55 56 System.out.println("File downloaded successfully! Local file path: " + downloadBillRequest.localFilePath); 57 58 } catch (WXPayUtility.ApiException e) { 59 // TODO: 请求失败,根据状态码执行不同的逻辑 60 String errorCode = e.getErrorCode(); 61 if (errorCode != null && errorCode.equals("NO_STATEMENT_EXIST")) { 62 // 错误:账单不存在 63 // 解决方式:请检查当前商户号在指定日期内是否有资金变动 64 // 描述:说明指定日期无账单文件生成。若无资金变动,则无需重试 65 } else if (errorCode != null && errorCode.equals("STATEMENT_CREATING")) { 66 // 错误:账单正在生成中 67 // 解决方式:请检查当前商户号在指定日期内是否有资金变动 68 // 描述:说明指定日期账单正在生成中。若无资金变动,则无需重试;若有资金变动,则在T+1日上午10点后再重新下载 69 } else if (errorCode != null && errorCode.equals("SYSTEM_ERROR")) { 70 // 错误:系统错误 71 // 解决方式:稍后重试 72 // 描述:微信支付系统失败,系统失败直接立即重试大概率还会是系统失败,建议等1分钟后再重试 73 } else if (errorCode != null && errorCode.equals("FREQUENCY_LIMITED")) { 74 // 错误:限频报错 75 // 解决方式:稍后重试 76 // 描述:接口频率限制,直接立即重试大概率还会是系统失败,建议等1分钟后再重试 77 } else if (errorCode != null && errorCode.equals("SIGN_ERROR")) { 78 // 错误:签名错误 79 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 80 // 描述:签名报错,需要确认签名材料和签名流程是否正确 81 } else if (errorCode != null && errorCode.equals("PARAM_ERROR")) { 82 // 错误:参数错误 83 // 解决方式:按照报错返回的message,重新输入请求参数 84 // 描述:参数的类型,长度,或者必填选项没有填写等 85 } else if (errorCode != null && errorCode.equals("INVALID_REQUEST")) { 86 // 错误:请求非法,请求参数正确,但是不符合下载账单业务规则 87 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后再重试 88 // 描述:不符合业务规则的场景如: 89 // - 账单时间超过三个月,不允许申请下载账单 90 } else { 91 // 其他类型错误:稍等一会后重试 92 } 93 } 94 } 95} 96
2.3. 申请二级资金账单并下载账单
微信支付按天提供微信支付账户的资金流水账单文件,电商平台可以通过该接口获取二级商户账单文件的下载地址。文件内包含电商平台二级商户资金操作相关的业务单号、收支金额、记账时间等信息,供电商平台进行核对。
1package com.java.downloadbilldemo.ecommerce; 2 3import com.java.demo.DownloadBill; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894 4import com.java.demo.GetAllSubMchFundFlowBill; // 申请二级商户资金账单 https://pay.weixin.qq.com/doc/v3/partner/4012760697 5import com.java.demo.GetAllSubMchFundFlowBill.EncryptBillEntity; 6import com.java.demo.GetAllSubMchFundFlowBill.GetAllSubMchFundFlowBillRequest; 7import com.java.demo.GetAllSubMchFundFlowBill.QueryEncryptBillEntity; 8import com.java.demo.GetAllSubMchFundFlowBill.SubMerchantFundFlowBillAccountType; 9import com.java.utils.WXPayUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4014985777 10 11public class DownloadAllSubMchFundFlowBill { 12 13 public static void main(String[] args) { 14 String billDate = "2025-11-26"; // 账单日期,格式为yyyy-MM-dd 15 SubMerchantFundFlowBillAccountType accountType = SubMerchantFundFlowBillAccountType.ALL; 16 17 try { 18 // 初始化API客户端 19 GetAllSubMchFundFlowBill billApi = createBillApi(); 20 DownloadBill downloadApi = createDownloadApi(); 21 22 // 申请并下载账单 23 QueryEncryptBillEntity response = applyForBill(billApi, accountType, billDate); 24 System.out.println("账单数量: " + response.downloadBillCount); 25 26 // 处理每个账单 27 for (int i = 0; i < response.downloadBillList.size(); i++) { 28 EncryptBillEntity billEntity = response.downloadBillList.get(i); 29 processBill(downloadApi, billEntity, billDate, i + 1, accountType); 30 } 31 32 System.out.println("\n所有账单下载并解密完成!"); 33 } catch (WXPayUtility.ApiException e) { 34 handleApiException(e); 35 } catch (Exception e) { 36 System.err.println("程序执行出错:"); 37 e.printStackTrace(); 38 } 39 } 40 41 /** 42 * 创建账单申请API客户端 43 */ 44 private static GetAllSubMchFundFlowBill createBillApi() { 45 return new GetAllSubMchFundFlowBill( 46 EncryptedBillDownloadHelper.getMchCode(), 47 EncryptedBillDownloadHelper.getMchSerialNo(), 48 EncryptedBillDownloadHelper.getMchPrivateKeyPath(), 49 EncryptedBillDownloadHelper.getMchPublicKeyId(), 50 EncryptedBillDownloadHelper.getMchPublicKeyPath() 51 ); 52 } 53 54 /** 55 * 创建账单下载API客户端 56 */ 57 private static DownloadBill createDownloadApi() { 58 return EncryptedBillDownloadHelper.createDownloadApi(); 59 } 60 61 /** 62 * 申请账单 63 */ 64 private static QueryEncryptBillEntity applyForBill( 65 GetAllSubMchFundFlowBill billApi, 66 SubMerchantFundFlowBillAccountType accountType, 67 String billDate) throws Exception { 68 69 GetAllSubMchFundFlowBillRequest request = new GetAllSubMchFundFlowBillRequest(); 70 request.billDate = billDate; 71 request.accountType = accountType; 72 request.algorithm = GetAllSubMchFundFlowBill.Algorithm.AEAD_AES_256_GCM; 73 request.tarType = null; 74 75 return billApi.run(request); 76 } 77 78 /** 79 * 处理单个账单:下载、解密、校验 80 */ 81 private static void processBill( 82 DownloadBill downloadApi, 83 EncryptBillEntity billEntity, 84 String billDate, 85 int billIndex, 86 SubMerchantFundFlowBillAccountType accountType) throws Exception { 87 88 System.out.println("\n下载第 " + billIndex + " 个账单 (序号: " + billEntity.billSequence + ")"); 89 System.out.println("Download URL: " + billEntity.downloadUrl); 90 91 // 生成文件路径 92 String encryptedFilePath = generateFilePath(billDate, billEntity.billSequence, accountType, true); 93 String decryptedFilePath = generateFilePath(billDate, billEntity.billSequence, accountType, false); 94 95 // 下载加密账单 96 downloadEncryptedBill(downloadApi, billEntity.downloadUrl, encryptedFilePath); 97 98 // 解密账单 99 decryptBill(encryptedFilePath, decryptedFilePath, billEntity); 100 101 // 校验SHA1 102 verifySha1(decryptedFilePath, billEntity); 103 } 104 105 /** 106 * 生成文件路径 107 */ 108 private static String generateFilePath(String billDate, long sequence, 109 SubMerchantFundFlowBillAccountType accountType, boolean encrypted) { 110 String suffix = encrypted ? "_encrypted.csv" : ".csv"; 111 return EncryptedBillDownloadHelper.getMchCode() + "_" + accountType.name() + "_" + billDate + "_" + sequence + suffix; 112 } 113 114 /** 115 * 下载加密账单 116 */ 117 private static void downloadEncryptedBill( 118 DownloadBill downloadApi, 119 String downloadUrl, 120 String filePath) throws Exception { 121 EncryptedBillDownloadHelper.downloadEncryptedBill(downloadApi, downloadUrl, filePath); 122 } 123 124 /** 125 * 解密账单 126 */ 127 private static void decryptBill( 128 String encryptedFilePath, 129 String decryptedFilePath, 130 EncryptBillEntity billEntity) throws Exception { 131 132 // 读取加密文件 133 byte[] encryptedData = EncryptedBillDownloadHelper.readFile(encryptedFilePath); 134 135 // 解密账单 136 EncryptedBillDownloadHelper.decryptBill( 137 encryptedFilePath, 138 decryptedFilePath, 139 billEntity.encryptKey, 140 billEntity.nonce, 141 encryptedData 142 ); 143 } 144 145 /** 146 * 校验SHA1 147 */ 148 private static void verifySha1(String filePath, EncryptBillEntity billEntity) throws Exception { 149 EncryptedBillDownloadHelper.verifySha1(filePath, billEntity.hashValue, billEntity.hashType); 150 } 151 152 /** 153 * 处理API异常 154 */ 155 private static void handleApiException(WXPayUtility.ApiException e) { 156 EncryptedBillDownloadHelper.handleApiException(e); 157 } 158} 159
2.4. 申请单个子商户资金账单并下载账单
微信支付按天提供微信支付账户的资金流水账单文件,服务商可以通过该接口获取子商户账单文件的下载地址。文件内包含子商户资金操作相关的业务单号、收支金额、记账时间等信息,供商户进行核对。
1package com.java.downloadbilldemo.ecommerce; 2 3import com.java.demo.DownloadBill; // 下载账单 https://pay.weixin.qq.com/doc/v3/partner/4012124894 4import com.java.demo.GetSingleSubMchFundFlowBill; // 申请单个子商户资金账单 https://pay.weixin.qq.com/doc/v3/partner/4012760249 5import com.java.demo.GetSingleSubMchFundFlowBill.EncryptBillEntity; 6import com.java.demo.GetSingleSubMchFundFlowBill.GetSingleSubMchFundFlowBillRequest; 7import com.java.demo.GetSingleSubMchFundFlowBill.QueryEncryptBillEntity; 8import com.java.demo.GetSingleSubMchFundFlowBill.SubMchFundFlowBillAccountType; 9import com.java.utils.WXPayUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/partner/4014985777 10 11public class DownloadSingleSubMchFundFlowBillDemo { 12 13 public static void main(String[] args) { 14 String subMchid = "2600173132"; // 子商户号 15 String billDate = "2025-11-25"; // 账单日期,格式为yyyy-MM-dd 16 SubMchFundFlowBillAccountType accountType = SubMchFundFlowBillAccountType.BASIC; 17 18 try { 19 // 初始化API客户端 20 GetSingleSubMchFundFlowBill billApi = createBillApi(); 21 DownloadBill downloadApi = createDownloadApi(); 22 23 // 申请并下载账单 24 QueryEncryptBillEntity response = applyForBill(billApi, subMchid, accountType, billDate); 25 System.out.println("账单数量: " + response.downloadBillCount); 26 27 // 处理每个账单 28 for (int i = 0; i < response.downloadBillList.size(); i++) { 29 EncryptBillEntity billEntity = response.downloadBillList.get(i); 30 processBill(downloadApi, billEntity, subMchid, billDate, i + 1, accountType); 31 } 32 33 System.out.println("\n所有账单下载并解密完成!"); 34 } catch (WXPayUtility.ApiException e) { 35 handleApiException(e); 36 } catch (Exception e) { 37 System.err.println("程序执行出错:"); 38 e.printStackTrace(); 39 } 40 } 41 42 /** 43 * 创建账单申请API客户端 44 */ 45 private static GetSingleSubMchFundFlowBill createBillApi() { 46 return new GetSingleSubMchFundFlowBill( 47 EncryptedBillDownloadHelper.getMchCode(), 48 EncryptedBillDownloadHelper.getMchSerialNo(), 49 EncryptedBillDownloadHelper.getMchPrivateKeyPath(), 50 EncryptedBillDownloadHelper.getMchPublicKeyId(), 51 EncryptedBillDownloadHelper.getMchPublicKeyPath() 52 ); 53 } 54 55 /** 56 * 创建账单下载API客户端 57 */ 58 private static DownloadBill createDownloadApi() { 59 return EncryptedBillDownloadHelper.createDownloadApi(); 60 } 61 62 /** 63 * 申请账单 64 */ 65 private static QueryEncryptBillEntity applyForBill( 66 GetSingleSubMchFundFlowBill billApi, 67 String subMchid, 68 SubMchFundFlowBillAccountType accountType, 69 String billDate) throws Exception { 70 71 GetSingleSubMchFundFlowBillRequest request = new GetSingleSubMchFundFlowBillRequest(); 72 request.subMchid = subMchid; 73 request.billDate = billDate; 74 request.accountType = accountType; 75 request.algorithm = GetSingleSubMchFundFlowBill.Algorithm.AEAD_AES_256_GCM; 76 request.tarType = null; 77 78 return billApi.run(request); 79 } 80 81 /** 82 * 处理单个账单:下载、解密、校验 83 */ 84 private static void processBill( 85 DownloadBill downloadApi, 86 EncryptBillEntity billEntity, 87 String subMchid, 88 String billDate, 89 int billIndex, 90 SubMchFundFlowBillAccountType accountType) throws Exception { 91 92 System.out.println("\n下载第 " + billIndex + " 个账单 (序号: " + billEntity.billSequence + ")"); 93 System.out.println("Download URL: " + billEntity.downloadUrl); 94 95 // 生成文件路径 96 String encryptedFilePath = generateFilePath(subMchid, billDate, billEntity.billSequence, accountType, true); 97 String decryptedFilePath = generateFilePath(subMchid, billDate, billEntity.billSequence, accountType, false); 98 99 // 下载加密账单 100 downloadEncryptedBill(downloadApi, billEntity.downloadUrl, encryptedFilePath); 101 102 // 解密、解压账单 103 decryptBill(encryptedFilePath, decryptedFilePath, billEntity); 104 105 // 校验SHA1 106 verifySha1(decryptedFilePath, billEntity); 107 } 108 109 /** 110 * 生成文件路径 111 */ 112 private static String generateFilePath(String subMchid, String billDate, long sequence, 113 SubMchFundFlowBillAccountType accountType, boolean encrypted) { 114 String suffix = encrypted ? "_encrypted.csv" : ".csv"; 115 return subMchid + "_" + accountType.name() + "_" + billDate + "_" + sequence + suffix; 116 } 117 118 /** 119 * 下载加密账单 120 */ 121 private static void downloadEncryptedBill( 122 DownloadBill downloadApi, 123 String downloadUrl, 124 String filePath) throws Exception { 125 EncryptedBillDownloadHelper.downloadEncryptedBill(downloadApi, downloadUrl, filePath); 126 } 127 128 /** 129 * 解密账单 130 */ 131 private static void decryptBill( 132 String encryptedFilePath, 133 String decryptedFilePath, 134 EncryptBillEntity billEntity) throws Exception { 135 136 // 读取加密文件 137 byte[] encryptedData = EncryptedBillDownloadHelper.readFile(encryptedFilePath); 138 139 // 解密账单 140 EncryptedBillDownloadHelper.decryptBill( 141 encryptedFilePath, 142 decryptedFilePath, 143 billEntity.encryptKey, 144 billEntity.nonce, 145 encryptedData 146 ); 147 } 148 149 /** 150 * 校验SHA1 151 */ 152 private static void verifySha1(String filePath, EncryptBillEntity billEntity) throws Exception { 153 EncryptedBillDownloadHelper.verifySha1(filePath, billEntity.hashValue, billEntity.hashType); 154 } 155 156 /** 157 * 处理API异常 158 */ 159 private static void handleApiException(WXPayUtility.ApiException e) { 160 EncryptedBillDownloadHelper.handleApiException(e); 161 } 162} 163
2.5. 加密账单下载工具类
本工具类用于电商平台场景下的加密账单下载、解密和校验。参考下载单个子商户/二级商户资金账单。被 类DownloadAllSubMchFundFlowBill 和 类DownloadSingleSubMchFundFlowBillDemo 使用。
1package com.java.downloadbilldemo.ecommerce; 2 3import com.java.demo.DownloadBill; 4import com.java.demo.DownloadBill.DownloadBillRequest; 5import com.java.utils.WXPayUtility; 6 7import java.io.FileInputStream; 8import java.io.FileOutputStream; 9import java.nio.charset.StandardCharsets; 10import java.security.PrivateKey; 11 12/** 13 * 加密账单下载通用工具类 14 * <p> 15 * 本工具类用于电商平台场景下的加密账单下载、解密和校验。 16 * 主要功能包括: 17 * <ul> 18 * <li>下载加密账单文件</li> 19 * <li>使用商户私钥解密账单加密密钥(encrypt_key)</li> 20 * <li>使用解密后的密钥解密账单内容(AEAD_AES_256_GCM算法)</li> 21 * <li>校验账单文件的SHA1哈希值</li> 22 * </ul> 23 * </p> 24 * <p> 25 * <b>使用位置:</b> 26 * <ul> 27 * <li>适用于电商平台(ecommerce)场景下的账单下载</li> 28 * <li>被 {@link DownloadAllSubMchFundFlowBill} 和 {@link DownloadSingleSubMchFundFlowBillDemo} 使用</li> 29 * <li>用于处理需要加密的二级商户资金账单</li> 30 * </ul> 31 * </p> 32 * <p> 33 * <b>使用前准备:</b> 34 * <ol> 35 * <li>修改类中的商户配置信息(MCH_CODE、MCH_SERIAL_NO等)</li> 36 * <li>确保证书文件路径正确(MCH_PRIVATE_KEY_PATH、MCH_PUBLIC_KEY_PATH)</li> 37 * <li>确保已正确配置微信支付公钥ID(MCH_PUBLIC_KEY_ID)</li> 38 * </ol> 39 * </p> 40 * <p> 41 * <b>参考文档:</b> 42 * <ul> 43 * <li>申请二级商户资金账单:https://pay.weixin.qq.com/doc/v3/partner/4012760697</li> 44 * <li>申请单个子商户资金账单:https://pay.weixin.qq.com/doc/v3/partner/4012760249</li> 45 * </ul> 46 * </p> 47 */ 48public class EncryptedBillDownloadHelper { 49 50 // 配置属性 51 private static final String MCH_CODE = "19xxxxxxxx"; // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/partner/4013080340 52 private static final String MCH_SERIAL_NO = "1DDE55AD98Exxxxxxxxxx"; // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4012081992 53 private static final String MCH_PRIVATE_KEY_PATH = "/path/to/apiclient_key.pem"; // 商户API证书私钥文件路径,本地文件路径 54 private static final String MCH_PUBLIC_KEY_ID = "PUB_KEY_ID_xxxxxxxxxxxxx"; // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/partner/4013038589 55 private static final String MCH_PUBLIC_KEY_PATH = "/path/to/wxp_pub.pem"; // 微信支付公钥文件路径,本地文件路径 56 57 /** 58 * 获取商户号 59 */ 60 public static String getMchCode() { 61 return MCH_CODE; 62 } 63 64 /** 65 * 获取商户证书序列号 66 */ 67 public static String getMchSerialNo() { 68 return MCH_SERIAL_NO; 69 } 70 71 /** 72 * 获取商户私钥路径 73 */ 74 public static String getMchPrivateKeyPath() { 75 return MCH_PRIVATE_KEY_PATH; 76 } 77 78 /** 79 * 获取微信支付公钥ID 80 */ 81 public static String getMchPublicKeyId() { 82 return MCH_PUBLIC_KEY_ID; 83 } 84 85 /** 86 * 获取微信支付公钥路径 87 */ 88 public static String getMchPublicKeyPath() { 89 return MCH_PUBLIC_KEY_PATH; 90 } 91 92 /** 93 * 创建账单下载API客户端 94 */ 95 public static DownloadBill createDownloadApi() { 96 return new DownloadBill( 97 MCH_CODE, MCH_SERIAL_NO, MCH_PRIVATE_KEY_PATH, 98 MCH_PUBLIC_KEY_ID, MCH_PUBLIC_KEY_PATH 99 ); 100 } 101 102 /** 103 * 下载加密账单 104 */ 105 public static void downloadEncryptedBill( 106 DownloadBill downloadApi, 107 String downloadUrl, 108 String filePath) throws Exception { 109 110 DownloadBillRequest request = new DownloadBillRequest(); 111 request.downloadUrl = downloadUrl; 112 request.localFilePath = filePath; 113 request.tarType = null; 114 request.expectedHashType = null; // 跳过SHA1校验,解密后再校验 115 request.expectedHashValue = null; 116 117 downloadApi.run(request); 118 System.out.println("加密账单下载成功: " + filePath); 119 } 120 121 /** 122 * 解密账单 123 */ 124 public static void decryptBill( 125 String encryptedFilePath, 126 String decryptedFilePath, 127 String encryptKey, 128 String nonce, 129 byte[] encryptedData) throws Exception { 130 131 // 解密密钥 132 String billEncryptKey = decryptEncryptKey(encryptKey); 133 134 // 解密内容 135 byte[] billKeyBytes = billEncryptKey.getBytes(StandardCharsets.UTF_8); 136 byte[] nonceBytes = nonce.getBytes(StandardCharsets.UTF_8); 137 String decryptedContent = WXPayUtility.aesAeadDecrypt(billKeyBytes, null, nonceBytes, encryptedData); 138 139 // 保存明文文件 140 writeFile(decryptedFilePath, decryptedContent); 141 System.out.println("账单解密成功: " + decryptedFilePath); 142 } 143 144 /** 145 * 解密encrypt_key 146 */ 147 public static String decryptEncryptKey(String encryptKey) throws Exception { 148 PrivateKey privateKey = WXPayUtility.loadPrivateKeyFromPath(MCH_PRIVATE_KEY_PATH); 149 return WXPayUtility.rsaOaepDecrypt(privateKey, encryptKey); 150 } 151 152 /** 153 * 校验SHA1 154 * @param filePath 文件路径 155 * @param expectedHashValue 期望的SHA1值 156 * @param hashType 哈希类型(用于类型检查) 157 */ 158 public static void verifySha1(String filePath, String expectedHashValue, Object hashType) throws Exception { 159 // 检查hashType是否为SHA1类型 160 if (hashType == null || !hashType.toString().equals("SHA1")) { 161 return; 162 } 163 164 String actualSha1 = org.apache.commons.codec.digest.DigestUtils.sha1Hex(new FileInputStream(filePath)); 165 if (actualSha1.equals(expectedHashValue)) { 166 System.out.println("SHA1 校验通过: " + actualSha1); 167 } else { 168 System.err.println("警告:SHA1 校验失败!"); 169 System.err.println(" 期望值: " + expectedHashValue); 170 System.err.println(" 实际值: " + actualSha1); 171 } 172 } 173 174 /** 175 * 读取文件 176 */ 177 public static byte[] readFile(String filePath) throws Exception { 178 FileInputStream fis = new FileInputStream(filePath); 179 byte[] data = fis.readAllBytes(); 180 fis.close(); 181 return data; 182 } 183 184 /** 185 * 写入文件 186 */ 187 public static void writeFile(String filePath, String content) throws Exception { 188 FileOutputStream fos = new FileOutputStream(filePath); 189 fos.write(content.getBytes(StandardCharsets.UTF_8)); 190 fos.close(); 191 } 192 193 /** 194 * 处理API异常 195 * @param e API异常 196 */ 197 public static void handleApiException(WXPayUtility.ApiException e) { 198 // TODO: 请求失败,根据状态码执行不同的逻辑 199 String errorCode = e.getErrorCode(); 200 if (errorCode == null) { 201 // API异常,可通过 e.getStatusCode() 和 e.getErrorMessage() 获取详细信息 202 } else if (errorCode.equals("NO_STATEMENT_EXIST")) { 203 // 错误:账单不存在 204 // 解决方式:请检查子商户是否在指定日期有资金变动 205 // 描述:说明指定日期无账单文件生成。若无交易,则无需重试 206 } else if (errorCode.equals("STATEMENT_CREATING")) { 207 // 错误:账单正在生成中 208 // 解决方式:请检查子商户是否在指定日期有资金变动 209 // 描述:说明指定日期账单正在生成中。若无交易,则无需重试;若有交易,则在T+1日上午10点后再重新下载 210 } else if (errorCode.equals("SYSTEM_ERROR")) { 211 // 错误:系统错误 212 // 解决方式:稍后重试 213 // 描述:微信支付系统失败,系统失败直接立即重试大概率还会是系统失败,建议等1分钟后再重试 214 } else if (errorCode.equals("FREQUENCY_LIMITED")) { 215 // 错误:限频报错 216 // 解决方式:稍后重试 217 // 描述:接口频率限制,直接立即重试大概率还会是系统失败,建议等1分钟后再重试 218 } else if (errorCode.equals("SIGN_ERROR")) { 219 // 错误:签名错误 220 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 221 // 描述:签名报错,需要确认签名材料和签名流程是否正确 222 } else if (errorCode.equals("PARAM_ERROR")) { 223 // 错误:参数错误 224 // 解决方式:按照报错返回的message,重新输入请求参数 225 // 描述:参数的类型,长度,或者必填选项没有填写等 226 } else if (errorCode.equals("INVALID_REQUEST")) { 227 // 错误:请求非法,请求参数正确,但是不符合下载账单业务规则 228 // 解决方式:根据具体message查看具体哪里不符合业务规则,如果可以修改参数达到符合业务规则的修改请求符合业务规则之后再重试 229 // 描述:不符合业务规则的场景如: 230 // - 账单时间超过三个月,不允许申请下载账单 231 } else { 232 // 其他类型错误:稍等一会后重试 233 } 234 } 235} 236
文档是否有帮助

