Skip to content

Commit 32779b1

Browse files
Copilotbinarywang
andcommitted
Update WeChat Pay withdrawal APIs to support notify_url parameter and callback
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
1 parent 1c5596c commit 32779b1

File tree

5 files changed

+278
-4
lines changed

5 files changed

+278
-4
lines changed

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* 电商平台提现
1111
* <pre>
12-
* 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_5.shtml
12+
* 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012476670
1313
* </pre>
1414
*/
1515
@Data
@@ -88,4 +88,19 @@ public class SpWithdrawRequest implements Serializable {
8888
@SerializedName(value = "account_type")
8989
private String accountType;
9090

91+
/**
92+
* <pre>
93+
* 字段名:回调通知地址
94+
* 变量名:notify_url
95+
* 是否必填:否
96+
* 类型:string(256)
97+
* 描述:
98+
* 异步接收提现状态变更通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
99+
* 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的地址。
100+
* 示例值:https://www.weixin.qq.com/wxpay/pay.php
101+
* </pre>
102+
*/
103+
@SerializedName(value = "notify_url")
104+
private String notifyUrl;
105+
91106
}

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
/**
1010
* 二级商户账户余额提现
1111
* <pre>
12-
* 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_3.shtml
12+
* 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4012476652
1313
* </pre>
1414
*/
1515
@Data
@@ -86,4 +86,19 @@ public class SubWithdrawRequest implements Serializable {
8686
@SerializedName(value = "bank_memo")
8787
private String bankMemo;
8888

89+
/**
90+
* <pre>
91+
* 字段名:回调通知地址
92+
* 变量名:notify_url
93+
* 是否必填:否
94+
* 类型:string(256)
95+
* 描述:
96+
* 异步接收提现状态变更通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
97+
* 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的地址。
98+
* 示例值:https://www.weixin.qq.com/wxpay/pay.php
99+
* </pre>
100+
*/
101+
@SerializedName(value = "notify_url")
102+
private String notifyUrl;
103+
89104
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package com.github.binarywang.wxpay.bean.ecommerce;
2+
3+
import com.google.gson.annotations.SerializedName;
4+
import lombok.Data;
5+
import lombok.NoArgsConstructor;
6+
7+
import java.io.Serializable;
8+
9+
/**
10+
* 提现状态变更通知结果
11+
* <pre>
12+
* 文档地址:https://pay.weixin.qq.com/doc/v3/partner/4013049135
13+
* </pre>
14+
*
15+
* @author copilot
16+
* created on 2024/12/24
17+
*/
18+
@Data
19+
@NoArgsConstructor
20+
public class WithdrawNotifyResult implements Serializable {
21+
22+
private static final long serialVersionUID = -7451351849088368701L;
23+
24+
/**
25+
* 源数据
26+
*/
27+
private NotifyResponse rawData;
28+
29+
/**
30+
* <pre>
31+
* 字段名:电商平台商户号
32+
* 变量名:sp_mchid
33+
* 是否必填:是
34+
* 类型:string(32)
35+
* 描述:
36+
* 微信支付分配给电商平台的商户号
37+
* 示例值:1900000100
38+
* </pre>
39+
*/
40+
@SerializedName(value = "sp_mchid")
41+
private String spMchid;
42+
43+
/**
44+
* <pre>
45+
* 字段名:二级商户号
46+
* 变量名:sub_mchid
47+
* 是否必填:否
48+
* 类型:string(32)
49+
* 描述:
50+
* 微信支付分配给二级商户的商户号,仅二级商户提现时返回
51+
* 示例值:1900000109
52+
* </pre>
53+
*/
54+
@SerializedName(value = "sub_mchid")
55+
private String subMchid;
56+
57+
/**
58+
* <pre>
59+
* 字段名:商户提现单号
60+
* 变量名:out_request_no
61+
* 是否必填:是
62+
* 类型:string(32)
63+
* 描述:
64+
* 商户提现单号,由商户自定义生成
65+
* 示例值:20190611222222222200000000012122
66+
* </pre>
67+
*/
68+
@SerializedName(value = "out_request_no")
69+
private String outRequestNo;
70+
71+
/**
72+
* <pre>
73+
* 字段名:微信支付提现单号
74+
* 变量名:withdraw_id
75+
* 是否必填:是
76+
* 类型:string(32)
77+
* 描述:
78+
* 微信支付提现单号
79+
* 示例值:12321002198704230011101200
80+
* </pre>
81+
*/
82+
@SerializedName(value = "withdraw_id")
83+
private String withdrawId;
84+
85+
/**
86+
* <pre>
87+
* 字段名:提现状态
88+
* 变量名:status
89+
* 是否必填:是
90+
* 类型:string(16)
91+
* 描述:
92+
* 提现状态:
93+
* CREATE_SUCCESS:受理成功
94+
* SUCCESS:提现成功
95+
* FAILED:提现失败
96+
* REFUND:提现退票
97+
* CLOSE:关单
98+
* 示例值:SUCCESS
99+
* </pre>
100+
*/
101+
@SerializedName(value = "status")
102+
private String status;
103+
104+
/**
105+
* <pre>
106+
* 字段名:提现金额
107+
* 变量名:amount
108+
* 是否必填:是
109+
* 类型:int64
110+
* 描述:
111+
* 提现金额,单位:分(人民币)
112+
* 示例值:100
113+
* </pre>
114+
*/
115+
@SerializedName(value = "amount")
116+
private Integer amount;
117+
118+
/**
119+
* <pre>
120+
* 字段名:提现发起时间
121+
* 变量名:create_time
122+
* 是否必填:是
123+
* 类型:string(29)
124+
* 描述:
125+
* 提现发起时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
126+
* YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
127+
* TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
128+
* 例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日13点29分35秒
129+
* 示例值:2018-06-08T10:34:56+08:00
130+
* </pre>
131+
*/
132+
@SerializedName(value = "create_time")
133+
private String createTime;
134+
135+
/**
136+
* <pre>
137+
* 字段名:提现更新时间
138+
* 变量名:update_time
139+
* 是否必填:是
140+
* 类型:string(29)
141+
* 描述:
142+
* 提现更新时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
143+
* YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
144+
* TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
145+
* 例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日13点29分35秒
146+
* 示例值:2018-06-08T10:34:56+08:00
147+
* </pre>
148+
*/
149+
@SerializedName(value = "update_time")
150+
private String updateTime;
151+
152+
/**
153+
* <pre>
154+
* 字段名:失败原因
155+
* 变量名:reason
156+
* 是否必填:否
157+
* 类型:string(256)
158+
* 描述:
159+
* 提现失败原因,仅在提现失败、退票时有值
160+
* 示例值:账户余额不足
161+
* </pre>
162+
*/
163+
@SerializedName(value = "reason")
164+
private String reason;
165+
166+
/**
167+
* <pre>
168+
* 字段名:备注
169+
* 变量名:remark
170+
* 是否必填:否
171+
* 类型:string(256)
172+
* 描述:
173+
* 商户对提现单的备注,若提现申请时未传递,则无此字段
174+
* 示例值:交易提现
175+
* </pre>
176+
*/
177+
@SerializedName(value = "remark")
178+
private String remark;
179+
180+
/**
181+
* <pre>
182+
* 字段名:银行附言
183+
* 变量名:bank_memo
184+
* 是否必填:否
185+
* 类型:string(256)
186+
* 描述:
187+
* 展示在收款银行系统中的附言,若提现申请时未传递,则无此字段
188+
* 示例值:微信支付提现
189+
* </pre>
190+
*/
191+
@SerializedName(value = "bank_memo")
192+
private String bankMemo;
193+
194+
/**
195+
* <pre>
196+
* 字段名:账户类型
197+
* 变量名:account_type
198+
* 是否必填:否
199+
* 类型:string(16)
200+
* 描述:
201+
* 提现账户类型,仅电商平台提现时返回:
202+
* BASIC:基本账户
203+
* OPERATION:运营账户
204+
* FEES:手续费账户
205+
* 示例值:BASIC
206+
* </pre>
207+
*/
208+
@SerializedName(value = "account_type")
209+
private String accountType;
210+
}

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,10 +417,23 @@ public interface EcommerceService {
417417
*/
418418
RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
419419

420+
/**
421+
* <pre>
422+
* 提现状态变更通知回调数据处理
423+
* 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4013049135
424+
* </pre>
425+
*
426+
* @param notifyData 通知数据
427+
* @param header 通知头部数据,不传则表示不校验头
428+
* @return 解密后通知数据 withdraw notify result
429+
* @throws WxPayException the wx pay exception
430+
*/
431+
WithdrawNotifyResult parseWithdrawNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
432+
420433
/**
421434
* <pre>
422435
* 二级商户账户余额提现API
423-
* 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_2.shtml
436+
* 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476652
424437
* </pre>
425438
*
426439
* @param request 提现请求
@@ -432,7 +445,7 @@ public interface EcommerceService {
432445
/**
433446
* <pre>
434447
* 电商平台提现API
435-
* 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_5.shtml
448+
* 文档地址: https://pay.weixin.qq.com/doc/v3/partner/4012476670
436449
* </pre>
437450
*
438451
* @param request 提现请求

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,27 @@ public RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHe
337337
}
338338
}
339339

340+
@Override
341+
public WithdrawNotifyResult parseWithdrawNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
342+
if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) {
343+
throw new WxPayException("非法请求,头部信息验证失败");
344+
}
345+
NotifyResponse response = GSON.fromJson(notifyData, NotifyResponse.class);
346+
NotifyResponse.Resource resource = response.getResource();
347+
String cipherText = resource.getCiphertext();
348+
String associatedData = resource.getAssociatedData();
349+
String nonce = resource.getNonce();
350+
String apiV3Key = this.payService.getConfig().getApiV3Key();
351+
try {
352+
String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key);
353+
WithdrawNotifyResult notifyResult = GSON.fromJson(result, WithdrawNotifyResult.class);
354+
notifyResult.setRawData(response);
355+
return notifyResult;
356+
} catch (GeneralSecurityException | IOException e) {
357+
throw new WxPayException("解析报文异常!", e);
358+
}
359+
}
360+
340361
@Override
341362
public SubWithdrawResult subWithdraw(SubWithdrawRequest request) throws WxPayException {
342363
String url = String.format("%s/v3/ecommerce/fund/withdraw", this.payService.getPayBaseUrl());

0 commit comments

Comments
 (0)