还是接上文:订单查询 、我们获取到订单详情之后、接下来就要开始  退款接口了、在我们调运退款接口的时候、我们可以先调运订单查询接口、看订单状态是否正常或者订单是否存在,如果订单存在并且订单状态正常、那我们就可以调运退款借口了。

如下


     退款API

业务功能

商户针对某一个已经成功支付的订单发起退款,操作结果在同一会话中同步返回。

一、退款方式

目前只支持原路返回退款

说明:退到银行卡则是非实时的,每个银行的处理速度不同,一般发起退款后1-3个工作日内到账。

同一笔单的部分退款需要设置相同的订单号和不同的 out_refund_no 。一笔退款失败后重新提交,要采用原来 的out_refund_no。总退款金额不能超过用户实际支付金额(现金券金额不能退款)

二、退款限制

商户在退款操作时应该注意退款限制,避免发起不会成功的退款请求,下面是主要的退款限制:

1.在平台中,只要退款累计金额不超过交易单支付总额,一笔交易单可以多次退款,退款申请单号(退款接口中有此参数)唯一确定一次退       款,而不是交易单号确定一次退款。退款申请单号由商户生成,所以商户一定要保证退款申请单的唯一性。商家在退款过程中要特别注 意,只有在能确定退款失败的情况下,才能重新发起另一笔退款。一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号。

2.请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次。

3.错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次。

4.每个支付订单的部分退款次数不能超过50次。

 

交互模式

后台系统调用交互模式

请求参数列表

请求url:https://pay.swiftpass.cn/pay/gateway

POST XML 内容体进行请求

 

 

字段名

变量名

必填

类型

说明

接口类型

service


String(32)

接口类型:unified.trade.refund

版本号

version


String(8)

版本号,version默认值是1.0。

字符集

charset


String(8)

可选值 UTF-8 ,默认为 UTF-8。

签名方式

sign_type


String(8)

签名类型,取值:MD5默认:MD5

商户号

mch_id


String(32)

商户号,由平台分配

商户订单号

out_trade_no


String(32)

商户系统内部的订单号, out_trade_no和transaction_id至少一个必填,同时存在时transaction_id优先

平台订单号

transaction_id


String(32)

平台单号, out_trade_no和transaction_id至少一个必填,同时存在时transaction_id优先

商户退款单号

out_refund_no


String(32)

商户退款单号,32个字符内、可包含字母,确保在商户系统唯一。同个退款单号多次请求,平台当一个单处理,只会退一次款。如果出现退款不成功,请采用原退款单号重新发起,避免出现重复退款。

总金额

total_fee


Int

订单总金额,单位为分

退款金额

refund_fee


Int

退款总金额,单位为分,可以做部分退款

操作员

op_user_id


String(32)

操作员帐号,默认为商户号

退款渠道

refund_channel


String(16)

ORIGINAL-原路退款,默认

随机字符串

nonce_str


String(32)

随机字符串,不长于 32 位

签名

sign


String(32)

MD5签名结果,详见“安全规范”

返回结果

数据按XML的格式实时返回

字段名

变量名

必填

类型

说明

版本号

version


String(8)

版本号,version默认值是2.0。

字符集

charset


String(8)

可选值 UTF-8 ,默认为 UTF-8。

签名方式

sign_type


String(8)

签名类型,取值:MD5默认:MD5

返回状态码

status


String(16)

0表示成功,非0表示失败此字段是通信标识,非交易标识,交易是否成功需要查看 result_code 来判断

返回信息

message


String(128)

异常或错误时返回信息,具体描述请看文档最后返回信息列表

以下字段在 status 为 0的时候有返回

业务结果

result_code


String(16)

0表示成功,非0表示失败

注:此处返回0表示退款申请接收成功,实际的退款结果根据退款查询接口查询

商户号

mch_id


String(32)

商户号,由平台分配

设备号

device_info


String(32)

平台分配的终端设备号

随机字符串

nonce_str


String(32)

随机字符串,不长于 32 位

错误代码

