做完微信支付,如果遇到顾客需要退款的情况,我们就要调用微信的退款接口进行对款操作。下面大致介绍下微信支付中退款的流程、主要代码以及一些我测出的bug解决方法。
先说下我们需要哪些jar包以及微信给我们的证书。
证书:apiclient_cert.p12。
jar包:commons-codec-1.6.jar commons-logging-1.1.3.jar fluent-hc-4.3.4.jar httpclient-4.3.4.jar httpclient-cache-4.3.4.jar httpcore-4.3.2.jar httpmime-4.3.4.jar
上面7个jar包,微信官网文档对应的demo里面都有。官网下载地址:https://mp.weixin.qq.com/paymch/readtemplate?t=mp/business/course3_tmpl&lang=zh_CN。
不想去官网找的,下列地址直接下载即可(象征性要一分下载分):http://download.csdn.net/detail/u011160656/8262955
注意:
1.交易时间超过1 年的订单无法提交退款;
2.支持部分退款, 部分退需要设置相同的订单号和不同的out_refund_no。一笔退款失败后重新提交,要采用原来的out_refund_no。总退款金额丌能超过用户实际支付金额。
看看退款接口中需要哪些参数。
特别注意:图中的refund_fee和total_fee官方文档写的类型为Int,其实我用string也是可以,其中原因不明。
代码如下:代码需要稍作修改,具体修改地方已在main方法中做了文字描述
SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
parameters.put("appid", "wx0953bae287adfeee");
parameters.put("mch_id", "你的微信支付商户号");
parameters.put("nonce_str", CreateNoncestr());
//在notify_url中解析微信返回的信息获取到 transaction_id,此项不是必填,详细请看上图文档
parameters.put("transaction_id", "微信支付订单中调用统一接口后微信返回的 transaction_id");
parameters.put("out_trade_no", "微信支付订单中的out_trade_no");
parameters.put("out_refund_no", "No.QM20141215002"); //我们自己设定的退款申请号,约束为UK
parameters.put("total_fee", "1") ; //单位为分
parameters.put("refund_fee", "1"); //单位为分
parameters.put("op_user_id", "你的微信支付商户号");
String sign = createSign("utf-8", parameters);
parameters.put("sign", sign);
String reuqestXml = getRequestXml(parameters);
KeyStore keyStore = KeyStore.getInstance("PKCS12");
FileInputStream instream = new FileInputStream(new File("D:/apiclient_cert.p12"));//放退款证书的路径
try {
keyStore.load(instream, "你的微信支付商户号".toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, "你的微信支付商户号".toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[] { "TLSv1" },
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
try {
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/secapi/pay/refund");//退款接口
System.out.println("executing request" + httpPost.getRequestLine());
StringEntity reqEntity = new StringEntity(reuqestXml);
// 设置类型
reqEntity.setContentType("application/x-www-form-urlencoded");
httpPost.setEntity(reqEntity);
CloseableHttpResponse response = httpclient.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
if (entity != null) {
System.out.println("Response content length: " + entity.getContentLength());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(),"UTF-8"));
String text;
while ((text = bufferedReader.readLine()) != null) {
System.out.println(text);
}
}
EntityUtils.consume(entity);
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
public static String CreateNoncestr() {
String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
String res = "";
for (int i = 0; i < 16; i++) {
Random rd = new Random();
res += chars.charAt(rd.nextInt(chars.length() - 1));
}
return res;
}
public static String createSign(String charSet,SortedMap<Object,Object> parameters){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + "zhuhaizhongyuele4008866060liuboz");
String sign = MD5Util.MD5Encode(sb.toString(), charSet).toUpperCase();
return sign;
}
public static String getRequestXml(SortedMap<Object,Object> parameters){
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if ("attach".equalsIgnoreCase(k)||"body".equalsIgnoreCase(k)||"sign".equalsIgnoreCase(k)) {
sb.append("<"+k+">"+"<![CDATA["+v+"]]></"+k+">");
}else {
sb.append("<"+k+">"+v+"</"+k+">");
}
}
sb.append("</xml>");
return sb.toString();
}
我们可以获得调用接口后返回的信息(上面main方法中,打印的text就是返回的信息)具体信息如下图:
退款成功的返回信息如下:
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx0953bae287adfeee]]></appid>
<mch_id><![CDATA[10032574]]></mch_id>
<sub_mch_id><![CDATA[]]></sub_mch_id>
<nonce_str><![CDATA[3ILl3mJupKAVrPdU]]></nonce_str>
<sign><![CDATA[1B213B3EFC73C40DFFAC5AAC777DF08D]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<transaction_id><![CDATA[1008010446201412020006419926]]></transaction_id>
<out_trade_no><![CDATA[1706467782]]></out_trade_no>
<out_refund_no><![CDATA[No.QM20141205002]]></out_refund_no>
<refund_id><![CDATA[2008010446201412050000255624]]></refund_id>
<refund_channel><![CDATA[]]></refund_channel>
<refund_fee>1</refund_fee>
<coupon_refund_fee>0</coupon_refund_fee>
</xml>
下面罗列下我测试遇到的bug:
同一账单进行二次退款操作返回信息如下
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[wx0953bae287adfeee]]></appid>
<mch_id><![CDATA[10032574]]></mch_id>
<sub_mch_id><![CDATA[]]></sub_mch_id>
<nonce_str><![CDATA[H7gbRloPIJq5zk6S]]></nonce_str>
<sign><![CDATA[295234A53534EB345C7189E31A2C422D]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<transaction_id><![CDATA[1008010446201412030006453329]]></transaction_id>
<out_trade_no><![CDATA[1406539982]]></out_trade_no>
<out_refund_no><![CDATA[20141205001]]></out_refund_no>
<refund_id><![CDATA[2008010446201412050000262217]]></refund_id>
<refund_channel><![CDATA[]]></refund_channel>
<refund_fee>1</refund_fee>
<coupon_refund_fee>0</coupon_refund_fee>
</xml>
<span style="color:#FF0000;">(仍然显示退款成功,但是只退款一次),所以我们要根据自己的订单退款状态进行判断是否进行数据库操作。</span>
退款金额( refund_fee)大于总金额( total_fee)的返回信息
<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[invalid refund_fee]]></return_msg>退款中的总金额( total_fee)与微信支付订单总金额不一致
</xml>
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg><appid><![CDATA[wx0953bae287adfeee]]></appid><mch_id><![CDATA[10032574]]></mch_id><sub_mch_id><![CDATA[]]></sub_mch_id><nonce_str><![CDATA[pt4JUHwVzYEtLzyh]]></nonce_str><sign><![CDATA[6193D0EECEE231BEDA427AABAA8A20DF]]></sign><result_code><![CDATA[FAIL]]></result_code><err_code><![CDATA[REFUND_FEE_MISMATCH]]></err_code><err_code_des><![CDATA[同一个out_refund_no退款金额要一致]]></err_code_des></xml>
退款单号( out_refund_no)不唯一
<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg>仍有不明之处,可留言给我。
<appid><![CDATA[wx0953bae287adfeee]]></appid>
<mch_id><![CDATA[10032574]]></mch_id>
<sub_mch_id><![CDATA[]]></sub_mch_id>
<nonce_str><![CDATA[T4tZ3GhGasjEszIl]]></nonce_str>
<sign><![CDATA[2A38E0FAF22FEAB10049C9D020EECE02]]></sign>
<result_code><![CDATA[FAIL]]></result_code>
<err_code><![CDATA[REFUND_ID_INVALID]]></err_code>
<err_code_des><![CDATA[退款单号非法]]></err_code_des>
</xml>