Skip to content

Commit 5fe1c06

Browse files
committed
重构微信支付申请退款接口 for issue #25
1 parent 10ce6a8 commit 5fe1c06

File tree

5 files changed

+413
-159
lines changed

5 files changed

+413
-159
lines changed

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

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
11
package me.chanjar.weixin.mp.api;
22

3+
import me.chanjar.weixin.common.exception.WxErrorException;
4+
import me.chanjar.weixin.mp.bean.pay.*;
5+
36
import java.io.File;
47
import java.util.Map;
58

6-
import me.chanjar.weixin.common.exception.WxErrorException;
7-
import me.chanjar.weixin.mp.bean.pay.WxEntPayRequest;
8-
import me.chanjar.weixin.mp.bean.pay.WxEntPayResult;
9-
import me.chanjar.weixin.mp.bean.pay.WxMpPayCallback;
10-
import me.chanjar.weixin.mp.bean.pay.WxMpPayRefundResult;
11-
import me.chanjar.weixin.mp.bean.pay.WxMpPayResult;
12-
import me.chanjar.weixin.mp.bean.pay.WxRedpackResult;
13-
import me.chanjar.weixin.mp.bean.pay.WxSendRedpackRequest;
14-
import me.chanjar.weixin.mp.bean.pay.WxUnifiedOrderRequest;
15-
import me.chanjar.weixin.mp.bean.pay.WxUnifiedOrderResult;
16-
179
/**
1810
* 微信支付相关接口
1911
* Created by Binary Wang on 2016/7/28.
@@ -25,7 +17,7 @@ public interface WxMpPayService {
2517
* 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1)
2618
* 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识"
2719
* 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder
28-
* @throws WxErrorException
20+
* @throws WxErrorException
2921
*
3022
*/
3123
WxUnifiedOrderResult unifiedOrder(WxUnifiedOrderRequest request)
@@ -42,7 +34,7 @@ WxUnifiedOrderResult unifiedOrder(WxUnifiedOrderRequest request)
4234
/**
4335
* 该接口提供所有微信支付订单的查询,当支付通知处理异常戒丢失的情冴,商户可以通过该接口查询订单支付状态。
4436
* 详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
45-
* @throws WxErrorException
37+
* @throws WxErrorException
4638
*
4739
*/
4840
WxMpPayResult getJSSDKPayResult(String transactionId, String outTradeNo)
@@ -56,18 +48,15 @@ WxMpPayResult getJSSDKPayResult(String transactionId, String outTradeNo)
5648
WxMpPayCallback getJSSDKCallbackData(String xmlData);
5749

5850
/**
51+
* <pre>
5952
* 微信支付-申请退款
6053
* 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
61-
*
62-
* @param parameters 需要传入的退款参数的Map。以下几项为参数的必须项:<br/>
63-
* <li/> transaction_id
64-
* <li/> out_trade_no (仅在上述transaction_id为空时是必须项)
65-
* <li/> out_refund_no
66-
* <li/> total_fee
67-
* <li/> refund_fee
54+
* 接口链接:https://api.mch.weixin.qq.com/secapi/pay/refund
55+
* </pre>
56+
* @param keyFile 证书文件对象
6857
* @return 退款操作结果
6958
*/
70-
WxMpPayRefundResult refundPay(Map<String, String> parameters) throws WxErrorException;
59+
WxMpPayRefundResult refund(WxMpPayRefundRequest request, File keyFile) throws WxErrorException;
7160

