
个人简介:
📦个人主页:赵四司机
🏆学习方向:JAVA后端开发
📣种一棵树最好的时间是十年前,其次是现在!
🧡喜欢的话麻烦点点关注喔,你们的支持是我的最大动力。
前言
目前更新的是Springboot项目整合微信支付系列的文章,可以在我的主页中找到该系列其他文章,这一系列的文章将会系统介绍如何在项目中引入微信支付的下单、关单、处理回调通知等功能。由于前面创作经验不足,写的文章可能不是很好,后面我会多加努力学习怎么创作,也请各位大佬有什么建议的可以不吝赐教。因为我侧重的方面不是介绍项目开发,所以关于项目开发的具体代码可以查看文末的项目源代码(后面可能会出文章介绍该项目的开发)。喜欢的话希望大家多多点赞评论收藏,当然还可以加个关注喔。
文章目录
前面讲到用户支付完成之后微信支付服务器会发送回调通知给商户,商户要能够正常处理这个回调通知并返回正确的状态码给微信支付后台服务器,不然微信支付后台服务器就会在一段时间之内重复发送回调通知给商户。具体流程见下图:

那么这时候问题就来了,微信后台发送回调通知次数也是有限制的,而且,微信支付开发文档中这样说到:对后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽可能提高通知的成功率,但微信不保证通知最终能成功。也就是说我们不能单单通过微信支付的回调通知来被动地更新订单状态,假如接收微信回调通知失败但是这时候用户是已经付了款的,而商户这边却显示未付款状态,假如没有作进一步处理就会造成一些不必要的麻烦。这时候就需要商户主动向微信支付后台查询订单状态。

一开始我采用的策略并不是延迟队列,而是采用定时器定时查询数据库来实现商户主动查单并实现订单过期自动取消功能,但是这一做法十分不友好,体现在下面几个方面:
假如我设定的定时时间是5分钟查询一次,那么假如定时器还有1秒就要去查询一次,但是有一批订单还有2秒才到期,这时候定时器就处理不到这批订单。等下次再进行处理时候这批订单已经过期差不多5分钟了,这显然是很大的一个缺陷。此外假如很长一段时间都没有用户下单,但是由于定时器并不知道什么时候有用户下单什么时候没有用户下单,它只是个一到点就开始定时查询的无感情机器,这样就会产生一些不必要的开销。
实现订单过期自动删除的策略有很多,其中一种就是我上面提到的数据库轮询方法,此外,还可以采用的策略有:JDK的延迟队列、时间轮算法、redis缓存、使用消息队列等等,我选用的策略是采用RabbitMQ的延迟队列来实现,至于延迟队列的实现细节我将在下一篇文章讲解,这里仅介绍订单处理部分。
处理策略为商户下单之后生成订单存入数据库并将该订单号存入延迟队列,此时订单状态为“未支付”,假如接收微信回调成功并且验证到用户已付款,这时候就更新数据库中该订单状态为“已付款”。当延迟队列到期进行消费时,根据延迟队列中的订单号先在数据库中进行查询,假如这时候数据库中该订单状态为“已支付”,这时候就不需要进行处理,假如订单状态为“未支付”,商户程序应主动向微信支付后台进行订单状态查询,如果订单状态为已支付,这时候就不需要进行处理,如果订单状态为未支付,这时候就将订单状态改为“已取消”。一开始我的做法为无论数据库中该订单状态是否已支付都向微信支付后台进行订单状态查询,然后再根据查询结果做进一步处理,显然这种做法存在缺陷,就是每一笔订单都主动向微信支付后台进行查询会消耗很大的网络带宽,而且假如已经成功接收到微信支付回调通知的订单并不需要进行再次查询确认。
3.1:orderServiceImpl.java
/**
* 提交订单
* @param orders
* @param session
*/
@Override
@Transactional
public Orders createOrder(Orders orders, HttpSession session) {
//获取当前用户信息
Long userId = (Long) session.getAttribute("user");
//查询地址数据
Long addressBookId = orders.getAddressBookId();
AddressBook addressBook = addressBookService.getById(addressBookId);
if(addressBook == null) {
throw new CustomException("用户地址信息有误,不能下单");
}
//获取当前用户购物车数据
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));
//设置订单状态(待付款)
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);
}
//批量保存订单详细数据
orderDetailService.saveBatch(orderDetailList);
//清空购物车数据
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
/**
* 监听订单延迟队列
* @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);
//告诉服务器收到这条消息 已经被我消费了 可以在队列删掉 这样以后就不会再发了 否则消息服务器以为这条消息没处理掉 后续还会在发
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
/**
* 商户主动查询订单状态
* 当核实到订单超时未支付则取消订单
* 当核实到订单已支付则更新订单状态
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void checkOrderStatus(String orderNo) throws Exception {
log.info("根据订单号核实订单状态==>{}",orderNo);
log.info("在数据库中查询订单状态....");
Integer status = ordersService.getOrderStatus(orderNo);
if(status != 1) {
//订单不是”未支付“状态
log.info("订单不是”未支付“状态,无需进行进一步处理");
return;
}
String result = this.queryOrder(orderNo);
Gson gson = new Gson();
Map<String,String> map = gson.fromJson(result, HashMap.class);
//获取订单状态
String tradeState = map.get("trade_state");
//判断订单状态
if(WxTradeState.NOTPAY.getType().equals(tradeState)) {
log.info("核实到订单超时未支付==>{}",orderNo);
//关闭订单
log.info("订单已自动取消");
this.closeOrder(orderNo);
//更新本地订单状态
ordersService.updateStatusByOrderNo(orderNo,"5");
}
else if(WxTradeState.SUCCESS.getType().equals(tradeState)) {
log.info("核实到订单已支付==>{}",orderNo);
Integer orderStatus = ordersService.getOrderStatus(orderNo);
if(orderStatus != null && orderStatus != 2) {
//更新本地订单状态
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) {
//更新本地订单状态
ordersService.updateStatusByOrderNo(orderNo,"5");
//保存订单记录
paymentInfoService.saveInfo(result);
}
}
}
/**
* 查询订单
* @param orderNo
* @return
*/
@Override
public String queryOrder(String orderNo) throws Exception {
log.info("查单接口调用===>{}",orderNo);
//构建请求链接
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());
}
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘
我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="
假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立
项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU
@作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors 1、什么是behaviors 2、behaviors的工作方式 3、创建behavior 4、导入并使用behavior 5、behavior中所有可用的节点 6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors 1、什么是behaviorsbehaviors是小程序中,用于实现
是否可以在所有delayed_job任务之前运行一个方法?基本上,我们试图确保每个运行delayed_job的服务器都有我们代码的最新实例,所以我们想运行一个方法来在每个作业运行之前检查它。(我们已经有了“check”方法并在别处使用它。问题只是关于如何从delayed_job中调用它。) 最佳答案 现在有一种官方方法可以通过插件来做到这一点。这篇博文通过示例清楚地描述了如何执行此操作http://www.salsify.com/blog/delayed-jobs-callbacks-and-hooks-in-rails(本文中描述
我正在尝试创建一个带有项目符号字符的Ruby1.9.3字符串。str="•"+"helloworld"但是,当我输入它时,我收到有关非ASCII字符的语法错误。我该怎么做? 最佳答案 你可以把Unicode字符放在那里。str="\u2022"+"helloworld" 关于ruby-如何在Ruby字符串中插入项目符号字符?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1195