Skip to content

Commit 1bc2a5c

Browse files
committed
部分实现微信支付查询退款的接口 for #59
1 parent 33ef6eb commit 1bc2a5c

File tree

5 files changed

+743
-50
lines changed

5 files changed

+743
-50
lines changed

weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpPayService.java

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import me.chanjar.weixin.common.exception.WxErrorException;
44
import me.chanjar.weixin.mp.bean.pay.WxPayJsSDKCallback;
5-
import me.chanjar.weixin.mp.bean.pay.result.WxPayOrderCloseResult;
65
import me.chanjar.weixin.mp.bean.pay.request.WxEntPayRequest;
76
import me.chanjar.weixin.mp.bean.pay.request.WxPayRefundRequest;
87
import me.chanjar.weixin.mp.bean.pay.request.WxPaySendRedpackRequest;
@@ -13,8 +12,9 @@
1312
import java.util.Map;
1413

1514
/**
16-
* 微信支付相关接口
17-
* Created by Binary Wang on 2016/7/28.
15+
* 微信支付相关接口
16+
* Created by Binary Wang on 2016/7/28.
17+
*
1818
* @author binarywang (https://github.com/binarywang)
1919
*/
2020
public interface WxMpPayService {
@@ -24,14 +24,15 @@ public interface WxMpPayService {
2424
* 查询订单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2)
2525
* 该接口提供所有微信支付订单的查询,商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。
2626
* 需要调用查询接口的情况:
27-
◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
28-
◆ 调用支付接口后,返回系统错误或未知交易状态情况;
29-
◆ 调用被扫支付API,返回USERPAYING的状态;
30-
◆ 调用关单或撤销接口API之前,需确认支付状态;
27+
* ◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
28+
* ◆ 调用支付接口后,返回系统错误或未知交易状态情况;
29+
* ◆ 调用被扫支付API,返回USERPAYING的状态;
30+
* ◆ 调用关单或撤销接口API之前,需确认支付状态;
3131
* 接口地址:https://api.mch.weixin.qq.com/pay/orderquery
3232
* </pre>
33+
*
3334
* @param transactionId 微信支付分配的商户号
34-
* @param outTradeNo 商户系统内部的订单号,当没提供transaction_id时需要传这个。
35+
* @param outTradeNo 商户系统内部的订单号,当没提供transaction_id时需要传这个。
3536
* @throws WxErrorException
3637
*/
3738
WxPayOrderQueryResult queryOrder(String transactionId, String outTradeNo) throws WxErrorException;
@@ -47,6 +48,7 @@ public interface WxMpPayService {
4748
* 接口地址:https://api.mch.weixin.qq.com/pay/closeorder
4849
* 是否需要证书: 不需要。
4950
* </pre>
51+
*
5052
* @param outTradeNo 商户系统内部的订单号,当没提供transaction_id时需要传这个。
5153
* @throws WxErrorException
5254
*/
@@ -56,15 +58,16 @@ public interface WxMpPayService {
5658
* 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
5759
* 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识"
5860
* 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
59-
* @throws WxErrorException
60-
* @param request 请求对象
6161
*
62+
* @param request 请求对象
63+
* @throws WxErrorException
6264
*/
6365
WxPayUnifiedOrderResult unifiedOrder(WxPayUnifiedOrderRequest request) throws WxErrorException;
6466

6567
/**
6668
* 该接口调用“统一下单”接口,并拼装发起支付请求需要的参数
6769
* 详见http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN
70+
*
6871
* @param request 请求对象
6972
*/
7073
Map<String, String> getPayInfo(WxPayUnifiedOrderRequest request) throws WxErrorException;
@@ -75,16 +78,33 @@ public interface WxMpPayService {
7578
* 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
7679
* 接口链接:https://api.mch.weixin.qq.com/secapi/pay/refund
7780
* </pre>
81+
*
7882
* @param request 请求对象
79-
* @param keyFile 证书文件对象
83+
* @param keyFile 证书文件对象
8084
* @return 退款操作结果
8185
*/
8286
WxPayRefundResult refund(WxPayRefundRequest request, File keyFile) throws WxErrorException;
8387

88+
/**
89+
* <pre>
90+
* 微信支付-查询退款
91+
* 应用场景:
92+
* 提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
93+
* 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
94+
* 接口链接:https://api.mch.weixin.qq.com/pay/refundquery
95+
* </pre>
96+
* 以下四个参数四选一
97+
* @param transactionId 微信订单号
98+
* @param outTradeNo 商户订单号
99+
* @param outRefundNo 商户退款单号
100+
* @param refundId 微信退款单号
101+
* @return 退款信息
102+
*/
103+
WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, String outRefundNo, String refundId) throws WxErrorException;
104+
84105
/**
85106
* 读取支付结果通知
86107
* 详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
87-
*
88108
*/
89109
WxPayJsSDKCallback getJSSDKCallbackData(String xmlData) throws WxErrorException;
90110

@@ -93,7 +113,6 @@ public interface WxMpPayService {
93113
* 计算Map键值对是否和签名相符,
94114
* 按照字段名的 ASCII 码从小到大排序(字典序)后,使用 URL 键值对的 格式(即 key1=value1&key2=value2...)拼接成字符串
95115
* </pre>
96-
*
97116
*/
98117
boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm, String signature);
99118

@@ -104,8 +123,9 @@ public interface WxMpPayService {
104123
* 发送普通红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
105124
* 发送裂变红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5&index=4
106125
* </pre>
126+
*
107127
* @param request 请求对象
108-
* @param keyFile 证书文件对象
128+
* @param keyFile 证书文件对象
109129
*/
110130
WxPaySendRedpackResult sendRedpack(WxPaySendRedpackRequest request, File keyFile) throws WxErrorException;
111131

@@ -118,8 +138,9 @@ public interface WxMpPayService {
118138
* 文档详见:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
119139
* 接口链接:https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers
120140
* </pre>
141+
*
121142
* @param request 请求对象
122-
* @param keyFile 证书文件对象
143+
* @param keyFile 证书文件对象
123144
*/
124145
WxEntPayResult entPay(WxEntPayRequest request, File keyFile) throws WxErrorException;
125146

@@ -130,8 +151,9 @@ public interface WxMpPayService {
130151
* 文档详见:https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_3
131152
* 接口链接:https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo
132153
* </pre>
154+
*
133155
* @param partnerTradeNo 商户订单号
134-
* @param keyFile 证书文件对象
156+
* @param keyFile 证书文件对象
135157
*/
136158
WxEntPayQueryResult queryEntPay(String partnerTradeNo, File keyFile) throws WxErrorException;
137159

weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpPayServiceImpl.java

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import me.chanjar.weixin.mp.api.WxMpPayService;
99
import me.chanjar.weixin.mp.api.WxMpService;
1010
import me.chanjar.weixin.mp.bean.pay.WxPayJsSDKCallback;
11-
import me.chanjar.weixin.mp.bean.pay.result.WxPayOrderCloseResult;
1211
import me.chanjar.weixin.mp.bean.pay.request.*;
1312
import me.chanjar.weixin.mp.bean.pay.result.*;
1413
import org.apache.commons.codec.digest.DigestUtils;
@@ -42,13 +41,11 @@
4241
*/
4342
public class WxMpPayServiceImpl implements WxMpPayService {
4443

45-
protected final Logger log = LoggerFactory.getLogger(this.getClass());
46-
4744
private static final String PAY_BASE_URL = "https://api.mch.weixin.qq.com";
48-
private static final String[] TRADE_TYPES = new String[]{"JSAPI","NATIVE", "APP"};
49-
private static final String[] REFUND_ACCOUNT = new String[]{"REFUND_SOURCE_RECHARGE_FUNDS",
45+
private static final String[] TRADE_TYPES = new String[]{"JSAPI", "NATIVE", "APP"};
46+
private static final String[] REFUND_ACCOUNT = new String[]{"REFUND_SOURCE_RECHARGE_FUNDS",
5047
"REFUND_SOURCE_UNSETTLED_FUNDS"};
51-
48+
protected final Logger log = LoggerFactory.getLogger(this.getClass());
5249
private WxMpService wxMpService;
5350

5451
public WxMpPayServiceImpl(WxMpService wxMpService) {
@@ -57,7 +54,7 @@ public WxMpPayServiceImpl(WxMpService wxMpService) {
5754

5855
@Override
5956
public WxPayRefundResult refund(WxPayRefundRequest request, File keyFile)
60-
throws WxErrorException {
57+
throws WxErrorException {
6158
checkParameters(request);
6259

6360
XStream xstream = XStreamInitializer.getInstance();
@@ -67,7 +64,7 @@ public WxPayRefundResult refund(WxPayRefundRequest request, File keyFile)
6764
request.setAppid(this.wxMpService.getWxMpConfigStorage().getAppId());
6865
String partnerId = this.wxMpService.getWxMpConfigStorage().getPartnerId();
6966
request.setMchId(partnerId);
70-
request.setNonceStr( System.currentTimeMillis() + "");
67+
request.setNonceStr(System.currentTimeMillis() + "");
7168
request.setOpUserId(partnerId);
7269
String sign = this.createSign(BeanUtils.xmlBean2Map(request), this.wxMpService.getWxMpConfigStorage().getPartnerKey());
7370
request.setSign(sign);
@@ -79,11 +76,45 @@ public WxPayRefundResult refund(WxPayRefundRequest request, File keyFile)
7976
return result;
8077
}
8178

79+
@Override
80+
public WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, String outRefundNo, String refundId) throws WxErrorException {
81+
if ((StringUtils.isBlank(transactionId) && StringUtils.isBlank(outTradeNo) && StringUtils.isBlank(outRefundNo) && StringUtils.isBlank(refundId)) ||
82+
(StringUtils.isNotBlank(transactionId) && StringUtils.isNotBlank(outTradeNo) && StringUtils.isNotBlank(outRefundNo) && StringUtils.isNotBlank(refundId))) {
83+
throw new IllegalArgumentException("transaction_id , out_trade_no,out_refund_no, refund_id 必须四选一");
84+
}
85+
86+
XStream xstream = XStreamInitializer.getInstance();
87+
xstream.processAnnotations(WxPayRefundQueryRequest.class);
88+
xstream.processAnnotations(WxPayRefundQueryResult.class);
89+
90+
WxPayRefundQueryRequest request = new WxPayRefundQueryRequest();
91+
request.setOutTradeNo(StringUtils.trimToNull(outTradeNo));
92+
request.setTransactionId(StringUtils.trimToNull(transactionId));
93+
request.setOutRefundNo(StringUtils.trimToNull(outRefundNo));
94+
request.setRefundId(StringUtils.trimToNull(refundId));
95+
96+
request.setAppid(this.wxMpService.getWxMpConfigStorage().getAppId());
97+
request.setMchId(this.wxMpService.getWxMpConfigStorage().getPartnerId());
98+
request.setNonceStr(System.currentTimeMillis() + "");
99+
100+
String sign = this.createSign(BeanUtils.xmlBean2Map(request),
101+
this.wxMpService.getWxMpConfigStorage().getPartnerKey());
102+
request.setSign(sign);
103+
104+
String url = PAY_BASE_URL + "/pay/refundquery";
105+
106+
String responseContent = this.executeRequest(url, xstream.toXML(request));
107+
WxPayRefundQueryResult result = (WxPayRefundQueryResult) xstream.fromXML(responseContent);
108+
result.composeRefundRecords(responseContent);
109+
this.checkResult(result);
110+
return result;
111+
}
112+
82113
private void checkResult(WxPayBaseResult result) throws WxErrorException {
83114
if (!"SUCCESS".equalsIgnoreCase(result.getReturnCode())
84115
|| !"SUCCESS".equalsIgnoreCase(result.getResultCode())) {
85116
throw new WxErrorException(WxError.newBuilder().setErrorCode(-1)
86-
.setErrorMsg("返回代码:" + result.getReturnCode() + ", 返回信息: "
117+
.setErrorMsg("返回代码: " + result.getReturnCode() + ", 返回信息: "
87118
+ result.getReturnMsg() + ", 结果代码: " + result.getResultCode() + ", 错误代码: "
88119
+ result.getErrCode() + ", 错误详情: " + result.getErrCodeDes())
89120
.build());
@@ -94,7 +125,7 @@ private void checkParameters(WxPayRefundRequest request) throws WxErrorException
94125
BeanUtils.checkRequiredFields(request);
95126

96127
if (StringUtils.isNotBlank(request.getRefundAccount())) {
97-
if(!ArrayUtils.contains(REFUND_ACCOUNT, request.getRefundAccount())){
128+
if (!ArrayUtils.contains(REFUND_ACCOUNT, request.getRefundAccount())) {
98129
throw new IllegalArgumentException("refund_account目前必须为" + Arrays.toString(REFUND_ACCOUNT) + "其中之一");
99130
}
100131
}
@@ -118,14 +149,14 @@ public WxPayJsSDKCallback getJSSDKCallbackData(String xmlData) throws WxErrorExc
118149

119150
@Override
120151
public boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm,
121-
String signature) {
152+
String signature) {
122153
return signature.equals(this.createSign(kvm,
123-
this.wxMpService.getWxMpConfigStorage().getPartnerKey()));
154+
this.wxMpService.getWxMpConfigStorage().getPartnerKey()));
124155
}
125156

126157
@Override
127158
public WxPaySendRedpackResult sendRedpack(WxPaySendRedpackRequest request, File keyFile)
128-
throws WxErrorException {
159+
throws WxErrorException {
129160
XStream xstream = XStreamInitializer.getInstance();
130161
xstream.processAnnotations(WxPaySendRedpackRequest.class);
131162
xstream.processAnnotations(WxPaySendRedpackResult.class);
@@ -136,7 +167,7 @@ public WxPaySendRedpackResult sendRedpack(WxPaySendRedpackRequest request, File
136167
request.setNonceStr(System.currentTimeMillis() + "");
137168

138169
String sign = this.createSign(BeanUtils.xmlBean2Map(request),
139-
this.wxMpService.getWxMpConfigStorage().getPartnerKey());
170+
this.wxMpService.getWxMpConfigStorage().getPartnerKey());
140171
request.setSign(sign);
141172

142173
String url = PAY_BASE_URL + "/mmpaymkttransfers/sendredpack";
@@ -147,15 +178,16 @@ public WxPaySendRedpackResult sendRedpack(WxPaySendRedpackRequest request, File
147178

148179
String responseContent = this.executeRequestWithKeyFile(url, keyFile, xstream.toXML(request), mchId);
149180
WxPaySendRedpackResult result = (WxPaySendRedpackResult) xstream
150-
.fromXML(responseContent);
181+
.fromXML(responseContent);
151182
this.checkResult(result);
152183
return result;
153184
}
154185

155186
/**
156187
* 微信公众号支付签名算法(详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3)
188+
*
157189
* @param packageParams 原始参数
158-
* @param signKey 加密Key(即 商户Key)
190+
* @param signKey 加密Key(即 商户Key)
159191
* @return 签名字符串
160192
*/
161193
private String createSign(Map<String, String> packageParams, String signKey) {
@@ -165,7 +197,7 @@ private String createSign(Map<String, String> packageParams, String signKey) {
165197
for (String key : sortedMap.keySet()) {
166198
String value = packageParams.get(key);
167199
if (null != value && !"".equals(value) && !"sign".equals(key)
168-
&& !"key".equals(key)) {
200+
&& !"key".equals(key)) {
169201
toSign.append(key + "=" + value + "&");
170202
}
171203
}
@@ -237,7 +269,7 @@ public WxPayOrderCloseResult closeOrder(String outTradeNo) throws WxErrorExcepti
237269

238270
@Override
239271
public WxPayUnifiedOrderResult unifiedOrder(WxPayUnifiedOrderRequest request)
240-
throws WxErrorException {
272+
throws WxErrorException {
241273
checkParameters(request);
242274

243275
XStream xstream = XStreamInitializer.getInstance();
@@ -249,22 +281,22 @@ public WxPayUnifiedOrderResult unifiedOrder(WxPayUnifiedOrderRequest request)
249281
request.setNonceStr(System.currentTimeMillis() + "");
250282

251283
String sign = this.createSign(BeanUtils.xmlBean2Map(request),
252-
this.wxMpService.getWxMpConfigStorage().getPartnerKey());
284+
this.wxMpService.getWxMpConfigStorage().getPartnerKey());
253285
request.setSign(sign);
254286

255287
String url = PAY_BASE_URL + "/pay/unifiedorder";
256288

257289
String responseContent = this.executeRequest(url, xstream.toXML(request));
258290
WxPayUnifiedOrderResult result = (WxPayUnifiedOrderResult) xstream
259-
.fromXML(responseContent);
291+
.fromXML(responseContent);
260292
this.checkResult(result);
261293
return result;
262294
}
263295

264296
private void checkParameters(WxPayUnifiedOrderRequest request) throws WxErrorException {
265297
BeanUtils.checkRequiredFields(request);
266298

267-
if (! ArrayUtils.contains(TRADE_TYPES, request.getTradeType())) {
299+
if (!ArrayUtils.contains(TRADE_TYPES, request.getTradeType())) {
268300
throw new IllegalArgumentException("trade_type目前必须为" + Arrays.toString(TRADE_TYPES) + "其中之一");
269301
}
270302

@@ -283,7 +315,7 @@ public Map<String, String> getPayInfo(WxPayUnifiedOrderRequest request) throws W
283315
String prepayId = unifiedOrderResult.getPrepayId();
284316
if (StringUtils.isBlank(prepayId)) {
285317
throw new RuntimeException(String.format("Failed to get prepay id due to error code '%s'(%s).",
286-
unifiedOrderResult.getErrCode(), unifiedOrderResult.getErrCodeDes()));
318+
unifiedOrderResult.getErrCode(), unifiedOrderResult.getErrCodeDes()));
287319
}
288320

289321
Map<String, String> payInfo = new HashMap<>();
@@ -347,7 +379,7 @@ public WxEntPayQueryResult queryEntPay(String partnerTradeNo, File keyFile) thro
347379
return result;
348380
}
349381

350-
private String executeRequest( String url, String requestStr) throws WxErrorException {
382+
private String executeRequest(String url, String requestStr) throws WxErrorException {
351383
HttpPost httpPost = new HttpPost(url);
352384
if (this.wxMpService.getHttpProxy() != null) {
353385
httpPost.setConfig(RequestConfig.custom().setProxy(this.wxMpService.getHttpProxy()).build());
@@ -358,25 +390,25 @@ private String executeRequest( String url, String requestStr) throws WxErrorExce
358390

359391
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
360392
String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
361-
this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}",url, requestStr, result);
393+
this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", url, requestStr, result);
362394
return result;
363395
}
364396
} catch (IOException e) {
365397
this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[EXCEPTION]: {}", url, requestStr, e.getMessage());
366398
throw new WxErrorException(WxError.newBuilder().setErrorCode(-1).setErrorMsg(e.getMessage()).build(), e);
367-
}finally {
399+
} finally {
368400
httpPost.releaseConnection();
369401
}
370402
}
371403

372-
private String executeRequestWithKeyFile( String url, File keyFile, String requestStr, String mchId) throws WxErrorException {
404+
private String executeRequestWithKeyFile(String url, File keyFile, String requestStr, String mchId) throws WxErrorException {
373405
try (FileInputStream inputStream = new FileInputStream(keyFile)) {
374406
KeyStore keyStore = KeyStore.getInstance("PKCS12");
375407
keyStore.load(inputStream, mchId.toCharArray());
376408

377409
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
378-
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,
379-
new DefaultHostnameVerifier());
410+
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null,
411+
new DefaultHostnameVerifier());
380412

381413
HttpPost httpPost = new HttpPost(url);
382414
if (this.wxMpService.getHttpProxy() != null) {
@@ -387,10 +419,10 @@ private String executeRequestWithKeyFile( String url, File keyFile, String reque
387419
httpPost.setEntity(new StringEntity(new String(requestStr.getBytes("UTF-8"), "ISO-8859-1")));
388420
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
389421
String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8);
390-
this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}",url, requestStr, result);
422+
this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", url, requestStr, result);
391423
return result;
392424
}
393-
}finally {
425+
} finally {
394426
httpPost.releaseConnection();
395427
}
396428
} catch (Exception e) {

0 commit comments

Comments
 (0)