7261
/**
7362
* <pre>
@@ -80,7 +69,7 @@ WxMpPayResult getJSSDKPayResult(String transactionId, String outTradeNo)
8069

8170
/**
8271
* 发送微信红包给个人用户
83-
* <pre>
72+
* <pre>
8473
* 文档详见:
8574
* 发送普通红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
8675
* 发送裂变红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5&index=4
@@ -89,7 +78,7 @@ WxMpPayResult getJSSDKPayResult(String transactionId, String outTradeNo)
8978
WxRedpackResult sendRedpack(WxSendRedpackRequest request) throws WxErrorException;
9079

9180
/**
92-
* <pre>
81+
* <pre>
9382
* 企业付款业务是基于微信支付商户平台的资金管理能力,为了协助商户方便地实现企业向个人付款,针对部分有开发能力的商户,提供通过API完成企业付款的功能。
9483
* 比如目前的保险行业向客户退保、给付、理赔。
9584
* 企业付款将使用商户的可用余额,需确保可用余额充足。查看可用余额、充值、提现请登录商户平台“资金管理”https://pay.weixin.qq.com/进行操作。

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

Lines changed: 70 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
package me.chanjar.weixin.mp.api.impl;
22

3-
import java.io.File;
4-
import java.io.FileInputStream;
5-
import java.lang.reflect.Field;
6-
import java.security.KeyStore;
7-
import java.util.HashMap;
8-
import java.util.List;
9-
import java.util.Map;
10-
import java.util.Map.Entry;
11-
import java.util.SortedMap;
12-
import java.util.TreeMap;
13-
14-
import javax.net.ssl.SSLContext;
15-
3+
import com.google.common.collect.Lists;
4+
import com.google.common.collect.Maps;
5+
import com.thoughtworks.xstream.XStream;
6+
import com.thoughtworks.xstream.annotations.XStreamAlias;
7+
import me.chanjar.weixin.common.annotation.Required;
8+
import me.chanjar.weixin.common.bean.result.WxError;
9+
import me.chanjar.weixin.common.exception.WxErrorException;
10+
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
11+
import me.chanjar.weixin.mp.api.WxMpPayService;
12+
import me.chanjar.weixin.mp.api.WxMpService;
13+
import me.chanjar.weixin.mp.bean.pay.*;
1614
import org.apache.commons.codec.digest.DigestUtils;
15+
import org.apache.commons.lang3.ArrayUtils;
1716
import org.apache.commons.lang3.StringUtils;
1817
import org.apache.http.client.methods.CloseableHttpResponse;
1918
import org.apache.http.client.methods.HttpPost;
@@ -26,26 +25,13 @@
2625
import org.apache.http.util.EntityUtils;
2726
import org.joor.Reflect;
2827

29-
import com.google.common.collect.Lists;
30-
import com.google.common.collect.Maps;
31-
import com.thoughtworks.xstream.XStream;
32-
import com.thoughtworks.xstream.annotations.XStreamAlias;
33-
34-
import me.chanjar.weixin.common.annotation.Required;
35-
import me.chanjar.weixin.common.bean.result.WxError;
36-
import me.chanjar.weixin.common.exception.WxErrorException;
37-
import me.chanjar.weixin.common.util.xml.XStreamInitializer;
38-
import me.chanjar.weixin.mp.api.WxMpPayService;
39-
import me.chanjar.weixin.mp.api.WxMpService;
40-
import me.chanjar.weixin.mp.bean.pay.WxEntPayRequest;
41-
import me.chanjar.weixin.mp.bean.pay.WxEntPayResult;
42-
import me.chanjar.weixin.mp.bean.pay.WxMpPayCallback;
43-
import me.chanjar.weixin.mp.bean.pay.WxMpPayRefundResult;
44-
import me.chanjar.weixin.mp.bean.pay.WxMpPayResult;
45-
import me.chanjar.weixin.mp.bean.pay.WxRedpackResult;
46-
import me.chanjar.weixin.mp.bean.pay.WxSendRedpackRequest;
47-
import me.chanjar.weixin.mp.bean.pay.WxUnifiedOrderRequest;
48-
import me.chanjar.weixin.mp.bean.pay.WxUnifiedOrderResult;
28+
import javax.net.ssl.SSLContext;
29+
import java.io.File;
30+
import java.io.FileInputStream;
31+
import java.lang.reflect.Field;
32+
import java.security.KeyStore;
33+
import java.util.*;
34+
import java.util.Map.Entry;
4935

5036
/**
5137
* Created by Binary Wang on 2016/7/28.
@@ -55,8 +41,10 @@
5541
public class WxMpPayServiceImpl implements WxMpPayService {
5642

5743
private static final String PAY_BASE_URL = "https://api.mch.weixin.qq.com";
58-
private static final List<String> TRADE_TYPES = Lists.newArrayList("JSAPI",
59-
"NATIVE", "APP");
44+
private static final String[] TRADE_TYPES = new String[]{"JSAPI","NATIVE", "APP"};
45+
private static final String[] REFUND_ACCOUNT = new String[]{"REFUND_SOURCE_RECHARGE_FUNDS",
46+
"REFUND_SOURCE_UNSETTLED_FUNDS"};
47+
6048
private WxMpService wxMpService;
6149

6250
public WxMpPayServiceImpl(WxMpService wxMpService) {
@@ -115,33 +103,25 @@ public WxMpPayCallback getJSSDKCallbackData(String xmlData) {
115103
}
116104

117105
@Override
118-
public WxMpPayRefundResult refundPay(Map<String, String> parameters)
106+
public WxMpPayRefundResult refund(WxMpPayRefundRequest request, File keyFile)
119107
throws WxErrorException {
120-
SortedMap<String, String> refundParams = new TreeMap<>(parameters);
121-
refundParams.put("appid",
122-
this.wxMpService.getWxMpConfigStorage().getAppId());
123-
refundParams.put("mch_id",
124-
this.wxMpService.getWxMpConfigStorage().getPartnerId());
125-
refundParams.put("nonce_str", System.currentTimeMillis() + "");
126-
refundParams.put("op_user_id",
127-
this.wxMpService.getWxMpConfigStorage().getPartnerId());
128-
String sign = this.createSign(refundParams,
129-
this.wxMpService.getWxMpConfigStorage().getPartnerKey());
130-
refundParams.put("sign", sign);
131-
132-
StringBuilder request = new StringBuilder("<xml>");
133-
for (Map.Entry<String, String> para : refundParams.entrySet()) {
134-
request.append(String.format("<%s>%s</%s>", para.getKey(),
135-
para.getValue(), para.getKey()));
136-
}
137-
request.append("</xml>");
108+
checkParameters(request);
138109

139-
String url = PAY_BASE_URL + "/secapi/pay/refund";
140-
String responseContent = this.wxMpService.post(url, request.toString());
141110
XStream xstream = XStreamInitializer.getInstance();
142111
xstream.processAnnotations(WxMpPayRefundResult.class);
143-
WxMpPayRefundResult wxMpPayRefundResult = (WxMpPayRefundResult) xstream
144-
.fromXML(responseContent);
112+
xstream.processAnnotations(WxMpPayRefundRequest.class);
113+
114+
request.setAppid(this.wxMpService.getWxMpConfigStorage().getAppId());
115+
String partnerId = this.wxMpService.getWxMpConfigStorage().getPartnerId();
116+
request.setMchId(partnerId);
117+
request.setNonceStr( System.currentTimeMillis() + "");
118+
request.setOpUserId(partnerId);
119+
String sign = this.createSign(this.xmlBean2Map(request), this.wxMpService.getWxMpConfigStorage().getPartnerKey());
120+
request.setSign(sign);
121+
122+
String url = PAY_BASE_URL + "/secapi/pay/refund";
123+
String responseContent = this.executeRequestWithKeyFile(url, xstream.toXML(request), keyFile, partnerId);
124+
WxMpPayRefundResult wxMpPayRefundResult = (WxMpPayRefundResult) xstream.fromXML(responseContent);
145125

146126
if (!"SUCCESS".equalsIgnoreCase(wxMpPayRefundResult.getResultCode())
147127
|| !"SUCCESS".equalsIgnoreCase(wxMpPayRefundResult.getReturnCode())) {
@@ -158,6 +138,20 @@ public WxMpPayRefundResult refundPay(Map<String, String> parameters)
158138
return wxMpPayRefundResult;
159139
}
160140

141+
private void checkParameters(WxMpPayRefundRequest request) {
142+
checkNotNullParams(request);
143+
144+
if (StringUtils.isNotBlank(request.getRefundAccount())) {
145+
if(!ArrayUtils.contains(REFUND_ACCOUNT, request.getRefundAccount())){
146+
throw new IllegalArgumentException("refund_account目前必须为" + Arrays.toString(REFUND_ACCOUNT) + "其中之一");
147+
}
148+
}
149+
150+
if (StringUtils.isBlank(request.getOutTradeNo()) && StringUtils.isBlank(request.getTransactionId())) {
151+
throw new IllegalArgumentException("transaction_id 和 out_trade_no 不能同时为空,必须提供一个");
152+
}
153+
}
154+
161155
@Override
162156
public boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm,
163157
String signature) {
@@ -176,7 +170,7 @@ public WxRedpackResult sendRedpack(WxSendRedpackRequest request)
176170
request.setMchId(this.wxMpService.getWxMpConfigStorage().getPartnerId());
177171
request.setNonceStr(System.currentTimeMillis() + "");
178172

179-
String sign = this.createSign(xmlBean2Map(request),
173+
String sign = this.createSign(this.xmlBean2Map(request),
180174
this.wxMpService.getWxMpConfigStorage().getPartnerKey());
181175
request.setSign(sign);
182176

@@ -258,7 +252,7 @@ public WxUnifiedOrderResult unifiedOrder(WxUnifiedOrderRequest request)
258252
request.setMchId(this.wxMpService.getWxMpConfigStorage().getPartnerId());
259253
request.setNonceStr(System.currentTimeMillis() + "");
260254

261-
String sign = this.createSign(xmlBean2Map(request),
255+
String sign = this.createSign(this.xmlBean2Map(request),
262256
this.wxMpService.getWxMpConfigStorage().getPartnerKey());
263257
request.setSign(sign);
264258

@@ -274,16 +268,13 @@ public WxUnifiedOrderResult unifiedOrder(WxUnifiedOrderRequest request)
274268
}
275269

276270
return result;
277-
278271
}
279272

280273
private void checkParameters(WxUnifiedOrderRequest request) {
281-
282274
checkNotNullParams(request);
283275

284-
if (!TRADE_TYPES.contains(request.getTradeType())) {
285-
throw new IllegalArgumentException("trade_type目前必须为" + TRADE_TYPES + "其中之一");
286-
276+
if (! ArrayUtils.contains(TRADE_TYPES, request.getTradeType())) {
277+
throw new IllegalArgumentException("trade_type目前必须为" + Arrays.toString(TRADE_TYPES) + "其中之一");
287278
}
288279

289280
if ("JSAPI".equals(request.getTradeType()) && request.getOpenid() == null) {
@@ -368,28 +359,30 @@ public WxEntPayResult entPay(WxEntPayRequest request, File keyFile) throws WxErr
368359

369360
String url = PAY_BASE_URL + "/mmpaymkttransfers/promotion/transfers";
370361

371-
try (FileInputStream instream = new FileInputStream(keyFile)) {
372-
String mchId = request.getMchId();
362+
String responseContent = this.executeRequestWithKeyFile(xstream.toXML(request), url, keyFile, request.getMchId());
363+
WxEntPayResult result = (WxEntPayResult) xstream.fromXML(responseContent);
364+
if ("FAIL".equals(result.getResultCode())) {
365+
throw new WxErrorException(
366+
WxError.newBuilder().setErrorMsg(result.getErrCode() + ":" + result.getErrCodeDes()).build());
367+
}
368+
return result;
369+
}
370+
371+
private String executeRequestWithKeyFile( String requestStr, String url, File keyFile, String mchId) throws WxErrorException {
372+
try (FileInputStream inputStream = new FileInputStream(keyFile)) {
373373
KeyStore keyStore = KeyStore.getInstance("PKCS12");
374-
keyStore.load(instream, mchId.toCharArray());
374+
keyStore.load(inputStream, mchId.toCharArray());
375375

376376
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchId.toCharArray()).build();
377377
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null,
378378
new DefaultHostnameVerifier());
379379

380380
try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build()) {
381381
HttpPost httpPost = new HttpPost(url);
382-
httpPost.setEntity(new StringEntity(new String(xstream.toXML(request).getBytes("UTF-8"), "ISO-8859-1")));
382+
httpPost.setEntity(new StringEntity(new String(requestStr.getBytes("UTF-8"), "ISO-8859-1")));
383383

384384
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
385-
String responseContent = EntityUtils.toString(response.getEntity());
386-
WxEntPayResult result = (WxEntPayResult) xstream.fromXML(responseContent);
387-
if ("FAIL".equals(result.getResultCode())) {
388-
throw new WxErrorException(
389-
WxError.newBuilder().setErrorMsg(result.getErrCode() + ":" + result.getErrCodeDes()).build());
390-
}
391-
392-
return result;
385+
return EntityUtils.toString(response.getEntity());
393386
}
394387
}
395388
} catch (Exception e) {

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

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,9 @@
11
package me.chanjar.weixin.mp.api.impl;
22

3-
import java.io.IOException;
4-
5-
import org.apache.http.HttpHost;
6-
import org.apache.http.client.config.RequestConfig;
7-
import org.apache.http.client.methods.CloseableHttpResponse;
8-
import org.apache.http.client.methods.HttpGet;
9-
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
10-
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
11-
import org.apache.http.impl.client.BasicResponseHandler;
12-
import org.apache.http.impl.client.CloseableHttpClient;
13-
import org.slf4j.Logger;
14-
import org.slf4j.LoggerFactory;
15-
163
import com.google.gson.JsonArray;
174
import com.google.gson.JsonElement;
185
import com.google.gson.JsonObject;
196
import com.google.gson.JsonParser;
20-
217
import me.chanjar.weixin.common.bean.WxAccessToken;
228
import me.chanjar.weixin.common.bean.WxJsapiSignature;
239
import me.chanjar.weixin.common.bean.result.WxError;
@@ -26,38 +12,22 @@
2612
import me.chanjar.weixin.common.session.WxSessionManager;
2713
import me.chanjar.weixin.common.util.RandomUtils;
2814
import me.chanjar.weixin.common.util.crypto.SHA1;
29-
import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder;
30-
import me.chanjar.weixin.common.util.http.DefaultApacheHttpClientBuilder;
31-
import me.chanjar.weixin.common.util.http.RequestExecutor;
32-
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
33-
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
34-
import me.chanjar.weixin.common.util.http.URIUtil;
35-
import me.chanjar.weixin.mp.api.WxMpCardService;
36-
import me.chanjar.weixin.mp.api.WxMpConfigStorage;
37-
import me.chanjar.weixin.mp.api.WxMpDataCubeService;
38-
import me.chanjar.weixin.mp.api.WxMpKefuService;
39-
import me.chanjar.weixin.mp.api.WxMpMaterialService;
40-
import me.chanjar.weixin.mp.api.WxMpMenuService;
41-
import me.chanjar.weixin.mp.api.WxMpPayService;
42-
import me.chanjar.weixin.mp.api.WxMpQrcodeService;
43-
import me.chanjar.weixin.mp.api.WxMpService;
44-
import me.chanjar.weixin.mp.api.WxMpStoreService;
45-
import me.chanjar.weixin.mp.api.WxMpUserBlacklistService;
46-
import me.chanjar.weixin.mp.api.WxMpUserService;
47-
import me.chanjar.weixin.mp.api.WxMpUserTagService;
48-
import me.chanjar.weixin.mp.bean.WxMpIndustry;
49-
import me.chanjar.weixin.mp.bean.WxMpMassNews;
50-
import me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage;
51-
import me.chanjar.weixin.mp.bean.WxMpMassPreviewMessage;
52-
import me.chanjar.weixin.mp.bean.WxMpMassTagMessage;
53-
import me.chanjar.weixin.mp.bean.WxMpMassVideo;
54-
import me.chanjar.weixin.mp.bean.WxMpSemanticQuery;
55-
import me.chanjar.weixin.mp.bean.WxMpTemplateMessage;
56-
import me.chanjar.weixin.mp.bean.result.WxMpMassSendResult;
57-
import me.chanjar.weixin.mp.bean.result.WxMpMassUploadResult;
58-
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
59-
import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult;
60-
import me.chanjar.weixin.mp.bean.result.WxMpUser;
15+
import me.chanjar.weixin.common.util.http.*;
16+
import me.chanjar.weixin.mp.api.*;
17+
import me.chanjar.weixin.mp.bean.*;
18+
import me.chanjar.weixin.mp.bean.result.*;
19+
import org.apache.http.HttpHost;
20+
import org.apache.http.client.config.RequestConfig;
21+
import org.apache.http.client.methods.CloseableHttpResponse;
22+
import org.apache.http.client.methods.HttpGet;
23+
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
24+
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
25+
import org.apache.http.impl.client.BasicResponseHandler;
26+
import org.apache.http.impl.client.CloseableHttpClient;
27+
import org.slf4j.Logger;
28+
import org.slf4j.LoggerFactory;
29+
30+
import java.io.IOException;
6131

6232
public class WxMpServiceImpl implements WxMpService {
6333

@@ -477,6 +447,7 @@ protected synchronized <T, E> T executeInternal(RequestExecutor<T, E> executor,
477447
}
478448
return null;
479449
} catch (IOException e) {
450+
this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[EXECEPTION]: {}", uri, data, e.getMessage());
480451
throw new RuntimeException(e);
481452
}
482453
}

0 commit comments

Comments
 (0)