用戶掃碼支付成功,微信異步回調商戶
上一篇博客完成用戶掃碼支付功能: 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)