微信支付04

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

如下
 


     退款API

业务功能

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

一、退款方式

目前只支持原路返回退款

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

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

二、退款限制

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

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

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

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

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

 

微信支付04

交互模式

后台系统调用交互模式

请求参数列表

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

POST XML 内容体进行请求

 

 

字段名变量名必填类型说明
接口类型serviceString(32)接口类型:unified.trade.refund
版本号versionString(8)版本号,version默认值是1.0。
字符集charsetString(8)可选值 UTF-8 ,默认为 UTF-8。
签名方式sign_typeString(8)签名类型,取值:MD5默认:MD5
商户号mch_idString(32)商户号,由平台分配
商户订单号out_trade_noString(32)商户系统内部的订单号, out_trade_no和transaction_id至少一个必填,同时存在时transaction_id优先
平台订单号transaction_idString(32)平台单号, out_trade_no和transaction_id至少一个必填,同时存在时transaction_id优先
商户退款单号out_refund_noString(32)商户退款单号,32个字符内、可包含字母,确保在商户系统唯一。同个退款单号多次请求,平台当一个单处理,只会退一次款。如果出现退款不成功,请采用原退款单号重新发起,避免出现重复退款。
总金额total_feeInt订单总金额,单位为分
退款金额refund_feeInt退款总金额,单位为分,可以做部分退款
操作员op_user_idString(32)操作员帐号,默认为商户号
退款渠道refund_channelString(16)ORIGINAL-原路退款,默认
随机字符串nonce_strString(32)随机字符串,不长于 32 位
签名signString(32)MD5签名结果,详见“安全规范”

返回结果

数据按XML的格式实时返回

字段名变量名必填类型说明
版本号versionString(8)版本号,version默认值是2.0。
字符集charsetString(8)可选值 UTF-8 ,默认为 UTF-8。
签名方式sign_typeString(8)签名类型,取值:MD5默认:MD5
返回状态码statusString(16)0表示成功,非0表示失败此字段是通信标识,非交易标识,交易是否成功需要查看 result_code 来判断
返回信息messageString(128)异常或错误时返回信息,具体描述请看文档最后返回信息列表
以下字段在 status 为 0的时候有返回
业务结果result_codeString(16)0表示成功,非0表示失败
注:此处返回0表示退款申请接收成功,实际的退款结果根据退款查询接口查询
商户号mch_idString(32)商户号,由平台分配
设备号device_infoString(32)平台分配的终端设备号
随机字符串nonce_strString(32)随机字符串,不长于 32 位
错误代码err_codeString(32)具体错误码请看文档最后错误码列表
签名signString(32)MD5签名结果,详见“安全规范”
以下字段在 status 和 result_code 都为 0的时候有返回
平台订单号transaction_idString(32)平台订单号。
商户订单号out_trade_noString(32)商户系统内部的订单号
商户退款单号out_refund_noString(32)商户退款单号
平台退款单号refund_idString(32)平台退款单号
退款渠道refund_channelString(16)ORIGINAL—原路退款,默认
退款金额refund_feeInt退款总金额,单位为分,可以做部分退款
现金券退款金额coupon_refund_feeInt现金券退款金额 <= 退款金额, 退款金额-现金券退款金额为现金

 
 


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

注意:文档中的必传参数
下面我们看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 }

 demo中直接是把返回结果打印在了页面、response掉了、所以、我们需要拿到那个    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 //解析json73 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之后、就方便了我们下面对数据的处理。
 
 
 
今天在开发的时候、微信测试的商家都超过测试限额了、、、尴尬的啊、明天再说吧。
 
 
 
 
 
 
 
 

相关文章