err_code


String(32)

具体错误码请看文档最后错误码列表

签名

sign


String(32)

MD5签名结果,详见“安全规范”

以下字段在 status 和 result_code 都为 0的时候有返回

平台订单号

transaction_id


String(32)

平台订单号。

商户订单号

out_trade_no


String(32)

商户系统内部的订单号

商户退款单号

out_refund_no


String(32)

商户退款单号

平台退款单号

refund_id


String(32)

平台退款单号

退款渠道

refund_channel


String(16)

ORIGINAL—原路退款,默认

退款金额

refund_fee


Int

退款总金额,单位为分,可以做部分退款

现金券退款金额

coupon_refund_fee


Int

现金券退款金额 <= 退款金额, 退款金额-现金券退款金额为现金


 


由文档我们可以获取到我们需要传的参数和返回的参数、所以、最重要的、、突然想到、、要有一个自己的商户才好啊 ,,,,  不闲扯了

注意:文档中的必传参数

下面我们看demo

1  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 2         req.setCharacterEncoding("utf-8");
 3         resp.setCharacterEncoding("utf-8");
 4         
 5         SortedMap<String,String> map = XmlUtils.getParameterMap(req);
 6         System.out.println(XmlUtils.toXml(map));
 7         String key = SwiftpassConfig.key;
 8         String res = null;
 9         String reqUrl = SwiftpassConfig.req_url;
10         map.put("mch_id", SwiftpassConfig.mch_id);
11         map.put("op_user_id", SwiftpassConfig.mch_id);
12         map.put("nonce_str", String.valueOf(new Date().getTime()));
13         
14         Map<String,String> params = SignUtils.paraFilter(map);
15         StringBuilder buf = new StringBuilder((params.size() +1) * 10);
16         SignUtils.buildPayParams(buf,params,false);
17         String preStr = buf.toString();
18         String sign = MD5.sign(preStr, "&key=" + key, "utf-8");
19         map.put("sign", sign);
20         
21         System.out.println("reqUrl:" + reqUrl);
22         
23         CloseableHttpResponse response = null;
24         CloseableHttpClient client = null;
25         try {
26             HttpPost httpPost = new HttpPost(reqUrl);
27             StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map),"utf-8");
28             httpPost.setEntity(entityParams);
29             httpPost.setHeader("Content-Type", "text/xml;charset=ISO-8859-1");
30             client = HttpClients.createDefault();
31             response = client.execute(httpPost);
32             if(response != null && response.getEntity() != null){
33                 Map<String,String> resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
34                 res = XmlUtils.toXml(resultMap);
35                 System.out.println("请求结果:" + res);
36                 
37                 if(resultMap.containsKey("sign") && !SignUtils.checkParam(resultMap, key)){
38                     res = "验证签名不通过";
39                 }
40             }else{
41                 res = "操作失败!";
42             }
43         } catch (Exception e) {
44             e.printStackTrace();
45             res = "操作失败";
46         } finally {
47             if(response != null){
48                 response.close();
49             }
50             if(client != null){
51                 client.close();
52             }
53         }
54         if(res.startsWith("<")){
55             resp.setHeader("Content-type", "text/xml;charset=UTF-8");
56         }else{
57             resp.setHeader("Content-type", "text/html;charset=UTF-8");
58         }
59         resp.getWriter().write(res);
60     }

resultMap  这里面就是返回的各种数据 --  上面文档中的返回数据

注意:1. 第5行的那个map 、那里面已经从req里面获取到了很多参数、比如service之类的、如果我们要开发的话、我们就要把那儿改一下、下面会讲到:

现在是我们本地的demo:

