浅析微信支付:申请退款、退款回调接口、查询退款 (3)

这里有一个比较需要注意的点,在我们调用退款之后,会返回一些异常处理情况,官方文档中收录了一系列错误码code,我们可以在系统中对其进行处理,这里就不细说了。

2、退款回调接口

以下为微信官方的退款结果通知文档:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=10 2.1. 应用场景

当商户申请的退款有结果后,微信会把相关结果发送给商户,商户需要接收处理,并返回应答。
对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。

(通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)

注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。

特别说明:退款结果对重要的数据进行了加密,商户需要用商户秘钥进行解密后才能获得结果通知的内容

2.2. 接口链接

在申请退款接口中上传参数“notify_url”以开通该功能
如果链接无法访问,商户将无法接收到微信通知。
通知url必须为直接可访问的url,不能携带参数。

示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action” 2.3. 解密方式 解密步骤如下: (1)对加密串A做base64解码,得到加密串B (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台-->账户设置-->API安全-->密钥设置 ) (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding)

PS:特别注意,如果要进行微信AES解密,因为GJ的进口管制限制,Java发布的运行环境包中的加解密有一定的限制。默认不允许256位密钥的AES加解密,解决方法就是修改策略文件,我们需要从官方网站下载无限制权限策略文件,注意自己JDK的版本别下错了。

jdk8的jce下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html

将local_policy.jar和US_export_policy.jar这两个文件替换%JRE_HOME%\lib\security和%JDK_HOME%\jre\lib\security下原来的文件,注意先备份原文件。

如果是jdk8,可能会遇到安全目录下有policy文件夹的情况,拿作者的电脑举例,jdk路径为/opt/jdk1.8.0_152/jre/lib/security/policy,此目录下有两个子文件夹limited、limited,需要替换limited文件夹下的文件 local_policy.jar、US_export_policy.jar这两个,最好先备份哦~!!!替换后重启项目即可。

2.4. 调用接口

因为退款回调接口是咋们系统被动接收微信的消息,所以此处和支付回调接口一致,也是使用了流的方式,格式为xml,下面我们来看代码:

/** * 退款结果通知 * <p> * 在申请退款接口中上传参数“notify_url”以开通该功能 * 如果链接无法访问,商户将无法接收到微信通知。 * 通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action” * <p> * 当商户申请的退款有结果后,微信会把相关结果发送给商户,商户需要接收处理,并返回应答。 * 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。 * (通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒) * 注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 * 推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。 * 特别说明:退款结果对重要的数据进行了加密,商户需要用商户秘钥进行解密后才能获得结果通知的内容 * @param request req * @param response resp * @return res xml * * @author yclimb * @date 2018/6/21 */ @ApiOperation(value = "微信支付|微信退款回调接口", httpMethod = "POST", notes = "该链接是通过【微信退款API】中提交的参数notify_url设置,如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效。") @RequestMapping("/refund") public void refund(HttpServletRequest request, HttpServletResponse response) { String resXml = ""; InputStream inStream; try { inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } WXPayUtil.getLogger().info("refund:微信退款----start----"); // 获取微信调用我们notify_url的返回信息 String result = new String(outSteam.toByteArray(), "utf-8"); WXPayUtil.getLogger().info("refund:微信退款----result----=" + result); // 关闭流 outSteam.close(); inStream.close(); // xml转换为map Map<String, String> map = WXPayUtil.xmlToMap(result); if (WXPayConstants.SUCCESS.equalsIgnoreCase(map.get(WXPayConstants.RETURN_CODE))) { WXPayUtil.getLogger().info("refund:微信退款----返回成功"); /** 以下字段在return_code为SUCCESS的时候有返回: **/ // 加密信息:加密信息请用商户秘钥进行解密,详见解密方式 String req_info = map.get("req_info"); /** * 解密方式 * 解密步骤如下: * (1)对加密串A做base64解码,得到加密串B * (2)对商户key做md5,得到32位小写key* ( key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置 ) * (3)用key*对加密串B做AES-256-ECB解密(PKCS7Padding) */ String resultStr = AESUtil.decryptData(req_info); // WXPayUtil.getLogger().info("refund:解密后的字符串:" + resultStr); Map<String, String> aesMap = WXPayUtil.xmlToMap(resultStr); /** 以下为返回的加密字段: **/ // 商户退款单号 是 String(64) 1.21775E+27 商户退款单号 String out_refund_no = aesMap.get("out_refund_no"); // 退款状态 是 String(16) SUCCESS SUCCESS-退款成功、CHANGE-退款异常、REFUNDCLOSE—退款关闭 String refund_status = aesMap.get("refund_status"); // 商户订单号 是 String(32) 1.21775E+27 商户系统内部的订单号 String out_trade_no = aesMap.get("out_trade_no"); /*// 微信订单号 是 String(32) 1.21775E+27 微信订单号 String transaction_id = null; // 微信退款单号 是 String(32) 1.21775E+27 微信退款单号 String refund_id = null; // 订单金额 是 Int 100 订单总金额,单位为分,只能为整数,详见支付金额 String total_fee = null; // 应结订单金额 否 Int 100 当该订单有使用非充值券时,返回此字段。应结订单金额=订单金额-非充值代金券金额,应结订单金额<=订单金额。 String settlement_total_fee = null; // 申请退款金额 是 Int 100 退款总金额,单位为分 String refund_fee = null; // 退款金额 是 Int 100 退款金额=申请退款金额-非充值代金券退款金额,退款金额<=申请退款金额 String settlement_refund_fee = null;*/ // 退款是否成功 if (!WXPayConstants.SUCCESS.equals(refund_status)) { resXml = resFailXml; } else { // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了. resXml = resSuccessXml; isSuccess = true; } // 根据付款单号查询付款记录 out_refund_no // 付款记录修改 & 记录付款日志 if (payment != null) { WXPayUtil.getLogger().error("refund:微信支付回调:修改支付单"); } else { WXPayUtil.getLogger().error("refund:微信支付回调:找不到对应的支付单"); } } else { WXPayUtil.getLogger().error("refund:支付失败,错误信息:" + map.get(WXPayConstants.RETURN_MSG)); resXml = resFailXml; } } catch (Exception e) { WXPayUtil.getLogger().error("refund:微信退款回调发布异常:", e); } finally { try { // 处理业务完毕 BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); out.close(); } catch (IOException e) { WXPayUtil.getLogger().error("refund:微信退款回调发布异常:out:", e); } } }

以上代码详细解释了如何接收微信回调数据和解码数据,具体的AESUtil.decryptData(req_info)请参考作者源码,文末有地址,这里就不细讲了。

具体的退款接收参数请参考微信官方文档,需要注意的是商户退款单号和微信退款单号,此两个参数是修改和记录退款的必要凭证。

3、查询退款

以下为微信官方的查询退款文档:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5 3.1. 应用场景

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsxyzf.html