Springboot整合微信支付(訂單過期取消及商戶主動查單)
一:問題引入
前面講到用戶支付完成之后微信支付服務器會發(fā)送回調(diào)通知給商戶,商戶要能夠正常處理這個回調(diào)通知并返回正確的狀態(tài)碼給微信支付后臺服務器,不然微信支付后臺服務器就會在一段時間之內(nèi)重復發(fā)送回調(diào)通知給商戶。具體流程見下圖:
那么這時候問題就來了,微信后臺發(fā)送回調(diào)通知次數(shù)也是有限制的,而且,微信支付開發(fā)文檔中這樣說到:對后臺通知交互時,如果微信收到商戶的應答不符合規(guī)范或超時,微信認為通知失敗,微信會通過一定的策略定期重新發(fā)起通知,盡可能提高通知的成功率,但微信不保證通知最終能成功。也就是說我們不能單單通過微信支付的回調(diào)通知來被動地更新訂單狀態(tài),假如接收微信回調(diào)通知失敗但是這時候用戶是已經(jīng)付了款的,而商戶這邊卻顯示未付款狀態(tài),假如沒有作進一步處理就會造成一些不必要的麻煩。這時候就需要商戶主動向微信支付后臺查詢訂單狀態(tài)。
二:處理流程
一開始我采用的策略并不是延遲隊列,而是采用定時器定時查詢數(shù)據(jù)庫來實現(xiàn)商戶主動查單并實現(xiàn)訂單過期自動取消功能,但是這一做法十分不友好,體現(xiàn)在下面幾個方面:
- 定時查詢數(shù)據(jù)庫會加大數(shù)據(jù)庫負擔
- 假如數(shù)據(jù)量很大,頻繁查詢數(shù)據(jù)庫消耗的時間較多
- 會造成時間誤差,定時時間過長誤差大,定時時間過短查詢又太頻繁
假如我設定的定時時間是5分鐘查詢一次,那么假如定時器還有1秒就要去查詢一次,但是有一批訂單還有2秒才到期,這時候定時器就處理不到這批訂單。等下次再進行處理時候這批訂單已經(jīng)過期差不多5分鐘了,這顯然是很大的一個缺陷。此外假如很長一段時間都沒有用戶下單,但是由于定時器并不知道什么時候有用戶下單什么時候沒有用戶下單,它只是個一到點就開始定時查詢的無感情機器,這樣就會產(chǎn)生一些不必要的開銷。
實現(xiàn)訂單過期自動刪除的策略有很多,其中一種就是我上面提到的數(shù)據(jù)庫輪詢方法,此外,還可以采用的策略有:JDK的延遲隊列、時間輪算法、redis緩存、使用消息隊列等等,我選用的策略是采用RabbitMQ的延遲隊列來實現(xiàn),至于延遲隊列的實現(xiàn)細節(jié)我將在下一篇文章講解,這里僅介紹訂單處理部分。
處理策略為商戶下單之后生成訂單存入數(shù)據(jù)庫并將該訂單號存入延遲隊列,此時訂單狀態(tài)為“未支付”,假如接收微信回調(diào)成功并且驗證到用戶已付款,這時候就更新數(shù)據(jù)庫中該訂單狀態(tài)為“已付款”。當延遲隊列到期進行消費時,根據(jù)延遲隊列中的訂單號先在數(shù)據(jù)庫中進行查詢,假如這時候數(shù)據(jù)庫中該訂單狀態(tài)為“已支付”,這時候就不需要進行處理,假如訂單狀態(tài)為“未支付”,商戶程序應主動向微信支付后臺進行訂單狀態(tài)查詢,如果訂單狀態(tài)為已支付,這時候就不需要進行處理,如果訂單狀態(tài)為未支付,這時候就將訂單狀態(tài)改為“已取消”。一開始我的做法為無論數(shù)據(jù)庫中該訂單狀態(tài)是否已支付都向微信支付后臺進行訂單狀態(tài)查詢,然后再根據(jù)查詢結(jié)果做進一步處理,顯然這種做法存在缺陷,就是每一筆訂單都主動向微信支付后臺進行查詢會消耗很大的網(wǎng)絡帶寬,而且假如已經(jīng)成功接收到微信支付回調(diào)通知的訂單并不需要進行再次查詢確認。
三:代碼實現(xiàn)
3.1:orderServiceImpl.java
/** * 提交訂單 * @param orders * @param session */ @Override @Transactional public Orders createOrder(Orders orders, HttpSession session) { //獲取當前用戶信息 Long userId = (Long) session.getAttribute("user"); //查詢地址數(shù)據(jù) Long addressBookId = orders.getAddressBookId(); AddressBook addressBook = addressBookService.getById(addressBookId); if(addressBook == null) { throw new CustomException("用戶地址信息有誤,不能下單"); } //獲取當前用戶購物車數(shù)據(jù) LambdaQueryWrapper<ShoppingCart> SCLqw = new LambdaQueryWrapper<>(); SCLqw.eq(ShoppingCart::getUserId,userId); List<ShoppingCart> shoppingCartList = shoppingCartService.list(SCLqw); //生成訂單號 long orderId = IdWorker.getId(); //設置訂單號 orders.setNumber(String.valueOf(orderId)); //設置訂單狀態(tài)(待付款) orders.setStatus(1); //設置下單用戶id orders.setUserId(userId); //設置下單時間 orders.setOrderTime(LocalDateTime.now()); //設置付款時間 orders.setCheckoutTime(LocalDateTime.now()); //設置實收金額 AtomicInteger amount = new AtomicInteger(0); for(ShoppingCart shoppingCart : shoppingCartList) { amount.addAndGet(shoppingCart.getAmount().multiply(new BigDecimal(100)).multiply(new BigDecimal(shoppingCart.getNumber())).intValue()); } orders.setAmount(BigDecimal.valueOf(amount.get())); //設置用戶信息 User user = userService.getById(userId); orders.setPhone(addressBook.getPhone()); orders.setAddress(addressBook.getDetail()); orders.setUserName(user.getPhone()); orders.setConsignee(addressBook.getConsignee()); //保存單條訂單信息 this.save(orders); //設置訂單詳細信息 List<OrderDetail> orderDetailList = new ArrayList<>(); for(ShoppingCart shoppingCart : shoppingCartList) { OrderDetail orderDetail = new OrderDetail(); orderDetail.setName(shoppingCart.getName()); orderDetail.setImage(shoppingCart.getImage()); orderDetail.setOrderId(orderId); orderDetail.setDishId(shoppingCart.getDishId()); orderDetail.setSetmealId(shoppingCart.getSetmealId()); orderDetail.setDishFlavor(shoppingCart.getDishFlavor()); orderDetail.setNumber(shoppingCart.getNumber()); AtomicInteger detailAmount = new AtomicInteger(0); detailAmount.addAndGet(shoppingCart.getAmount().multiply(new BigDecimal(shoppingCart.getNumber())).intValue()); orderDetail.setAmount(BigDecimal.valueOf(detailAmount.get())); orderDetailList.add(orderDetail); } //批量保存訂單詳細數(shù)據(jù) orderDetailService.saveBatch(orderDetailList); //清空購物車數(shù)據(jù) shoppingCartService.remove(SCLqw); //設置延遲隊列,過期時間為5分鐘 log.info("訂單編號:{}進入延遲隊列...",orders.getNumber()); delayProducer.publish(orders.getNumber(),String.valueOf(orders.getId()), DelayMessageConfig.DELAY_EXCHANGE_NAME,DelayMessageConfig.ROUTING_KEY_ORDER,1000*60*5); return orders; }
3.2:RabbitmqDelayConsumer.java
/** * 監(jiān)聽訂單延遲隊列 * @param orderNo * @throws Exception */ @RabbitListener(queues = {"plugin.delay.order.queue"}) public void orderDelayQueue(String orderNo, Message message, Channel channel) throws Exception { log.info("訂單延遲隊列開始消費..."); try { //處理訂單 wxPayService.checkOrderStatus(orderNo); //告訴服務器收到這條消息 已經(jīng)被我消費了 可以在隊列刪掉 這樣以后就不會再發(fā)了 否則消息服務器以為這條消息沒處理掉 后續(xù)還會在發(fā) channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); log.info("消息接收成功"); } catch (Exception e) { e.printStackTrace(); //消息重新入隊 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,true); log.info("消息接收失敗,重新入隊"); } }
3.3:WxPayServiceImpl.java
/** * 商戶主動查詢訂單狀態(tài) * 當核實到訂單超時未支付則取消訂單 * 當核實到訂單已支付則更新訂單狀態(tài) */ @Transactional(rollbackFor = Exception.class) @Override public void checkOrderStatus(String orderNo) throws Exception { log.info("根據(jù)訂單號核實訂單狀態(tài)==>{}",orderNo); log.info("在數(shù)據(jù)庫中查詢訂單狀態(tài)...."); Integer status = ordersService.getOrderStatus(orderNo); if(status != 1) { //訂單不是”未支付“狀態(tài) log.info("訂單不是”未支付“狀態(tài),無需進行進一步處理"); return; } String result = this.queryOrder(orderNo); Gson gson = new Gson(); Map<String,String> map = gson.fromJson(result, HashMap.class); //獲取訂單狀態(tài) String tradeState = map.get("trade_state"); //判斷訂單狀態(tài) if(WxTradeState.NOTPAY.getType().equals(tradeState)) { log.info("核實到訂單超時未支付==>{}",orderNo); //關(guān)閉訂單 log.info("訂單已自動取消"); this.closeOrder(orderNo); //更新本地訂單狀態(tài) ordersService.updateStatusByOrderNo(orderNo,"5"); } else if(WxTradeState.SUCCESS.getType().equals(tradeState)) { log.info("核實到訂單已支付==>{}",orderNo); Integer orderStatus = ordersService.getOrderStatus(orderNo); if(orderStatus != null && orderStatus != 2) { //更新本地訂單狀態(tài) ordersService.updateStatusByOrderNo(orderNo,"2"); //保存訂單記錄 paymentInfoService.saveInfo(result); } } else if(WxTradeState.CLOSED.getType().equals(tradeState)) { log.info("核實到訂單已取消==>{}",orderNo); Integer orderStatus = ordersService.getOrderStatus(orderNo); if(orderStatus != null && orderStatus != 5) { //更新本地訂單狀態(tài) ordersService.updateStatusByOrderNo(orderNo,"5"); //保存訂單記錄 paymentInfoService.saveInfo(result); } } } /** * 查詢訂單 * @param orderNo * @return */ @Override public String queryOrder(String orderNo) throws Exception { log.info("查單接口調(diào)用===>{}",orderNo); //構(gòu)建請求鏈接 String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(),orderNo); url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId()); URIBuilder uriBuilder = new URIBuilder(url); HttpGet httpGet = new HttpGet(uriBuilder.build()); httpGet.addHeader("Accept","application/json"); CloseableHttpResponse response = wxPayClient.execute(httpGet); return EntityUtils.toString(response.getEntity()); }
到此這篇關(guān)于Springboot整合微信支付(訂單過期取消及商戶主動查單)的文章就介紹到這了,更多相關(guān)Springboot 微信支付內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- SpringBoot實現(xiàn)微信支付接口調(diào)用及回調(diào)函數(shù)(商戶參數(shù)獲取)
- java?Springboot對接開發(fā)微信支付詳細流程
- SpringBoot對接小程序微信支付的實現(xiàn)
- UniApp?+?SpringBoot?實現(xiàn)微信支付和退款功能
- SpringBoot實現(xiàn)整合微信支付方法詳解
- springboot對接微信支付的完整流程(附前后端代碼)
- 一篇文章帶你入門Springboot整合微信登錄與微信支付(附源碼)
- springboot整合微信支付sdk過程解析
- SpringBoot+MyBatis集成微信支付實現(xiàn)示例
相關(guān)文章
springboot整合logback實現(xiàn)日志管理操作
本章節(jié)是記錄logback在springboot項目中的簡單使用,本文將會演示如何通過logback將日志記錄到日志文件或輸出到控制臺等管理操作,感興趣的朋友跟隨小編一起看看吧2024-02-02說說@ModelAttribute在父類和子類中的執(zhí)行順序
這篇文章主要介紹了@ModelAttribute在父類和子類中的執(zhí)行順序,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06Springboot+Spring Security實現(xiàn)前后端分離登錄認證及權(quán)限控制的示例代碼
本文主要介紹了Springboot+Spring Security實現(xiàn)前后端分離登錄認證及權(quán)限控制的示例代碼,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11Java中JUC包(java.util.concurrent)下的常用子類
相信大家已經(jīng)對并發(fā)機制中出現(xiàn)的很多的常見知識點進行了總結(jié),下面這篇文章主要給大家介紹了關(guān)于Java中JUC包(java.util.concurrent)下的常用子類的相關(guān)資料,文中通過圖文以及示例代碼介紹的非常詳細,需要的朋友可以參考下2022-12-12Java ArrayList.toArray(T[]) 方法的參數(shù)類型是 T 而不是 E的原因分析
這篇文章主要介紹了Java ArrayList.toArray(T[]) 方法的參數(shù)類型是 T 而不是 E的原因分析的相關(guān)資料,需要的朋友可以參考下2016-04-04