1 public Pair<Boolean, Map<String,String>> doOrderRefund(Payparams paramss) throws Exception {
 2         boolean result = false;
 3         Map<String,String> resultMap = new HashMap<String, String>();
 4         HashMap<String, String> rspData = new HashMap<String,String>();
 5         logger.info("---退款开始---");
 6         String orderId = paramss.getOrderId();
 7         String merId = paramss.getMerId();
 8         String merOrderId = paramss.getMerOrderId();
 9         String txnAmt = paramss.getTxnAmt();
10         String refundOrderId = "TK" + orderId;  //退款单号 = TK + 订单号
11         
12         //组织请求报文
13         SortedMap<String,String> map = new TreeMap<String, String>();  //  标志02  下面解释
14         System.out.println(XmlUtils.toXml(map));
15         String key = SwiftpassConfig.key;
16         String res = null;
17         String reqUrl = SwiftpassConfig.req_url;
18         map.put("out_trade_no",orderId); //订单号
19         map.put("out_refund_no", refundOrderId); //商户退单号
20         map.put("service", "unified.trade.refund"); //接口类型   
21         map.put("mch_id", merId);
22         map.put("op_user_id", merId);
23         map.put("total_fee", txnAmt); //int 总金额  单位是分
24         map.put("refund_fee", txnAmt);  //int  退款金额  单位是分
25         
26         map.put("nonce_str", String.valueOf(new Date().getTime())); //随机字符串
27         
28         Map<String,String> params = SignUtils.paraFilter(map);
29         StringBuilder buf = new StringBuilder((params.size() +1) * 10);
30         SignUtils.buildPayParams(buf,params,false);
31         String preStr = buf.toString();
32         String sign = MD5.sign(preStr, "&key=" + key, "utf-8");
33         map.put("sign", sign);
34         
35         logger.info("reqUrl:" + reqUrl);
36         
37         CloseableHttpResponse response = null;
38         CloseableHttpClient client = null;
39         try {
40             HttpPost httpPost = new HttpPost(reqUrl);
41             StringEntity entityParams = new StringEntity(XmlUtils.parseXML(map),"utf-8");
42             httpPost.setEntity(entityParams);
43             httpPost.setHeader("Content-Type", "text/xml;charset=ISO-8859-1");
44             client = HttpClients.createDefault();
45             response = client.execute(httpPost);
46             if(response != null && response.getEntity() != null){
47                 resultMap = XmlUtils.toMap(EntityUtils.toByteArray(response.getEntity()), "utf-8");
48                 res = XmlUtils.toXml(resultMap);
49                 System.out.println("请求结果:" + res);
50                 
51                 if(resultMap.containsKey("sign") && !SignUtils.checkParam(resultMap, key)){
52                     res = "验证签名不通过";
53                 }
54             }else{
55                 res = "操作失败!";
56             }
57         } catch (Exception e) {
58             e.printStackTrace();
59             res = "操作失败";
60         } finally {
61             if(response != null){
62                 response.close();
63             }
64             if(client != null){
65                 client.close();
66             }
67         }
68         
69         //转换成json对象
70         JSONObject respJson = JSONObject.fromObject(resultMap); //标志 03 下面解释
71         Assert.notNull(respJson, "微信退款返回信息解析 json 字串为空");
72         //解析json
73         Assert.notNull(respJson.get("result_code"), "微信退款返回码异常");
74         String respCode = respJson.getString("result_code");
75         
76         PayRes payRes = PayReturnCode.getPayRes(Contents.WX_CODE, respCode);
77         logger.info("退款结果,返回码:" + respCode + ", 退款信息:" + payRes.getMsg());
78         
79         if(respCode.equals("SUCCESS")){
80             result=true;
81         }
82         
91         return new Pair<Boolean, Map<String,String>>(result, rspData);
92     }

 

 

注释:标识02 : 因为在demo示例中那个sortMap是将request的参数排序取出来了、所以在这里我们没有request的情况下、我们就new一个TreeMap(); 然后将下面红色的那部分参数传进来、放到map里面、请求微信的接口

            标识03 : 我们获取到的是一个map<String , String> 类型的map、而微信的整个参数都在这个map中、我们在这里把map转换为json之后、就方便了我们下面对数据的处理。

 

 

 

今天在开发的时候、微信测试的商家都超过测试限额了、、、尴尬的啊、明天再说吧。