业务示例代码
更新时间:2026.05.191. 目标
通过本教程的学习你应该可以:
了解代金券创建、管理、发放、查询的全流程
对于代金券批次,你可以:
创建、激活、暂停、重启代金券批次;
设定查询条件,查询的代金券批次列表;
查询某一个指定批次的详细信息、可用商户和可用单品;
下载指定批次代金券的退款明细和核销明细。
对于代金券,你可以:
根据商户号查询指定用户拥有的代金券,并可指定批次号、代金券状态等参数查询;
发放指定批次的代金券给用户;
根据代金券号查询代金券详细。
除此之外,你还可以:
设置和查询代金券消息的通知地址,接受代金券核销事件的回调通知;
在系统中上传图片并获得图片的 url 地址,图片url可在微信支付营销相关的API使用。
本文档中的代码 Demo 中均使用了平台提供的工具类 WXPayUtility :Java_SDK&开发工具
在使用代金券功能之前,请先开通代金券功能。
2. 前置流程
2.1. 设置和查询代金券消息通知地址
如果需要获得代金券核销的实时反馈,商户可以在制券前先设置好代金券消息的通知地址,以接受代金券核销事件的回调通知。
下面给出了设置代金券消息通知地址和查询代金券消息通知地址的示例代码:
1package com.java.coupon; 2 3import com.java.utils.WXPayUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/merchant/4014931831 4 5// 使用商户平台设置消息代金券消息通知地址接口,参考https://pay.weixin.qq.com/doc/v3/merchant/4012464198 6import com.java.demo.SetCallback; 7import static com.java.demo.SetCallback.*; 8 9// 使用商户平台查询消息通知地址接口,参考 https://pay.weixin.qq.com/doc/v3/merchant/4012464070 10import com.java.demo.QueryCallback; 11import static com.java.demo.QueryCallback.*; 12 13public class InitCouponDemo { 14 15 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756 16 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 17 // https://pay.weixin.qq.com/doc/v3/merchant/4013070756 18 private static String mchid = "19xxxxxxxx"; 19 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 20 private static String certificateSerialNo = "1DDE55AD98Exxxxxxxxxx"; 21 // 商户API证书私钥文件路径,本地文件路径 22 private static String privateKeyFilePath = "/path/to/apiclient_key.pem"; 23 // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 24 private static String wechatPayPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; 25 // 微信支付公钥文件路径,本地文件路径 26 private static String wechatPayPublicKeyFilePath = "/path/to/wxp_pub.pem"; 27 28 public static void main(String[] args) { 29 InitCouponDemo demo = new InitCouponDemo(); 30 31 // 1. 设置代金券消息通知地址 32 String notifyUrl = "https://pay.weixin.qq.com"; // 商户的消息通知地址 33 demo.setCallback(mchid, notifyUrl); 34 35 // 由于系统处理需要时间,建议间隔 1 分钟以上进行操作 36 37 // 2. 可以查询商户设置的消息通知地址 38 String notifyUrlResp = demo.queryCallback(mchid); 39 } 40 41 // 查询商户号为 queryMchid 的商户的消息通知地址 42 // 返回该商户号的消息通知地址 43 public String queryCallback(String queryMchid) { 44 45 QueryCallback client = new QueryCallback( 46 mchid, 47 certificateSerialNo, 48 privateKeyFilePath, 49 wechatPayPublicKeyId, 50 wechatPayPublicKeyFilePath 51 ); 52 53 QueryCallbackRequest request = new QueryCallbackRequest(); 54 request.mchid = queryMchid; // 需要查找回调地址的商户号 55 56 try { 57 Callback response = client.run(request); 58 59 // 请求成功,返回商户号和消息通知地址 60 String mchidResp = request.mchid; 61 String notifyUrlResp = response.notifyUrl; 62 63 return notifyUrlResp; 64 65 } catch (WXPayUtility.ApiException e) { 66 // 请求失败,根据状态码执行不同的逻辑 67 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 68 // 错误:系统错误 69 // 解决方式:稍后(建议等 1 分钟后)原单重试 70 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 71 // 错误:参数错误 72 // 解决方式:请根据错误提示正确传入参数 73 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 74 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 75 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 76 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 77 // 错误:验证不通过 78 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 79 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 80 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 81 // 错误:商户号与AppID不匹配 82 // 解决方式:请绑定调用接口的商户号和AppID后重试 83 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 84 // 错误:商户号不合法 85 // 解决方式:请输入正确的商户号 86 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 87 // 错误:批次预算不足 88 // 解决方式:请补充预算 89 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 90 // 错误:请求失败 91 // 解决方式:请根据具体的错误信息修改请求 92 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 93 // 错误:请求过于频繁 94 // 解决方式:稍后重试(建议等待一分钟后原单重试) 95 } else { 96 // 其他错误码 97 // 解决方式:请查看返回的错误信息 98 } 99 e.printStackTrace(); 100 101 return null; 102 } 103 } 104 105 // 设置商户号 callbackMchid 的消息通知地址为 notifyUrl 106 public void setCallback(String callbackMchid, String notifyUrl) { 107 108 SetCallback client = new SetCallback( 109 mchid, 110 certificateSerialNo, 111 privateKeyFilePath, 112 wechatPayPublicKeyId, 113 wechatPayPublicKeyFilePath 114 ); 115 116 SetCallbackRequest request = new SetCallbackRequest(); 117 118 // 请参阅实际文档进行传参 https://pay.weixin.qq.com/doc/v3/merchant/4012464198 119 request.mchid = callbackMchid; // 需要设置消息通知地址的商户号 120 request.notifyUrl = notifyUrl; // 新设置的消息通知地址 121 122 // 如果商户接收营销事件通知的开关。 123 // true:开启推送 / false:停止推送 124 // 目前暂不支持设置参数为 false,如果想关闭通知请在商户平台关闭券的回调通知 125 request._switch = true; 126 127 try { 128 SetCallbackResponse response = client.run(request); 129 130 // 请求成功,返回新设置的消息通知地址和更新的时间 131 String notifyUrlResp = response.notifyUrl; 132 String updateTime = response.updateTime; 133 134 } catch (WXPayUtility.ApiException e) { 135 // 请求失败,根据状态码执行不同的逻辑 136 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 137 // 错误:系统错误 138 // 解决方式:稍后(建议等 1 分钟后)原单重试 139 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 140 // 错误:参数错误 141 // 解决方式:请根据错误提示正确传入参数 142 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 143 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 144 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 145 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 146 // 错误:验证不通过 147 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 148 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 149 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 150 // 错误:商户号与AppID不匹配 151 // 解决方式:请绑定调用接口的商户号和AppID后重试 152 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 153 // 错误:商户号不合法 154 // 解决方式:请输入正确的商户号 155 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 156 // 错误:批次预算不足 157 // 解决方式:请补充预算 158 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 159 // 错误:请求失败 160 // 解决方式:请根据具体的错误信息修改请求 161 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 162 // 错误:请求过于频繁 163 // 解决方式:稍后重试(建议等待一分钟后原单重试) 164 } else { 165 // 其他错误码 166 // 解决方式:请查看返回的错误信息 167 } 168 e.printStackTrace(); 169 } 170 } 171}
2.2. 图片上传(营销专用)
商户可以通过接口上传图片,上传后可以获得图片 url 地址,图片url可在微信支付营销相关的API使用。
上传图片使用到 wechatpay-apache-httpclient 项目
下载项目后使用以下代码上传图片到营销平台:
1package com.wechat.pay.contrib.apache.httpclient; 2 3import static org.apache.http.HttpStatus.SC_OK; 4import static org.junit.Assert.assertEquals; 5 6import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier; 7import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; 8import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; 9import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; 10import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; 11import java.io.File; 12import java.io.FileInputStream; 13import java.io.IOException; 14import java.io.InputStream; 15import java.net.URI; 16import java.net.URISyntaxException; 17import java.nio.charset.StandardCharsets; 18import java.security.PrivateKey; 19import org.apache.commons.codec.digest.DigestUtils; 20import org.apache.http.HttpEntity; 21import org.apache.http.client.methods.CloseableHttpResponse; 22import org.apache.http.impl.client.CloseableHttpClient; 23import org.apache.http.util.EntityUtils; 24 25public class UploadPhoto { 26 27 // 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756 28 private static final String privateKey = "-----BEGIN PRIVATE KEY-----\n" // 你的商户私钥 29 + "your private key" 30 + "-----END PRIVATE KEY-----\n"; 31 private static final String merchantId = "merchant_id"; // 商户号 32 private static final String merchantSerialNumber = "merchant_serial_number"; // 商户证书序列号 33 private static final String apiV3Key = "api_v3_key"; // API V3密钥 34 35 public static void main(String[] args) throws IOException, URISyntaxException { 36 37 PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(privateKey); 38 39 //使用自动更新的签名验证器,不需要传入证书 40 AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier( 41 new WechatPay2Credentials(merchantId, new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)), 42 apiV3Key.getBytes(StandardCharsets.UTF_8)); 43 44 CloseableHttpClient httpClient = WechatPayHttpClientBuilder.create() 45 .withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey) 46 .withValidator(new WechatPay2Validator(verifier)) 47 .build(); 48 49 // 需要上传的图片路径 50 String filePath = "/home/filepath/to/tres.png"; 51 52 URI uri = new URI("https://api.mch.weixin.qq.com/v3/marketing/favor/media/image-upload"); 53 54 File file = new File(filePath); 55 try (FileInputStream fileIs = new FileInputStream(file)) { 56 String sha256 = DigestUtils.sha256Hex(fileIs); 57 try (InputStream is = new FileInputStream(file)) { 58 WechatPayUploadHttpPost request = new WechatPayUploadHttpPost.Builder(uri) 59 .withImage(file.getName(), sha256, is) 60 .build(); 61 62 try (CloseableHttpResponse response = httpClient.execute(request)) { 63 assertEquals(SC_OK, response.getStatusLine().getStatusCode()); 64 HttpEntity entity = response.getEntity(); 65 // 返回上传成功的图片的路径 66 String s = EntityUtils.toString(entity); 67 System.out.println(s); 68 } 69 } 70 } 71 httpClient.close(); 72 } 73}
3. 代金券批次的管理和发放
3.1. 业务流程
商户可以使用相关 API 管理本商户的代金券批次,进行包括创建、激活、暂停、重启以及查询代金券批次相关信息等一系列操作,代金券批次的状态机如下:
需要注意的是:
对于激活代金券批次:
如果是预充值代金券,激活时会从商户运营账户余额中锁定本批次的营销资金。
如果是免充值代金券,激活时不锁定资金。
对于发放代金券操作:
当在代金券处于“运行中”状态时,商家可以进行发放代金券操作。
商户可在H5活动页面、商户小程序、商户APP等自有场景内调用该接口完成发券,商户默认只允许发放本商户号(调用发券接口的商户号)创建的代金券,如需发放其他商户商户创建的代金券,请参考常见问题Q1。
跨商户发券时,请求参数中除了 stock_id 和 stock_creator_mchid 为创建方提供的数据,其他的所有调用数据都由发放方提供。
3.2. 示例代码
1package com.java.coupon; 2 3import com.java.utils.WXPayUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/merchant/4014931831 4// 使用商户平台创建代金券批次接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012534633 5import com.java.demo.CreateCouponStock; 6import static com.java.demo.CreateCouponStock.*; 7 8// 使用商户平台激活代金券接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012460137 9import com.java.demo.StartStock; 10import static com.java.demo.StartStock.*; 11 12// 使用商户平台暂停批次接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012460305 13import com.java.demo.PauseStock; 14import static com.java.demo.PauseStock.*; 15 16// 使用商户平台重启代金券批次接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012460411 17import com.java.demo.RestartStock; 18import static com.java.demo.RestartStock.*; 19 20// 使用商户平台发放指定批次的代金券接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012463767 21import com.java.demo.SendCoupon; 22import static com.java.demo.SendCoupon.*; 23 24import java.util.ArrayList; 25 26public class CouponManagementDemo { 27 28 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756 29 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 30 // https://pay.weixin.qq.com/doc/v3/merchant/4013070756 31 private static String mchid = "19xxxxxxxx"; 32 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 33 private static String certificateSerialNo = "1DDE55AD98Exxxxxxxxxx"; 34 // 商户API证书私钥文件路径,本地文件路径 35 private static String privateKeyFilePath = "/path/to/apiclient_key.pem"; 36 // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 37 private static String wechatPayPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; 38 // 微信支付公钥文件路径,本地文件路径 39 private static String wechatPayPublicKeyFilePath = "/path/to/wxp_pub.pem"; 40 41 public static void main(String[] args) { 42 43 CouponManagementDemo demo = new CouponManagementDemo(); 44 45 // 1. 创建一个新的代金券批次 46 // 创建后返回创建成功的代金券批次 ID 47 String stockId = demo.createCouponStock(); 48 49 // 如果未创建成功,返回 null,请检查错误信息并重新配置代金券后发布 50 if (stockId == null) { 51 return; 52 } 53 54 // 由于系统处理需要时间,建议间隔 1 分钟以上进行操作 55 56 // 2. 代金券批次创建成功后,需要激活才能使用 57 // 第二个参数为制券商户号,此处的示例为本商户制券,在实际使用中请注意制券商户号与批次号的对应关系 58 demo.startStock(stockId, mchid); 59 60 // 由于系统处理需要时间,建议间隔 1 分钟以上进行操作 61 62 // 3. 代金券激活后,商户可以发放代金券给用户 63 // 第二个参数为制券商户号,此处的示例为本商户制券,在实际使用中请注意制券商户号与批次号的对应关系 64 // 第三个参数是微信为发券方商户分配的公众账号ID 65 // 第四个参数是需要发放代金券的用户在该 appid 下对应的 openid 66 // 制券成功后返回发放成功的代金券 ID,失败返回 null 67 String couponId = demo.sendCoupon(stockId, mchid, "example_appid", "example_user_openid"); 68 69 // 由于系统处理需要时间,建议间隔 1 分钟以上进行操作 70 71 // 4. 根据需要可以暂停运营中的代价券批次 72 // 第二个参数为制券商户号,此处的示例为本商户制券,在实际使用中请注意制券商户号与批次号的对应关系 73 demo.pauseStock(stockId, mchid); 74 75 // 由于系统处理需要时间,建议间隔 1 分钟以上进行操作 76 77 // 5. 批次暂停后,可以根据需要重启代金券批次 78 // 重启代金券批次后,可以再次发放代金券给用户 79 // 第二个参数为制券商户号,此处的示例为本商户制券,在实际使用中请注意制券商户号与批次号的对应关系 80 demo.restartStock(stockId, mchid); 81 82 } 83 84 public String createCouponStock() { 85 86 CreateCouponStock client = new CreateCouponStock( 87 mchid, 88 certificateSerialNo, 89 privateKeyFilePath, 90 wechatPayPublicKeyId, 91 wechatPayPublicKeyFilePath 92 ); 93 94 // 请参阅实际文档进行传参 https://pay.weixin.qq.com/doc/v3/merchant/4012534633 95 CreateCouponStockRequest request = new CreateCouponStockRequest(); 96 97 request.stockName = "微信支付代金券"; // 代金券批次名称 98 request.comment = "零售批次2"; // 代金券批次注释 99 request.belongMerchant = "merchant_id"; // 代金券批次所属商户号 100 request.availableBeginTime = "2025-08-14T17:05:00.120+08:00"; // 代金券批次生效时间 101 request.availableEndTime = "2025-08-17T13:29:35.120+08:00"; // 代金券批次结束时间 102 103 // 代金券批次的发放规则 104 request.stockUseRule = new com.java.demo.CreateCouponStock.StockRule(); 105 request.stockUseRule.maxCoupons = 100L; 106 request.stockUseRule.maxAmount = 50000L; 107 request.stockUseRule.maxAmountByDay = 500L; 108 request.stockUseRule.maxCouponsPerUser = 10L; 109 request.stockUseRule.naturalPersonLimit = true; 110 request.stockUseRule.preventApiAbuse = true; 111 112 // 代金券详情页的相关信息 113 request.patternInfo = new com.java.demo.CreateCouponStock.PatternInfo(); 114 request.patternInfo.description = "微信支付营销代金券"; 115 request.patternInfo.merchantLogo = "CDN地址"; 116 request.patternInfo.merchantName = "微信支付"; 117 request.patternInfo.backgroundColor = com.java.demo.CreateCouponStock.BackgroundColor.COLOR010; 118 request.patternInfo.couponImage = "https://qpic.cn/xxx"; 119 request.patternInfo.jumpTarget = com.java.demo.CreateCouponStock.JumpTarget.PAYMENT_CODE; 120 request.patternInfo.miniProgramAppid = "wx23232232323"; 121 request.patternInfo.miniProgramPath = "/path/index/index"; 122 123 // 代金券核销规则 124 request.couponUseRule = new com.java.demo.CreateCouponStock.CouponRule(); 125 request.couponUseRule.couponAvailableTime = new com.java.demo.CreateCouponStock.FavorAvailableTime(); 126 request.couponUseRule.couponAvailableTime.fixAvailableTime = new com.java.demo.CreateCouponStock.FixedAvailableTime(); 127 request.couponUseRule.couponAvailableTime.fixAvailableTime.availableWeekDay = new ArrayList<>(); 128 { 129 request.couponUseRule.couponAvailableTime.fixAvailableTime.availableWeekDay.add(1L); 130 } 131 ; 132 request.couponUseRule.couponAvailableTime.fixAvailableTime.beginTime = 0L; 133 request.couponUseRule.couponAvailableTime.fixAvailableTime.endTime = 3600L; 134 request.couponUseRule.couponAvailableTime.secondDayAvailable = false; 135 request.couponUseRule.couponAvailableTime.availableTimeAfterReceive = 7L; 136 request.couponUseRule.fixedNormalCoupon = new com.java.demo.CreateCouponStock.FixedValueStockMsg(); 137 request.couponUseRule.fixedNormalCoupon.couponAmount = 500L; 138 request.couponUseRule.fixedNormalCoupon.transactionMinimum = 1000L; 139 request.couponUseRule.goodsTag = new ArrayList<>(); 140 { 141 request.couponUseRule.goodsTag.add("123321"); 142 } 143 ; 144 request.couponUseRule.tradeType = new ArrayList<>(); 145 { 146 request.couponUseRule.tradeType.add(com.java.demo.CreateCouponStock.TradeType.MICROAPP); 147 } 148 ; 149 request.couponUseRule.combineUse = true; 150 request.couponUseRule.availableItems = new ArrayList<>(); 151 { 152 request.couponUseRule.availableItems.add("123321"); 153 } 154 ; 155 request.couponUseRule.unavailableItems = new ArrayList<>(); 156 { 157 request.couponUseRule.unavailableItems.add("789987"); 158 } 159 ; 160 request.couponUseRule.availableMerchants = new ArrayList<>(); 161 { 162 request.couponUseRule.availableMerchants.add("2600044199"); 163 } 164 ; 165 request.couponUseRule.limitCard = new com.java.demo.CreateCouponStock.CardLimitation(); 166 request.couponUseRule.limitCard.name = "精粹白金"; 167 request.couponUseRule.limitCard.bin = new ArrayList<>(); 168 { 169 request.couponUseRule.limitCard.bin.add("62542688"); 170 } 171 ; 172 request.couponUseRule.limitPay = new ArrayList<>(); 173 { 174 request.couponUseRule.limitPay.add("BCZ_DEBIT"); 175 } 176 ; 177 178 request.noCash = false; // 营销经费,true:免充值代金券;false:预充值代金券。 179 request.stockType = "NORMAL"; // 批次类型,目前仅支持:NORMAL(固定面额满减券批次) 180 181 // 商户创建批次凭据号(格式:商户id+日期+流水号),可包含英文字母,数字,|,_,*,-等内容,不允许出现其他不合法符号,商户侧需保持 182 // 商户单据号全局唯一。 183 request.outRequestNo = "example_out_request_no2"; 184 185 // 扩展属性字段,按json格式。该字段暂未开放,不需要填写 186 request.extInfo = "{'exinfo1':'1234','exinfo2':'3456'}"; 187 try { 188 CreateCouponStockResponse response = client.run(request); 189 190 // 请求成功,可以获取创建代金券批次的批次号和创建时间信息。 191 String stockId = response.stockId; 192 String createTime = response.createTime; 193 194 return stockId; 195 196 } catch (WXPayUtility.ApiException e) { 197 // 创建代金券批次失败异常处理逻辑 198 // 由于 api 文档可能更新,所有的错误可以参考 https://pay.weixin.qq.com/doc/v3/merchant/4012534633 199 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 200 // 错误:系统错误 201 // 解决方式:稍后(建议等 1 分钟后)原单重试 202 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 203 // 错误:参数错误 204 // 解决方式:请根据错误提示正确传入参数 205 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 206 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 207 // 解决方式:请结合返回的错误信息,参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 208 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 209 // 错误:验证不通过 210 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 211 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 212 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 213 // 错误:商户号与AppID不匹配 214 // 解决方式:请绑定调用接口的商户号和AppID后重试 215 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 216 // 错误:商户号不合法 217 // 解决方式:请输入正确的商户号 218 } else if (e.getErrorCode().equals("NO_AUTH")) { 219 // 错误:你配置的信息需要开通特殊权限 220 // 解决方式:参考 https://developers.weixin.qq.com/community/develop/article/doc/000c6aefe208488a14eb7819651013 221 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 222 // 错误:批次预算不足 223 // 解决方式:请补充预算 224 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 225 // 错误:请求失败 226 // 解决方式:请根据具体的错误信息修改请求 227 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 228 // 错误:请求过于频繁 229 // 解决方式:稍后重试(建议等待 1 分钟后原单重试) 230 } else { 231 // 其他错误码 232 // 解决方式:请查看返回的错误信息 233 } 234 e.printStackTrace(); 235 236 // 创建失败 237 return null; 238 } 239 } 240 241 public void startStock(String stockId, String stockCreatorMchid) { 242 243 StartStock client = new StartStock( 244 mchid, 245 certificateSerialNo, 246 privateKeyFilePath, 247 wechatPayPublicKeyId, 248 wechatPayPublicKeyFilePath 249 ); 250 251 StartStockRequest request = new StartStockRequest(); 252 request.stockId = stockId; // 需要激活的优惠券批次号 253 request.stockCreatorMchid = stockCreatorMchid; // 优惠券制券商户号 254 try { 255 StartStockResponse response = client.run(request); 256 257 // 请求成功,系统会返回该批次的激活时间和批次 ID 258 String startTimeResp = response.startTime; 259 String stockIdResp = request.stockId; 260 261 } catch (WXPayUtility.ApiException e) { 262 // 请求失败,根据状态码执行不同的逻辑 263 if (e.getErrorCode().equals("PARAM_ERROR")) { 264 // 错误:参数错误 265 // 解决方式:请根据错误提示正确传入参数 266 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 267 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 268 // 解决方式:结合给定的提示信息,并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709 解决 269 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 270 // 错误:验证不通过 271 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 272 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 273 } else if (e.getErrorCode().equals("SYSTEM_ERROR")) { 274 // 错误:系统异常,请稍后重试 275 // 解决方式:请稍后原单重试 276 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 277 // 错误:商户号与AppID不匹配 278 // 解决方式:请绑定调用接口的商户号和AppID后重试 279 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 280 // 错误:商户号不合法 281 // 解决方式:请输入正确的商户号 282 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 283 // 错误:批次预算不足 284 // 解决方式:请补充预算 285 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 286 // 错误:请求失败 287 // 解决方式:请根据具体的报错信息修改请求后重试 288 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 289 // 错误:批次不存在 290 // 解决方式:请检查批次ID是否正确 291 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 292 // 错误:请求过于频繁 293 // 解决方式:稍后重试 294 } else { 295 // 未知错误码 296 // 解决方式:请查看返回的错误信息 297 } 298 299 e.printStackTrace(); 300 301 } 302 } 303 304 public void pauseStock(String stockId, String stockCreatorMchid) { 305 306 PauseStock client = new PauseStock( 307 mchid, 308 certificateSerialNo, 309 privateKeyFilePath, 310 wechatPayPublicKeyId, 311 wechatPayPublicKeyFilePath 312 ); 313 314 PauseStockRequest request = new PauseStockRequest(); 315 request.stockId = stockId; // 需要暂停的代金券批次 ID 316 request.stockCreatorMchid = stockCreatorMchid; // 批次的创建商户号 317 try { 318 PauseStockResponse response = client.run(request); 319 320 // 请求成功,系统会返回暂停时间和批次号 321 String pauseTimeResp = response.pauseTime; 322 String stockIdResp = response.stockId; 323 } catch (WXPayUtility.ApiException e) { 324 // 请求失败,根据状态码执行不同的逻辑 325 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 326 // 错误:系统错误 327 // 解决方式:稍后(建议等 1 分钟后)原单重试 328 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 329 // 错误:参数错误 330 // 解决方式:请根据错误提示正确传入参数 331 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 332 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 333 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 334 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 335 // 错误:验证不通过 336 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 337 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 338 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 339 // 错误:商户号与AppID不匹配 340 // 解决方式:请绑定调用接口的商户号和AppID后重试 341 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 342 // 错误:商户号不合法 343 // 解决方式:请输入正确的商户号 344 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 345 // 错误:批次预算不足 346 // 解决方式:请补充预算 347 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 348 // 错误:请求失败 349 // 解决方式:请根据具体的错误信息修改请求 350 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 351 // 错误:批次不存在 352 // 解决方式:请检查批次 ID 是否正确 353 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 354 // 错误:请求过于频繁 355 // 解决方式:稍后重试 356 } else { 357 // 其他错误码 358 // 解决方式:请查看返回的错误信息 359 } 360 e.printStackTrace(); 361 } 362 } 363 364 public void restartStock(String stockId, String stockCreatorMchid) { 365 366 RestartStock client = new RestartStock( 367 mchid, 368 certificateSerialNo, 369 privateKeyFilePath, 370 wechatPayPublicKeyId, 371 wechatPayPublicKeyFilePath 372 ); 373 374 RestartStockRequest request = new RestartStockRequest(); 375 request.stockId = stockId; // 需要重启的代金券批次ID 376 request.stockCreatorMchid = stockCreatorMchid; // 该代金券的制券商户号 377 try { 378 RestartStockResponse response = client.run(request); 379 380 // 请求成功,系统返回重启时间和代金券批次ID 381 String stockIdResp = request.stockId; 382 String restartTimeResp = response.restartTime; 383 384 } catch (WXPayUtility.ApiException e) { 385 // 请求失败,根据状态码执行不同的逻辑 386 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 387 // 错误:系统错误 388 // 解决方式:稍后(建议等 1 分钟后)原单重试 389 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 390 // 错误:参数错误 391 // 解决方式:请根据错误提示正确传入参数 392 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 393 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 394 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 395 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 396 // 错误:验证不通过 397 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 398 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 399 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 400 // 错误:商户号与AppID不匹配 401 // 解决方式:请绑定调用接口的商户号和AppID后重试 402 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 403 // 错误:商户号不合法 404 // 解决方式:请输入正确的商户号 405 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 406 // 错误:批次预算不足 407 // 解决方式:请补充预算 408 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 409 // 错误:请求失败 410 // 解决方式:请根据具体的错误信息修改请求 411 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 412 // 错误:批次不存在 413 // 解决方式:请检查批次 ID 是否正确 414 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 415 // 错误:请求过于频繁 416 // 解决方式:稍后重试 417 } else { 418 // 其他错误码 419 // 解决方式:请查看返回的错误信息 420 } 421 e.printStackTrace(); 422 } 423 } 424 425 public String sendCoupon(String stockId, String stockCreatorMchid, String appid, String userOpenid) { 426 427 SendCoupon client = new SendCoupon( 428 mchid, 429 certificateSerialNo, 430 privateKeyFilePath, 431 wechatPayPublicKeyId, 432 wechatPayPublicKeyFilePath 433 ); 434 435 SendCouponRequest request = new SendCouponRequest(); 436 437 // 请参阅实际文档进行传参 https://pay.weixin.qq.com/doc/v3/merchant/4012463767 438 request.appid = appid; // 微信为发券方商户分配的公众账号ID 439 request.openid = userOpenid; // 需要发放代金券的用户在该 appid 下对应的 openid 440 441 // 优惠券信息 442 request.stockId = stockId; // 券批次号 443 request.stockCreatorMchid = stockCreatorMchid; // 制券商户号 444 request.outRequestNo = "out_request_no"; // 商户此次发放凭据号(格式:商户id+日期+流水号),商户侧需保持唯一性 445 request.couponValue = 1L; // 指定面额发券场景,券面额,其他场景不需要填,单位:分。 (该字段暂未开放,注释掉不填写即可) 446 request.couponMinimum = 1L; // 指定面额发券批次门槛,其他场景不需要,单位:分。 (该字段暂未开放,注释掉不填写即可) 447 448 try { 449 SendCouponResponse response = client.run(request); 450 451 // 请求成功,系统会返回发放成功的代金券券号 452 String couponId = response.couponId; 453 454 return couponId; 455 } catch (WXPayUtility.ApiException e) { 456 // 请求失败,根据状态码执行不同的逻辑 457 if (e.getErrorCode() == null) { 458 // 错误码为空,无法判断具体错误 459 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 460 // 错误:参数错误 461 // 解决方式:请根据错误提示正确传入参数 462 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 463 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 464 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 465 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 466 // 错误:验证不通过 467 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 468 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 469 } else if (e.getErrorCode().equals("SYSTEM_ERROR")) { 470 // 错误:系统错误 471 // 解决方式:稍后(建议等 1 分钟后)原单重试 472 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 473 // 错误:商户号与AppID不匹配 474 // 解决方式:请绑定调用接口的商户号和AppID后重试 475 // 可以参考 https://developers.weixin.qq.com/community/pay/doc/0000046f16862087608adcdf959c08 中的 Q4 476 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 477 // 错误:商户号不合法 478 // 解决方式:请输入正确的商户号 479 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 480 // 可能的错误描述以及解决方式: 481 // - 批次预算不足:批次预算已发放完,请补充批次预算 482 // - 发券超过单天限额:已超过该批次设置的单天发放限制额度,无法发放 483 // - 账户余额不足,请充值:商户号余额不足,无法继续发券,请充值 484 // - 批次预算耗尽:该批次的预算已经耗尽,无法发放 485 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 486 // 错误:请求失败 487 // 解决方式:请根据具体的错误信息修改请求 488 } else if (e.getErrorCode().equals("RULE_LIMIT")) { 489 /* 490 * 错误码:RULE_LIMIT 491 * 可能的错误描述及解决方案: 492 * - 用户已达最大领券次数:该用户已达到该批次的领取上限,请参考常见问题Q6 493 * - 被自然人规则拦截:该自然人已达到该批次的领取上限,请参考常见问题Q6 494 */ 495 } else if (e.getErrorCode().equals("USER_ACCOUNT_ABNORMAL")) { 496 /* 497 * 错误码:USER_ACCOUNT_ABNORMAL 498 * 可能的错误描述及解决方案: 499 * - 用户非法:用户命中微信支付风控模型,请参考常见问题Q5 500 * - 用户未实名:该用户无实名信息,无法领券。商家可联系微信支付或让用户联系微信支付客服处理。 501 */ 502 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 503 /* 504 * 错误码:RESOURCE_NOT_EXISTS 505 * 错误描述:批次不存在 506 * 解决方案:请检查批次及制券商户号信息 507 */ 508 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 509 /* 510 * 错误码:FREQUENCY_LIMITED 511 * 错误描述:当前请求人数过多,请稍后重试 512 * 解决方案:请降低API调用频率 513 */ 514 } else { 515 // 未知错误码 516 // 解决方案:请查看返回的错误信息 517 } 518 e.printStackTrace(); 519 return null; 520 } 521 } 522}
4. 处理代金券的核销通知信息
如果商户配置了代金券消息通知地址(见第二章),用户使用券后,微信会把相关核销信息发送给商户,商户需要接收处理,并按照文档规范返回应答。
收到通知回调后,需要依次经过验签,解密的步骤,并进行应答:
验签通过:商户需告知微信支付接收回调成功,HTTP应答状态码需返回200或204,无需返回应答报文。
验签不通过:商户需告知微信支付接收回调失败,HTTP应答状态码需返回5XX或4XX,同时需返回应答报文。
为了保证业务信息的安全性,微信支付将业务信息进行了AES-256-GCM加密,并通过参数resource将加密信息回调给商户,商户需要进行解密后才能获取到业务信息。
对于回调验签、应答以及解密的参数和机制,可以参考文档核销事件回调通知。
以下给出了处理代金券核销通知信息的示例代码。
1package com.java.coupon; 2 3import java.security.PublicKey; 4import com.google.gson.annotations.SerializedName; 5 6import com.java.utils.WXPayUtility; 7import com.java.utils.WXPayUtility.Notification; 8 9import java.util.List; 10import okhttp3.Headers; 11 12public class HandleCallback { 13 14 private String wechatPayPublicKeyId; 15 private PublicKey wechatPayPublicKey; 16 private String apiv3AesKey; 17 18 public HandleCallback() { 19 this.wechatPayPublicKeyId = "填入 微信支付公钥ID"; 20 this.apiv3AesKey = "填入 APIv3密钥"; 21 22 String wechatPayPublicKeyPath = "填入 微信支付公钥文件路径"; 23 this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyPath); 24 } 25 26 public int callback(Headers headers, String reqBody) { 27 try { 28 // 解密 29 Notification notification = WXPayUtility.parseNotification(apiv3AesKey, wechatPayPublicKeyId, 30 wechatPayPublicKey, headers, reqBody); 31 String data = notification.getPlaintext(); 32 CallbackData callbackData = WXPayUtility.fromJson(data, CallbackData.class); 33 34 // 获取其中的字段并处理 35 // 具体字段以及含义请参考 https://pay.weixin.qq.com/doc/v3/merchant/4012285250 36 String stockCreatorMchid = callbackData.stockCreatorMchid; 37 String stockId = callbackData.stockId; 38 String couponId = callbackData.couponId; 39 // ...... 40 41 // 成功,响应:200,无响应体 42 return 200; 43 } catch (Exception e) { 44 e.printStackTrace(); 45 // 失败,响应:5xx或4xx,响应: 46 // { 47 // "code": "FAIL", 48 // "message": "失败" 49 // } 50 return 500; 51 } 52 } 53 54 public static class CallbackData { 55 56 @SerializedName("stock_creator_mchid") 57 public String stockCreatorMchid; 58 59 @SerializedName("stock_id") 60 public String stockId; 61 62 @SerializedName("coupon_id") 63 public String couponId; 64 65 @SerializedName("singleitem_discount_off") 66 public SingleItemDiscountOff singleItemDiscountOff; 67 68 @SerializedName("discount_to") 69 public DiscountTo discountTo; 70 71 @SerializedName("coupon_name") 72 public String couponName; 73 74 @SerializedName("status") 75 public String status; 76 77 @SerializedName("description") 78 public String description; 79 80 @SerializedName("create_time") 81 public String createTime; 82 83 @SerializedName("coupon_type") 84 public String couponType; 85 86 @SerializedName("no_cash") 87 public Boolean noCash; 88 89 @SerializedName("available_begin_time") 90 public String availableBeginTime; 91 92 @SerializedName("available_end_time") 93 public String availableEndTime; 94 95 @SerializedName("singleitem") 96 public Boolean singleItem; 97 98 @SerializedName("normal_coupon_information") 99 public Long normalCouponInformation; 100 101 @SerializedName("goods_detail") 102 public List<GoodsDetail> goodsDetail; 103 104 @SerializedName("business_type") 105 public String businessType; 106 } 107 108 public static class ConsumeInformation { 109 110 @SerializedName("consume_time") 111 public String consumeTime; 112 113 @SerializedName("consume_mchid") 114 public String consumeMchid; 115 116 @SerializedName("transaction_id") 117 public String transactionId; 118 } 119 120 public static class NormalCouponInformation { 121 122 @SerializedName("coupon_amount") 123 public Long couponAmount; 124 125 @SerializedName("transaction_minimum") 126 public Long transactionMinimum; 127 } 128 129 public static class SingleItemDiscountOff { 130 131 @SerializedName("single_price_max") 132 public Long singlePriceMax; 133 } 134 135 public static class DiscountTo { 136 137 @SerializedName("cut_to_price") 138 public Long cutToPrice; 139 140 @SerializedName("max_price") 141 public Long maxPrice; 142 } 143 144 public static class GoodsDetail { 145 146 @SerializedName("goods_id") 147 public String goodsId; 148 149 @SerializedName("quantity") 150 public Integer quantity; 151 152 @SerializedName("price") 153 public Integer price; 154 155 @SerializedName("discount_amount") 156 public Integer discount_amount; 157 158 } 159}
5. 查询代金券以及批次的具体信息
对于代金券批次,商家可以查询指定批次的信息,包括:
指定查询条件,查询满足条件的批次信息;
查询单个指定批次号的批次信息;
查询指定批次的可用商户信息;
查询指定批次的可用单品信息。
对于代金券,商家可以查询指定代金券的信息,包括:
指定查询条件,查询指定用户符合条件的代金券;
查询单个指定代金券的信息。
以下提供了这些查询操作的相关代码:
1package com.java.coupon; 2 3import com.java.utils.WXPayUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/merchant/4014931831 4 5// 使用商户平台条件查询批次列表接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012460489 6import com.java.demo.ListStocks; 7import static com.java.demo.ListStocks.*; 8 9// 使用商户平台查询批次详细接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012460564 10import com.java.demo.QueryStock; 11import static com.java.demo.QueryStock.*; 12 13// 使用商户平台查询代金券可用商户接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012463358 14import com.java.demo.ListAvailableMerchants; 15import static com.java.demo.ListAvailableMerchants.*; 16 17// 使用商户平台查询代金券可用单品接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012463442 18import com.java.demo.ListAvailableSingleitems; 19import static com.java.demo.ListAvailableSingleitems.*; 20 21// 使用商户平台 根据商户号查用户的券 接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012534690 22import com.java.demo.ListCouponsByFilter; 23import static com.java.demo.ListCouponsByFilter.*; 24 25// 使用商户平台查询代金券详情接口。参考:https://pay.weixin.qq.com/doc/v3/merchant/4012486942 26import com.java.demo.QueryCoupon; 27import static com.java.demo.QueryCoupon.*; 28 29public class QueryCouponAndStockDemo { 30 31 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756 32 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 33 // https://pay.weixin.qq.com/doc/v3/merchant/4013070756 34 private static String mchid = "19xxxxxxxx"; 35 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 36 private static String certificateSerialNo = "1DDE55AD98Exxxxxxxxxx"; 37 // 商户API证书私钥文件路径,本地文件路径 38 private static String privateKeyFilePath = "/path/to/apiclient_key.pem"; 39 // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 40 private static String wechatPayPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; 41 // 微信支付公钥文件路径,本地文件路径 42 private static String wechatPayPublicKeyFilePath = "/path/to/wxp_pub.pem"; 43 44 public static void main(String[] args) { 45 46 QueryCouponAndStockDemo demo = new QueryCouponAndStockDemo(); 47 48 String stockId = "example_stockId"; // 需要查询的批次号 49 String creatorMchid = mchid; // 创建批次的商户号 50 String couponId = "example_couponId"; // 需要查询的优惠券券号 51 String appid = "example_appid"; // 微信为发券方商户分配的公众账号ID 52 String openid = "example_openid"; // 用户在该 appid 下对应的 openid,用于标识需要查询的用户 53 54 // 1. 查询代金券批次的相关操作 55 // 1.1 条件查询代金券批次列表 56 // 可以根据批次创建商户号、创建的时间区间和当前状态查询商户批次列表 57 ListStocks.StockCollection stockCollection = demo.listStocks(); 58 59 // 1.2 查询批次详情 60 QueryStock.Stock stock = demo.queryStock(creatorMchid, stockId); 61 62 // 1.3 查询代金券批次可用商户 63 // 查询特定批次的可用商户集合 64 AvailableMerchantCollection merchantCollection = demo.listAvailableMerchants(creatorMchid, stockId); 65 66 // 1.4 查询代金券批次可用单品 67 // 查询特定批次的可用单品集合 68 AvailableSingleitemCollection singleitemCollection = demo.listAvailableSingleItems(creatorMchid, stockId); 69 70 // 2. 查询代金券的相关操作 71 // 2.1 根据商户号查用户的券 72 // 可以根据商户号、批次号、券状态等条件查询指定用户符合条件的代金券 73 ListCouponsByFilter.CouponCollection couponCollection = demo.listCouponByFilter(); 74 75 // 2.2 查询代金券详情 76 // 查询指定用户指定券号的代金券详情 77 QueryCoupon.Coupon coupon = demo.queryCoupon(couponId, appid, openid); 78 79 } 80 81 // 查询满足条件的批次列表 82 // 返回所有满足条件的批次集合 83 public ListStocks.StockCollection listStocks() { 84 85 ListStocks client = new ListStocks( 86 mchid, 87 certificateSerialNo, 88 privateKeyFilePath, 89 wechatPayPublicKeyId, 90 wechatPayPublicKeyFilePath 91 ); 92 93 ListStocksRequest request = new ListStocksRequest(); 94 95 // 请参阅实际文档进行传参 https://pay.weixin.qq.com/doc/v3/merchant/4012460489 96 // 分页信息 97 request.offset = 0L; // 分页页码,页码从0开始 98 request.limit = 8L; // 分页大小,目前最大为 10 99 100 // 批次信息 101 // 时间格式遵循rfc3339标准格式,格式为yyyy-MM-DDTHH:mm:ss+TIMEZONE 102 request.stockCreatorMchid = "merchant_id"; // 批次创建商户号 103 request.createStartTime = "2025-05-20T13:29:35.120+08:00"; // 查询的起始创建时间 104 request.createEndTime = "2025-08-25T13:29:35.120+08:00"; // 查询的终止创建时间 105 106 // 批次状态,具体状态描述请参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012460489 107 request.status = "running"; 108 109 try { 110 StockCollection response = client.run(request); 111 // 请求成功,返回 StockCollection 类的实例 response 储存查到的所有优惠券的集合, 112 // 这个类的定义详见 https://pay.weixin.qq.com/doc/v3/merchant/4012460489 的代码描述 113 114 return response; 115 116 } catch (WXPayUtility.ApiException e) { 117 // 请求失败,根据状态码执行不同的逻辑 118 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 119 // 错误:系统错误 120 // 解决方式:稍后(建议等 1 分钟后)原单重试 121 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 122 // 错误:参数错误 123 // 解决方式:请根据错误提示正确传入参数 124 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 125 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 126 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 127 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 128 // 错误:验证不通过 129 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 130 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 131 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 132 // 错误:商户号与AppID不匹配 133 // 解决方式:请绑定调用接口的商户号和AppID后重试 134 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 135 // 错误:商户号不合法 136 // 解决方式:请输入正确的商户号 137 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 138 // 错误:批次预算不足 139 // 解决方式:请补充预算 140 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 141 // 错误:请求失败 142 // 解决方式:请根据具体的错误信息修改请求 143 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 144 // 错误:请求过于频繁 145 // 解决方式:稍后重试 146 } else { 147 // 其他错误码 148 // 解决方式:请查看返回的错误信息 149 } 150 e.printStackTrace(); 151 152 return null; 153 } 154 } 155 156 // 查询商户 creatorMchid 创建的批次号为 stockId 的批次信息 157 // 返回该批次的详细信息 158 public QueryStock.Stock queryStock(String creatorMchid, String stockId) { 159 160 QueryStock client = new QueryStock( 161 mchid, 162 certificateSerialNo, 163 privateKeyFilePath, 164 wechatPayPublicKeyId, 165 wechatPayPublicKeyFilePath 166 ); 167 168 QueryStockRequest request = new QueryStockRequest(); 169 request.stockId = stockId; // 需要查询的代金券批次ID 170 request.stockCreatorMchid = creatorMchid; // 该代金券的制券商户号 171 try { 172 QueryStock.Stock response = client.run(request); 173 // 请求成功,返回 Stock 类型的变量 response 表示查询到的批次信息 174 // Stock 类型的定义详见 https://pay.weixin.qq.com/doc/v3/merchant/4012460564 的代码描述 175 176 return response; 177 178 } catch (WXPayUtility.ApiException e) { 179 // 请求失败,根据状态码执行不同的逻辑 180 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 181 // 错误:系统错误 182 // 解决方式:稍后(建议等 1 分钟后)原单重试 183 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 184 // 错误:参数错误 185 // 解决方式:请根据错误提示正确传入参数 186 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 187 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 188 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 189 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 190 // 错误:验证不通过 191 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 192 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 193 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 194 // 错误:商户号与AppID不匹配 195 // 解决方式:请绑定调用接口的商户号和AppID后重试 196 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 197 // 错误:商户号不合法 198 // 解决方式:请输入正确的商户号 199 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 200 // 错误:批次预算不足 201 // 解决方式:请补充预算 202 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 203 // 错误:请求失败 204 // 解决方式:请根据具体的错误信息修改请求 205 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 206 // 错误:批次不存在 207 // 解决方式:请检查批次 ID 是否正确 208 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 209 // 错误:请求过于频繁 210 // 解决方式:稍后重试 211 } else { 212 // 其他错误码 213 // 解决方式:请查看返回的错误信息 214 } 215 e.printStackTrace(); 216 return null; 217 } 218 } 219 220 // 查询商户 creatorMchid 创建的批次号为 stockId 批次的可用商户信息 221 // 可以设置分页参数 222 // 返回该批次可用的商户集合 223 public AvailableMerchantCollection listAvailableMerchants(String creatorMchid, String stockId) { 224 225 ListAvailableMerchants client = new ListAvailableMerchants( 226 mchid, 227 certificateSerialNo, 228 privateKeyFilePath, 229 wechatPayPublicKeyId, 230 wechatPayPublicKeyFilePath 231 ); 232 233 ListAvailableMerchantsRequest request = new ListAvailableMerchantsRequest(); 234 235 // 请参阅实际文档进行传参 https://pay.weixin.qq.com/doc/v3/merchant/4012463358 236 // 优惠券批次信息 237 request.stockId = stockId; // 需要查询的代金券批次ID 238 request.stockCreatorMchid = creatorMchid; // 该代金券的制券商户号 239 240 // 分页信息 241 request.offset = 0L; // 分页页码,从 0 开始,目前最大1000 242 request.limit = 10L; // 分页大小,目前最大为 50 243 244 try { 245 AvailableMerchantCollection response = client.run(request); 246 247 // 请求成功,返回该批次可用的商户号集合 248 for (String availableMerchant : response.data) { 249 System.out.println(availableMerchant); 250 // 对可用商户进行其他逻辑操作 251 } 252 253 return response; 254 } catch (WXPayUtility.ApiException e) { 255 // 请求失败,根据状态码执行不同的逻辑 256 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 257 // 错误:系统错误 258 // 解决方式:稍后(建议等 1 分钟后)原单重试 259 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 260 // 错误:参数错误 261 // 解决方式:请根据错误提示正确传入参数 262 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 263 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 264 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 265 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 266 // 错误:验证不通过 267 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 268 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 269 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 270 // 错误:商户号与AppID不匹配 271 // 解决方式:请绑定调用接口的商户号和AppID后重试 272 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 273 // 错误:商户号不合法 274 // 解决方式:请输入正确的商户号 275 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 276 // 错误:批次预算不足 277 // 解决方式:请补充预算 278 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 279 // 错误:请求失败 280 // 解决方式:请根据具体的错误信息修改请求 281 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 282 // 错误:批次不存在 283 // 解决方式:请检查批次 ID 是否正确 284 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 285 // 错误:请求过于频繁 286 // 解决方式:稍后重试 287 } else { 288 // 其他错误码 289 // 解决方式:请查看返回的错误信息 290 } 291 e.printStackTrace(); 292 293 return null; 294 } 295 } 296 297 // 查询商户 creatorMchid 创建的批次号为 stockId 批次的可用单品信息 298 // 可以设置分页参数 299 // 返回该批次可用的单品集合 300 public AvailableSingleitemCollection listAvailableSingleItems(String creatorMchid, String stockId) { 301 302 ListAvailableSingleitems client = new ListAvailableSingleitems( 303 mchid, 304 certificateSerialNo, 305 privateKeyFilePath, 306 wechatPayPublicKeyId, 307 wechatPayPublicKeyFilePath 308 ); 309 310 ListAvailableSingleitemsRequest request = new ListAvailableSingleitemsRequest(); 311 312 // 请参阅实际文档进行传参 https://pay.weixin.qq.com/doc/v3/merchant/4012463442 313 // 优惠券批次信息 314 request.stockId = stockId; // 需要查询的代金券批次ID 315 request.stockCreatorMchid = creatorMchid; // 该代金券的制券商户号 316 317 // 分页信息 318 request.offset = 0L; // 分页页码,从 0 开始,目前最大为 500 319 request.limit = 10L; // 分页大小,目前最大为 100 320 321 try { 322 AvailableSingleitemCollection response = client.run(request); 323 324 // 请求成功,返回代金券能够使用的商品集合 325 System.out.println(response.toString()); 326 for (String singleItem : response.data) { 327 // 对每个物品进行后续逻辑操作 328 } 329 330 return response; 331 } catch (WXPayUtility.ApiException e) { 332 // 请求失败,根据状态码执行不同的逻辑 333 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 334 // 错误:系统错误 335 // 解决方式:稍后(建议等 1 分钟后)原单重试 336 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 337 // 错误:参数错误 338 // 解决方式:请根据错误提示正确传入参数 339 // 在这个接口中,如果优惠券没有指定单品信息,也会报参数错误 340 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 341 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 342 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 343 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 344 // 错误:验证不通过 345 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 346 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 347 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 348 // 错误:商户号与AppID不匹配 349 // 解决方式:请绑定调用接口的商户号和AppID后重试 350 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 351 // 错误:商户号不合法 352 // 解决方式:请输入正确的商户号 353 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 354 // 错误:批次预算不足 355 // 解决方式:请补充预算 356 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 357 // 错误:请求失败 358 // 解决方式:请根据具体的错误信息修改请求 359 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 360 // 错误:批次不存在 361 // 解决方式:请检查批次 ID 是否正确 362 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 363 // 错误:请求过于频繁 364 // 解决方式:稍后重试(建议等待一分钟后原单重试) 365 } else { 366 // 其他错误码 367 // 解决方式:请查看返回的错误信息 368 } 369 e.printStackTrace(); 370 return null; 371 } 372 } 373 374 // 根据商户号查用户的券,查询用户满足条件的券列表 375 // 返回所有满足条件的券集合 376 public ListCouponsByFilter.CouponCollection listCouponByFilter() { 377 378 ListCouponsByFilter client = new ListCouponsByFilter( 379 mchid, 380 certificateSerialNo, 381 privateKeyFilePath, 382 wechatPayPublicKeyId, 383 wechatPayPublicKeyFilePath 384 ); 385 386 ListCouponsByFilterRequest request = new ListCouponsByFilterRequest(); 387 388 // 请参阅实际文档进行传参 https://pay.weixin.qq.com/doc/v3/merchant/4012534690 389 request.appid = "appid_example"; // 微信为发券方商户分配的公众账号ID 390 request.openid = "openid_example"; // 用户在该 appid 下对应的 openid 391 392 // 优惠券信息 393 request.stockId = "stock_id"; // 优惠券批次号 394 395 // 优惠券状态,选填creator_mchid时, SENDED:返回可用;USED:返回可用+已实扣。 396 // 选填available_mchid时,该字段不生效,仅返回 可用 状态的券或消费金。 397 request.status = "USED"; 398 399 // 是否查询消费金 400 request.businessType = ListCouponsByFilter.BusinessType.MULTIUSE; // 选填,如果填写为 BusinessType.MULTIUSE,则表示查询用户的消费金 401 402 // 批次对应的商户 403 // creator_mchid 与 available_mchid 二选一,如果同时存在,优先使用 creator_mchid。 404 request.creatorMchid = "merchant_id_1"; // 批次创建方商户号 405 request.availableMchid = "merchant_id_2"; // 可用商户号 406 407 // 分页信息,填写 available_mchid,则分页信息不生效 408 request.offset = 0L; // 页码,默认 0 409 request.limit = 20L; // 分页大小,默认 20 410 411 try { 412 CouponCollection response = client.run(request); 413 414 // 请求成功,返回查询到的优惠券集合 415 for (ListCouponsByFilter.Coupon coupon : response.data) { 416 // 对每一张券继续进行后续操作 417 } 418 419 return response; 420 } catch (WXPayUtility.ApiException e) { 421 // 请求失败,根据状态码执行不同的逻辑 422 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 423 // 错误:系统错误 424 // 解决方式:稍后(建议等 1 分钟后)原单重试 425 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 426 // 错误:参数错误 427 // 解决方式:请根据错误提示正确传入参数 428 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 429 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 430 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 431 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 432 // 错误:验证不通过 433 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 434 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 435 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 436 // 错误:商户号与AppID不匹配 437 // 解决方式:请绑定调用接口的商户号和AppID后重试 438 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 439 // 错误:商户号不合法 440 // 解决方式:请输入正确的商户号 441 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 442 // 错误:批次预算不足 443 // 解决方式:请补充预算 444 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 445 // 错误:请求失败 446 // 解决方式:请根据具体的错误信息修改请求 447 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 448 // 错误:批次不存在 449 // 解决方式:请检查批次 ID 是否正确 450 } else if (e.getErrorCode().equals("USER_ACCOUNT_ABNORMAL")) { 451 // 错误:用户非法 452 // 解决方式:该用户账号异常。商家可联系微信支付或让用户联系微信支付客服处理。 453 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 454 // 错误:请求过于频繁 455 // 解决方式:稍后重试(建议等待一分钟后原单重试) 456 } else { 457 // 其他错误码 458 // 解决方式:请查看返回的错误信息 459 } 460 e.printStackTrace(); 461 return null; 462 } 463 } 464 465 // 查询指定用户指定券号的代金券详情 466 // 返回该代金券的详细信息 467 public QueryCoupon.Coupon queryCoupon(String couponId, String appid, String openid) { 468 469 QueryCoupon client = new QueryCoupon( 470 mchid, 471 certificateSerialNo, 472 privateKeyFilePath, 473 wechatPayPublicKeyId, 474 wechatPayPublicKeyFilePath 475 ); 476 477 QueryCouponRequest request = new QueryCouponRequest(); 478 request.couponId = couponId; // 需要查询的优惠券 ID 479 request.openid = openid; // 用户在该 appid 下对应的 openid 480 request.appid = appid; // 微信为发券方商户分配的公众账号ID 481 try { 482 QueryCoupon.Coupon response = client.run(request); 483 // 请求成功,系统返回 Coupon 类型的实例表示查询到的代金券。 484 // 该类型的定义详见 https://pay.weixin.qq.com/doc/v3/merchant/4012486942 485 486 return response; 487 } catch (WXPayUtility.ApiException e) { 488 // 请求失败,根据状态码执行不同的逻辑 489 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 490 // 错误:系统错误 491 // 解决方式:稍后(建议等 1 分钟后)原单重试 492 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 493 // 错误:参数错误 494 // 解决方式:请根据错误提示正确传入参数 495 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 496 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 497 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 498 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 499 // 错误:验证不通过 500 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 501 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 502 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 503 // 错误:商户号与AppID不匹配 504 // 解决方式:请绑定调用接口的商户号和AppID后重试 505 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 506 // 错误:商户号不合法 507 // 解决方式:请输入正确的商户号 508 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 509 // 错误:批次预算不足 510 // 解决方式:请补充预算 511 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 512 // 错误:请求失败 513 // 解决方式:请根据具体的错误信息修改请求 514 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 515 // 错误:批次不存在 516 // 解决方式:请检查批次 ID 是否正确 517 } else if (e.getErrorCode().equals("USER_ACCOUNT_ABNORMAL")) { 518 // 错误:用户非法 519 // 解决方式:该用户账号异常。商家可联系微信支付或让用户联系微信支付客服处理。 520 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED") || e.getErrorCode() 521 .equals("FREQUENCY_LIMIT_EXCEED")) { 522 // 错误:请求过于频繁 523 // 解决方式:稍后重试(建议等待一分钟后原单重试) 524 } else { 525 // 其他错误码 526 // 解决方式:请查看返回的错误信息 527 } 528 e.printStackTrace(); 529 return null; 530 } 531 } 532}
需要注意的是,如有选填参数未填写导致运行时报错,可以尝试将 WXPayUtility 中的函数 urlEncode 替换为如下版本以解决问题:
1public static String urlEncode(Map<String, Object> params) { 2 if (params == null || params.isEmpty()) { 3 return ""; 4 } 5 6 int index = 0; 7 StringBuilder result = new StringBuilder(); 8 for (Map.Entry<String, Object> entry : params.entrySet()) { 9 if (entry.getValue() != null) { 10 result.append(entry.getKey()) 11 .append("=") 12 .append(urlEncode(entry.getValue().toString())); 13 } 14 index++; 15 if (index < params.size()) { 16 result.append("&"); 17 } 18 } 19 return result.toString(); 20}
6. 下载批次核销和退款明细
活动结束后,商户可以获取某批次的退款和核销明细数据,包括订单号、单品信息、银行流水号等,用于对账/数据分析。
需要注意的是:
账单文件的下载地址的有效时间为30s。
强烈建议商户将实际账单文件的哈希值和之前从接口获取到的哈希值进行比对,以确认数据的完整性。
该接口响应的信息请求头中不包含微信接口响应的签名值,因此需要跳过验签的流程。
该接口需要活动结束后次日10点才可以下载。
该接口只能使用创建活动方进行调用。
以下提供了请求下载地址的示例代码。
1package com.java.coupon; 2 3import com.java.utils.WXPayUtility; // 引用微信支付工具库,参考:https://pay.weixin.qq.com/doc/v3/merchant/4014931831 4 5// 使用商户平台下载批次退款明细接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012463523 6import com.java.demo.RefundFlow; 7import static com.java.demo.RefundFlow.*; 8 9// 使用商户平台下载批次核销明细接口,参考:https://pay.weixin.qq.com/doc/v3/merchant/4012463585 10import com.java.demo.UseFlow; 11import static com.java.demo.UseFlow.*; 12 13public class DownloadFlowDemo { 14 15 // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756 16 // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 17 // https://pay.weixin.qq.com/doc/v3/merchant/4013070756 18 private static String mchid = "19xxxxxxxx"; 19 // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 20 private static String certificateSerialNo = "1DDE55AD98Exxxxxxxxxx"; 21 // 商户API证书私钥文件路径,本地文件路径 22 private static String privateKeyFilePath = "/path/to/apiclient_key.pem"; 23 // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 24 private static String wechatPayPublicKeyId = "PUB_KEY_ID_xxxxxxxxxxxxx"; 25 // 微信支付公钥文件路径,本地文件路径 26 private static String wechatPayPublicKeyFilePath = "/path/to/wxp_pub.pem"; 27 28 public static void main(String[] args) { 29 30 DownloadFlowDemo demo = new DownloadFlowDemo(); 31 32 String stockId = "example_stock_id"; // 需要查询明细的批次号 33 34 // 在批次结束后次日10点后,商户可以下载批次的退款明细和核销明细 35 // 账单文件的下载地址的有效时间为30s,请商户及时下载 36 // 强烈建议商户将实际账单文件的哈希值和之前从接口获取到的哈希值进行比对,以确认数据的完整性。 37 38 // 1. 查询批次退款明细的下载地址 39 // 返回下载地址、哈希类型以及哈希值。 40 RefundFlowResponse refundFlowData = demo.refundFlow(stockId); 41 String refundFlowUrl = refundFlowData.url; 42 String refundFlowHashType = refundFlowData.hashType; 43 String refundFlowHashValue = refundFlowData.hashValue; 44 45 // 2. 查询批次核销明细的下载地址 46 // 返回下载地址、哈希类型以及哈希值。 47 UseFlowResponse useFlowData = demo.useFlow(stockId); 48 String useFlowUrl = useFlowData.url; 49 String useFlowHashType = useFlowData.hashType; 50 String useFlowHashValue = useFlowData.hashValue; 51 52 } 53 54 // 查询批次退款明细的下载地址 55 // 返回下载地址 56 public RefundFlow.RefundFlowResponse refundFlow(String stockId) { 57 58 RefundFlow client = new RefundFlow( 59 mchid, 60 certificateSerialNo, 61 privateKeyFilePath, 62 wechatPayPublicKeyId, 63 wechatPayPublicKeyFilePath 64 ); 65 66 RefundFlowRequest request = new RefundFlowRequest(); 67 68 // 需要查询退款明细的优惠券批次号 69 request.stockId = stockId; 70 71 try { 72 RefundFlowResponse response = client.run(request); 73 return response; 74 } catch (WXPayUtility.ApiException e) { 75 // 请求失败,根据状态码执行不同的逻辑 76 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 77 // 错误:系统错误 78 // 解决方式:稍后(建议等 1 分钟后)原单重试 79 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 80 // 错误:参数错误 81 // 解决方式:请根据错误提示正确传入参数 82 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 83 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 84 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 85 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 86 // 错误:验证不通过 87 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 88 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 89 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 90 // 错误:商户号与AppID不匹配 91 // 解决方式:请绑定调用接口的商户号和AppID后重试 92 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 93 // 错误:商户号不合法 94 // 解决方式:请输入正确的商户号 95 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 96 // 错误:批次预算不足 97 // 解决方式:请补充预算 98 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 99 // 错误:请求失败 100 // 解决方式:请根据具体的错误信息修改请求 101 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 102 // 错误:批次不存在 103 // 解决方式:请检查批次 ID 是否正确 104 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 105 // 错误:请求过于频繁 106 // 解决方式:稍后重试 107 } else { 108 // 其他错误码 109 // 解决方式:请查看返回的错误信息 110 } 111 e.printStackTrace(); 112 return null; 113 } 114 } 115 116 public UseFlow.UseFlowResponse useFlow(String stockId) { 117 // 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756 118 UseFlow client = new UseFlow( 119 mchid, 120 certificateSerialNo, 121 privateKeyFilePath, 122 wechatPayPublicKeyId, 123 wechatPayPublicKeyFilePath 124 ); 125 126 UseFlowRequest request = new UseFlowRequest(); 127 128 // 需要查询的优惠券批次号 129 request.stockId = stockId; 130 131 try { 132 UseFlowResponse response = client.run(request); 133 return response; 134 } catch (WXPayUtility.ApiException e) { 135 // 请求失败,根据状态码执行不同的逻辑 136 if (e.getErrorCode().equals("SYSTEM_ERROR")) { 137 // 错误:系统错误 138 // 解决方式:稍后(建议等 1 分钟后)原单重试 139 } else if (e.getErrorCode().equals("PARAM_ERROR")) { 140 // 错误:参数错误 141 // 解决方式:请根据错误提示正确传入参数 142 } else if (e.getErrorCode().equals("INVALID_REQUEST")) { 143 // 错误:HTTP 请求不符合微信支付 APIv3 接口规则 144 // 解决方式:结合错误信息并参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081709,修改参数达到符合业务规则之后再重试 145 } else if (e.getErrorCode().equals("SIGN_ERROR")) { 146 // 错误:验证不通过 147 // 解决方式:检查平台商户证书序列号,证书私钥文件,公钥ID,公钥文件,同时确认下签名过程是不是按照微信支付的签名方式 148 // 可以参阅 https://pay.weixin.qq.com/doc/v3/merchant/4012081606 下的 “如何签名” 和 “如何验签” 部分 149 } else if (e.getErrorCode().equals("APPID_MCHID_NOT_MATCH")) { 150 // 错误:商户号与AppID不匹配 151 // 解决方式:请绑定调用接口的商户号和AppID后重试 152 } else if (e.getErrorCode().equals("MCH_NOT_EXISTS")) { 153 // 错误:商户号不合法 154 // 解决方式:请输入正确的商户号 155 } else if (e.getErrorCode().equals("NOT_ENOUGH")) { 156 // 错误:批次预算不足 157 // 解决方式:请补充预算 158 } else if (e.getErrorCode().equals("REQUEST_BLOCKED")) { 159 // 错误:请求失败 160 // 解决方式:请根据具体的错误信息修改请求 161 } else if (e.getErrorCode().equals("RESOURCE_NOT_EXISTS")) { 162 // 错误:批次不存在 163 // 解决方式:请检查批次 ID 是否正确 164 } else if (e.getErrorCode().equals("FREQUENCY_LIMITED")) { 165 // 错误:请求过于频繁 166 // 解决方式:稍后重试 167 } else { 168 // 其他错误码 169 // 解决方式:请查看返回的错误信息 170 } 171 e.printStackTrace(); 172 return null; 173 } 174 } 175} 176

