业务示例代码

更新时间:2026.05.19
||

1. 目标

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

  • 了解代金券创建、管理、发放、查询的全流程

  • 对于代金券批次,你可以:

    • 创建、激活、暂停、重启代金券批次;

    • 设定查询条件,查询的代金券批次列表;

    • 查询某一个指定批次的详细信息、可用商户和可用单品;

    • 下载指定批次代金券的退款明细和核销明细。

  • 对于代金券,你可以:

    • 根据商户号查询指定用户拥有的代金券,并可指定批次号、代金券状态等参数查询;

    • 发放指定批次的代金券给用户;

    • 根据代金券号查询代金券详细。

  • 除此之外,你还可以:

    • 设置和查询代金券消息的通知地址,接受代金券核销事件的回调通知;

    • 在系统中上传图片并获得图片的 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. 下载批次核销和退款明细

活动结束后,商户可以获取某批次的退款和核销明细数据,包括订单号、单品信息、银行流水号等,用于对账/数据分析。

需要注意的是:

  1. 账单文件的下载地址的有效时间为30s

  2. 强烈建议商户将实际账单文件的哈希值和之前从接口获取到的哈希值进行比对,以确认数据的完整性。

  3. 该接口响应的信息请求头中不包含微信接口响应的签名值,因此需要跳过验签的流程。

  4. 该接口需要活动结束后次日10点才可以下载。

  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/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