用户扫码支付成功,微信异步回调商户
上一篇博客完成用户扫码支付功能: https://www.cnblogs.com/qdhxhz/p/9708534.html
当用户扫码支付成功之后,微信会异步回调商户接口,告知用户支付成功。好让商户进行下一步操作。
一、接口说明
1、流程图
这里要做的就是用户支付成功后,微信异步通知商户支付结果,商户收到通知后告知支付通知接收情况。
2、接口说明
有关商户接口应注意以下几点:
(1)该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。
(2)notify_url不能有参数,外网可以直接访问,不能有访问控制(比如必须要登录才能操作)。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”
(3)支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答。
(4)对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不
保证通知最终能成功。(通知频率为15/15/30/180/1800/1800/1800/1800/3600,单位:秒)注意:同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理
重复的通知。推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。
在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
(5)特别提醒:商户系统对于支付结果通知的内容一定要做签名验证,防止数据泄漏导致出现“假通知”,造成资金损失。
二、接口开发
1、回调接口
有关ngrok工具如果不了解的话,可以参考博客: https://www.cnblogs.com/qdhxhz/p/9678137.html
/** * 微信支付回调 * 这里在统一下单中提供的notify_url地址是:http://jincou.vipgz1.idcfengye.com/api/v1/order/callback * 该域名是sunny-ngrok中的二级域名,通过它映射到微信本地 */ @RequestMapping("callback") public void orderCallback(HttpServletRequest request,HttpServletResponse response) throws Exception { InputStream inputStream = request.getInputStream(); //BufferedReader是包装设计模式,性能更搞 BufferedReader in = new BufferedReader(new InputStreamReader(inputStream,"UTF-8")); StringBuffer sb = new StringBuffer(); //1、将微信回调信息转为字符串 String line ; while ((line = in.readLine()) != null){ sb.append(line); } in.close(); inputStream.close(); //2、将xml格式字符串格式转为map集合 Map<String,String> callbackMap = WXPayUtil.xmlToMap(sb.toString()); System.out.println(callbackMap.toString()); //3、转为有序的map SortedMap<String,String> sortedMap = WXPayUtil.getSortedMap(callbackMap); //4、判断签名是否正确 if(WXPayUtil.isCorrectSign(sortedMap,weChatConfig.getKey())){ //5、判断回调信息是否成功 if("SUCCESS".equals(sortedMap.get("result_code"))){ //获取商户订单号 //商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一 String outTradeNo = sortedMap.get("out_trade_no"); System.out.println(outTradeNo); //6、数据库查找订单,如果存在则根据订单号更新该订单 VideoOrder dbVideoOrder = videoOrderService.findByOutTradeNo(outTradeNo); System.out.println(dbVideoOrder); if(dbVideoOrder != null && dbVideoOrder.getState()==0){ //判断逻辑看业务场景 VideoOrder videoOrder = new VideoOrder(); videoOrder.setOpenid(sortedMap.get("openid")); videoOrder.setOutTradeNo(outTradeNo); videoOrder.setNotifyTime(new Date()); //修改支付状态,之前生成的订单支付状态是未支付,这里表面已经支付成功的订单 videoOrder.setState(1); //根据商户订单号更新订单 int rows = videoOrderService.updateVideoOderByOutTradeNo(videoOrder); System.out.println(rows); //7、通知微信订单处理成功 if(rows == 0){ response.setContentType("text/xml"); response.getWriter().println("success"); return; } }} } //7、通知微信订单处理失败 response.setContentType("text/xml"); response.getWriter().println("fail"); }
2、校验签名方法
/** * 校验签名 */ public static boolean isCorrectSign(SortedMap<String, String> params, String key){ //1、再进行一次生成sign String sign = createSign(params,key); String weixinPaySign = params.get("sign").toUpperCase(); //将两次生成的sign比较看是否一致 return weixinPaySign.equals(sign); } /** * 生成微信支付sign */ public static String createSign(SortedMap<String, String> params, String key){ StringBuilder sb = new StringBuilder(); Set<Map.Entry<String, String>> es = params.entrySet(); Iterator<Map.Entry<String,String>> it = es.iterator(); //生成 stringA="appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA"; while (it.hasNext()){ Map.Entry<String,String> entry = (Map.Entry<String,String>)it.next(); String k = (String)entry.getKey(); String v = (String)entry.getValue(); if(null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)){ sb.append(k+"="+v+"&"); } } sb.append("key=").append(key); String sign = CommonUtils.MD5(sb.toString()).toUpperCase(); return sign; }
3、测试
(1)支付成功
(2)通过ngrok回调到本地,通过断点可以看出sb字符串格式
(3)将xml格式字符串转为map
成功!
github源码
github: https://github.com/yudiandemingzi/spring-boot-wechat-pay
我只是偶尔安静下来,对过去的种种思忖一番。那些曾经的旧时光里即便有过天真愚钝,也不值得谴责。毕竟,往后的日子,还很长。不断鼓励自己,
天一亮,又是崭新的起点,又是未知的征程(上校17)