记录余额支付后的订单状态更新、购买记录策略模式改造,以及微信支付发起、状态查询与回调处理。
本篇要点
处理余额支付后的订单状态
用策略模式整理购买记录
梳理微信支付请求与回调
**学习提示:**微信支付相关代码和测试片段属于学习记录,不代表已经通过真实商户环境的端到端验收。
内容回顾 1、提交订单接口
检查和扣减余额
保存订单数据
延迟消息(订单超时未支付自动取消)
– RabbitMQ的TTL+死信队列
– RabbitMQ延迟插件
– Redisson实现
今天内容 0、订单模块总结 第一,订单模块流程(图)
第二,细节或者技术等
进行重复提交判断
进行数据加密防止篡改
使用策略模式对添加购买记录优化
1、余额支付完成修改订单状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public void orderPaySuccess (String orderNo) { OrderInfo orderInfo = this .getOrderInfoByOrderNo(orderNo); if (orderInfo.getOrderStatus().equals("0901" )) { orderInfo.setOrderStatus("0902" ); orderInfoMapper.updateById(orderInfo); } } public OrderInfo getOrderInfoByOrderNo (String orderNo) { LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(OrderInfo::getOrderNo, orderNo); OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper); Long orderId = orderInfo.getId(); LambdaQueryWrapper<OrderDetail> wrapper = new LambdaQueryWrapper <>(); wrapper.eq(OrderDetail::getOrderId, orderId); List<OrderDetail> orderDetails = orderDetailMapper.selectList(wrapper); orderInfo.setOrderDetailList(orderDetails); return orderInfo; }
2、优化保存购买记录代码 设计模式
策略模式概念
– 购买专辑、vip和声音都是添加购买记录,因为购买内容不同操作逻辑不同的
策略模式实现 策略模式的主要角色如下:
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
环境(Context)类:用来操作策略的上下文环境,屏蔽高层模块(客户端)对策略、算法的直接访问,封装可能存在的变化。
第一步 创建接口,定义保存购买记录方法 1 2 3 4 5 6 public interface ItemTypeStrategy { public void savePaidRecord (UserPaidRecordVo userPaidRecordVo) ; }
第二步 创建多个策略类(专辑、vip、声音)实现接口方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 @Component public class VipItemTypeStrategyImpl implements ItemTypeStrategy { @Autowired private UserInfoMapper userInfoMapper; @Autowired private VipServiceConfigMapper vipServiceConfigMapper; @Autowired private UserVipServiceMapper userVipServiceMapper; @Override public void savePaidRecord (UserPaidRecordVo userPaidRecordVo) { UserVipService userVipService = new UserVipService (); Long vipConfigId = userPaidRecordVo.getItemIdList().get(0 ); VipServiceConfig vipServiceConfig = vipServiceConfigMapper.selectById(vipConfigId); UserInfo userInfo = userInfoMapper.selectById(userPaidRecordVo.getUserId()); Date currentTime = new Date (); if (userInfo.getIsVip().intValue() == 1 && userInfo.getVipExpireTime().after(new Date ())) { currentTime = userInfo.getVipExpireTime(); } currentTime = new LocalDateTime (currentTime) .plusMonths(vipServiceConfig.getServiceMonth()).toDate(); userVipService.setExpireTime(currentTime); userVipService.setStartTime(new Date ()); userVipService.setUserId(userPaidRecordVo.getUserId()); userVipService.setOrderNo(userPaidRecordVo.getOrderNo()); userVipServiceMapper.insert(userVipService); userInfo.setIsVip(1 ); userInfo.setVipExpireTime(userVipService.getExpireTime()); userInfoMapper.updateById(userInfo); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Component public class AlbumItemTypeStrategyImpl implements ItemTypeStrategy { @Autowired private UserPaidAlbumMapper userPaidAlbumMapper; @Override public void savePaidRecord (UserPaidRecordVo userPaidRecordVo) { LambdaQueryWrapper<UserPaidAlbum> userPaidAlbumLambdaQueryWrapper = new LambdaQueryWrapper <>(); userPaidAlbumLambdaQueryWrapper.eq(UserPaidAlbum::getOrderNo, userPaidRecordVo.getOrderNo()); Long count = userPaidAlbumMapper.selectCount(userPaidAlbumLambdaQueryWrapper); if (count > 0 ) { return ; } UserPaidAlbum userPaidAlbum = new UserPaidAlbum (); userPaidAlbum.setOrderNo(userPaidRecordVo.getOrderNo()); userPaidAlbum.setUserId(userPaidRecordVo.getUserId()); userPaidAlbum.setAlbumId(userPaidRecordVo.getItemIdList().get(0 )); userPaidAlbumMapper.insert(userPaidAlbum); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Component public class TrackItemTypeStrategyImpl implements ItemTypeStrategy { @Autowired private UserPaidTrackMapper userPaidTrackMapper; @Autowired private TrackInfoFeignClient trackInfoFeignClient; @Override public void savePaidRecord (UserPaidRecordVo userPaidRecordVo) { LambdaQueryWrapper<UserPaidTrack> userPaidTrackLambdaQueryWrapper = new LambdaQueryWrapper <>(); userPaidTrackLambdaQueryWrapper.eq(UserPaidTrack::getOrderNo, userPaidRecordVo.getOrderNo()); Long count = userPaidTrackMapper.selectCount(userPaidTrackLambdaQueryWrapper); if (count > 0 ) { return ; } TrackInfo trackInfo = trackInfoFeignClient.getTrackInfo( userPaidRecordVo.getItemIdList().get(0 )).getData(); Long albumId = trackInfo.getAlbumId(); userPaidRecordVo.getItemIdList().forEach(trackId -> { UserPaidTrack userPaidTrack = new UserPaidTrack (); userPaidTrack.setOrderNo(userPaidRecordVo.getOrderNo()); userPaidTrack.setUserId(userPaidRecordVo.getUserId()); userPaidTrack.setAlbumId(albumId); userPaidTrack.setTrackId(trackId); userPaidTrackMapper.insert(userPaidTrack); }); } }
第三步 创建环境类,根据购买类型切换使用不同实现类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Component public class StrategyFactory { @Autowired private Map<String,ItemTypeStrategy> itemTypeStrategy; public ItemTypeStrategy getStrategy (String itemType) { if (itemTypeStrategy.containsKey(itemType)) { return itemTypeStrategy.get(itemType); } throw new GuiguException (500 , "该策略" + itemType + "实现类不存在" ); } }
第四步 使用策略模式修改调用地方 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Autowired private StrategyFactory strategyFactory;@Override public void savePaidRecord (UserPaidRecordVo userPaidRecordVo) { String itemType = userPaidRecordVo.getItemType(); ItemTypeStrategy strategy = strategyFactory.getStrategy(itemType); strategy.savePaidRecord(userPaidRecordVo); }
3、订单其他接口 根据订单编号查询订单数据 1 2 3 4 5 6 7 @GuiguLogin @Operation(summary = "根据订单号获取订单信息") @GetMapping("getOrderInfo/{orderNo}") public Result<OrderInfo> getOrderInfo (@PathVariable String orderNo) { OrderInfo orderInfo = orderInfoService.getOrderInfoByOrderNo(orderNo); return Result.ok(orderInfo); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public OrderInfo getOrderInfoByOrderNo (String orderNo) { LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper <>(); queryWrapper.eq(OrderInfo::getOrderNo, orderNo); OrderInfo orderInfo = orderInfoMapper.selectOne(queryWrapper); Long orderId = orderInfo.getId(); LambdaQueryWrapper<OrderDetail> wrapper = new LambdaQueryWrapper <>(); wrapper.eq(OrderDetail::getOrderId, orderId); List<OrderDetail> orderDetails = orderDetailMapper.selectList(wrapper); orderInfo.setOrderDetailList(orderDetails); orderInfo.setPayWayName(this .getPayWayName(orderInfo.getPayWay())); return orderInfo; }
查询当前用户所有订单 1 2 3 4 5 6 7 8 9 10 11 12 13 @GuiguLogin @Operation(summary = "获取用户订单") @GetMapping("findUserPage/{page}/{limit}") public Result index (@PathVariable Long page, @PathVariable Long limit) { Long userId = AuthContextHolder.getUserId(); Page<OrderInfo> pageParam = new Page <>(page, limit); IPage<OrderInfo> pageModel = orderInfoService .findUserPage(pageParam, userId); return Result.ok(pageModel); }
1 2 3 4 5 6 7 8 9 10 11 @Override public IPage<OrderInfo> findUserPage (Page<OrderInfo> pageParam, Long userId) { IPage<OrderInfo> infoIPage = orderInfoMapper.selectUserPage(pageParam,userId); infoIPage.getRecords().forEach(item->{ item.setOrderStatusName(getOrderStatusName(item.getOrderStatus())); item.setPayWayName(getPayWayName(item.getPayWay())); }); return infoIPage; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.atguigu.tingshu.order.mapper.OrderInfoMapper" > <resultMap id ="orderInfoMap" type ="com.atguigu.tingshu.model.order.OrderInfo" autoMapping ="true" > <id property ="id" column ="id" > </id > <collection property ="orderDetailList" ofType ="com.atguigu.tingshu.model.order.OrderDetail" autoMapping ="true" > <id property ="id" column ="detail_id" > </id > </collection > </resultMap > <sql id ="columns" > oi.id , oi.user_id, oi.order_title, oi.order_no, oi.order_status, oi.original_amount, oi.derate_amount, oi.order_amount, oi.item_type, oi.pay_way, od.id detail_id, od.item_id, od.item_name, od.item_url, od.item_price </sql > <select id ="selectUserPage" resultMap ="orderInfoMap" > select <include refid ="columns" > </include > from order_info oi inner join order_detail od on oi.id = od.order_id where oi.user_id = #{userId} order by oi.id desc </select > </mapper >
4、微信支付 4.1 发起微信支付请求接口
购买vip 或者 购买专辑,可以使用微信支付。但是购买声音只能余额!
点击微信执行,首先创建未支付订单,之后调用接口发起微信支付请求
发起微信支付请求接口传递两个参数:订单编号 和 支付种类(购买或者充值)
1301 购买种类
WxPayApiController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @Tag(name = "微信支付接口") @RestController @RequestMapping("api/payment/wxPay") @Slf4j public class WxPayApiController { @Autowired private WxPayService wxPayService; @GuiguLogin @Operation(summary = "微信下单") @PostMapping("/createJsapi/{paymentType}/{orderNo}") public Result createJsapi (@PathVariable String paymentType, @PathVariable String orderNo) { Long userId = AuthContextHolder.getUserId(); Map map = wxPayService.createJsapi(paymentType,orderNo,userId); return Result.ok(map); } }
Service 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 @Service @Slf4j public class WxPayServiceImpl implements WxPayService { @Autowired private PaymentInfoService paymentInfoService; @Autowired private OrderInfoFeignClient orderInfoFeignClient; @Autowired private RSAAutoCertificateConfig rsaAutoCertificateConfig; @Autowired private WxPayV3Config wxPayV3Config; @Autowired private UserInfoFeignClient userInfoFeignClient; @Override public Map createJsapi (String paymentType, String orderNo, Long userId) { if (!paymentType.equals(SystemConstant.PAYMENT_TYPE_RECHARGE)) { Result<OrderInfo> orderInfoResult = orderInfoFeignClient.getOrderInfo(orderNo); OrderInfo orderInfo = orderInfoResult.getData(); Assert.notNull(orderInfo,"订单为空" ); String orderStatus = orderInfo.getOrderStatus(); if (SystemConstant.ORDER_STATUS_CANCEL.equals(orderStatus)){ return null ; } } PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(paymentType, orderNo); JsapiServiceExtension service = new JsapiServiceExtension .Builder() .config(rsaAutoCertificateConfig).build(); PrepayRequest request = new PrepayRequest (); Amount amount = new Amount (); amount.setTotal(1 ); request.setAmount(amount); request.setAppid(wxPayV3Config.getAppid()); request.setMchid(wxPayV3Config.getMerchantId()); request.setDescription(paymentInfo.getContent()); request.setNotifyUrl(wxPayV3Config.getNotifyUrl()); request.setOutTradeNo(paymentInfo.getOrderNo()); Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(paymentInfo.getUserId()); Assert.notNull(userInfoVoResult,"返回用户结果集对象不能为空" ); UserInfoVo userInfoVo = userInfoVoResult.getData(); Assert.notNull(userInfoVo,"用户对象不能为空" ); String openid = userInfoVo.getWxOpenId(); Payer payer = new Payer (); payer.setOpenid(openid); request.setPayer(payer); PrepayWithRequestPaymentResponse response = service.prepayWithRequestPayment(request); Map<String, Object> result = new HashMap (); result.put("timeStamp" , response.getTimeStamp()); result.put("nonceStr" , response.getNonceStr()); result.put("package" , response.getPackageVal()); result.put("signType" , response.getSignType()); result.put("paySign" , response.getPaySign()); return result; } }
保存支付记录方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @Service @SuppressWarnings({"all"}) public class PaymentInfoServiceImpl extends ServiceImpl <PaymentInfoMapper, PaymentInfo> implements PaymentInfoService { @Autowired private OrderInfoFeignClient orderInfoFeignClient; @Autowired private RechargeInfoFeignClient rechargeInfoFeignClient; @Autowired private PaymentInfoMapper paymentInfoMapper; @Override public PaymentInfo savePaymentInfo (String paymentType, String orderNo) { LambdaQueryWrapper<PaymentInfo> wrapper = new LambdaQueryWrapper <>(); wrapper.eq(PaymentInfo::getOrderNo,orderNo); PaymentInfo paymentInfo = paymentInfoMapper.selectOne(wrapper); if (paymentInfo == null ) { paymentInfo = new PaymentInfo (); if (paymentType.equals(SystemConstant.PAYMENT_TYPE_ORDER)) { Result<OrderInfo> orderInfoResult = orderInfoFeignClient.getOrderInfo(orderNo); Assert.notNull(orderInfoResult,"返回订单对象不能为空" ); OrderInfo orderInfo = orderInfoResult.getData(); Assert.notNull(orderInfo,"返回订单对象不能为空" ); paymentInfo.setAmount(orderInfo.getOrderAmount()); paymentInfo.setUserId(orderInfo.getUserId()); paymentInfo.setContent(orderInfo.getOrderTitle()); } else { } paymentInfo.setPaymentType(paymentType); paymentInfo.setOrderNo(orderNo); paymentInfo.setPaymentStatus(SystemConstant.PAYMENT_STATUS_UNPAID); paymentInfoMapper.insert(paymentInfo); } return paymentInfo; } }
4.2 查询微信支付状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Operation(summary = "支付状态查询") @GetMapping("/queryPayStatus/{orderNo}") public Result queryPayStatus (@PathVariable String orderNo) { Transaction transaction = wxPayService.queryPayStatus(orderNo); if (transaction != null && transaction.getTradeState()==Transaction.TradeStateEnum.SUCCESS) { paymentInfoService.updatePaymentStatus(transaction); return Result.ok(true ); } return Result.ok(false ); }
1 2 3 4 5 6 7 8 9 10 11 12 @Override public Transaction queryPayStatus (String orderNo) { JsapiServiceExtension service = new JsapiServiceExtension .Builder() .config(rsaAutoCertificateConfig).build(); QueryOrderByOutTradeNoRequest queryRequest = new QueryOrderByOutTradeNoRequest (); queryRequest.setMchid(wxPayV3Config.getMerchantId()); queryRequest.setOutTradeNo(orderNo); Transaction transaction = service.queryOrderByOutTradeNo(queryRequest); return transaction; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Override public void updatePaymentStatus (Transaction transaction) { String orderNo = transaction.getOutTradeNo(); PaymentInfo paymentInfo = this .getOne(new LambdaQueryWrapper <PaymentInfo>() .eq(PaymentInfo::getOrderNo, orderNo)); if ("1402" .equals(paymentInfo.getPaymentStatus())) { return ; } paymentInfo.setPaymentStatus("1402" ); paymentInfo.setOrderNo(transaction.getOutTradeNo()); paymentInfo.setOutTradeNo(transaction.getTransactionId()); paymentInfo.setCallbackTime(new Date ()); paymentInfo.setCallbackContent(JSON.toJSONString(transaction)); this .updateById(paymentInfo); String routing = paymentInfo.getPaymentType().equals(SystemConstant.PAYMENT_TYPE_ORDER) ? MqConst.ROUTING_ORDER_PAY_SUCCESS : MqConst.ROUTING_RECHARGE_PAY_SUCCESS; rabbitService.sendMessage(MqConst.EXCHANGE_ORDER, routing, paymentInfo.getOrderNo()); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Component public class OrderReceiver { @Autowired private OrderInfoService orderInfoService; @SneakyThrows @RabbitListener(bindings = @QueueBinding( exchange = @Exchange(value = MqConst.EXCHANGE_ORDER, durable = "true"), value = @Queue(value = MqConst.QUEUE_ORDER_PAY_SUCCESS, durable = "true"), key = {MqConst.ROUTING_ORDER_PAY_SUCCESS} )) public void orderPaySuccess (String orderNo, Message message, Channel channel) { if (StringUtils.hasText(orderNo)) { orderInfoService.updateOrderSuccess(orderNo); } channel.basicAck(message.getMessageProperties().getDeliveryTag(), false ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 @Override public void updateOrderSuccess (String orderNo) { OrderInfo orderInfo = this .getOrderInfoByOrderNo(orderNo); if (orderInfo.getOrderStatus().equals("0901" )) { orderInfo.setOrderStatus("0902" ); orderInfoMapper.updateById(orderInfo); } UserPaidRecordVo userPaidRecordVo = new UserPaidRecordVo (); userPaidRecordVo.setOrderNo(orderNo); userPaidRecordVo.setUserId(orderInfo.getUserId()); userPaidRecordVo.setItemType(orderInfo.getItemType()); List<Long> itemIdList = orderInfo.getOrderDetailList().stream().map(OrderDetail::getItemId).collect(Collectors.toList()); userPaidRecordVo.setItemIdList(itemIdList); userInfoFeignClient.savePaidRecord(userPaidRecordVo); }
4.3 支付成功微信回调通知
发起微信支付请求之后,前端每隔5s调用一次查询支付状态接口,如果支付中,前端继续调用,如果支付成功前端不再调用
当微信支付成功之后,微信主动调用提供的接口,在这个接口获取微信传递过来成功信息,收到信息之后返回给微信收到标识。如果不给微信返回,微信继续通知,最多通知15次
微信回调我们通知成功,需要有公网ip或者域名才可以,如果没有公网ip可以使用内网穿透工具
5、测试微信支付方式 第一步 找到支付测试位置
第二步 使用微信支付方式生成订单,不支付 第三步 复制订单编号到支付测试里面
第四步 修改订单编号,重新复制过来
第五步 修改nacos微信相关值
1 2 3 4 5 6 7 8 9 10 11 12 @GuiguLogin @Operation(summary = "微信Native下单") @Parameters({ @Parameter(name = "paymentType",description = "支付类型:1301-订单 1302-充值",in = ParameterIn.PATH,required = true), @Parameter(name = "orderNo",description = "订单号",required = true,in = ParameterIn.PATH), }) @PostMapping("/createNative/{paymentType}/{orderNo}") public Result<Map<String, Object>> createNative (@PathVariable String paymentType, @PathVariable String orderNo) { Map<String, Object> map = wxPayService.createNative(paymentType, orderNo, AuthContextHolder.getUserId()); return Result.ok(map); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Override public Map<String, Object>createNative (String paymentType, String orderNo, Long userId) { try { PaymentInfo paymentInfo = paymentInfoService.savePaymentInfo(paymentType, orderNo); NativePayService service = new NativePayService .Builder().config(rsaAutoCertificateConfig).build(); com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest request = new com .wechat.pay.java.service.payments.nativepay.model.PrepayRequest(); com.wechat.pay.java.service.payments.nativepay.model.Amount amount = new com .wechat.pay.java.service.payments.nativepay.model.Amount(); amount.setTotal(1 ); request.setAmount(amount); request.setAppid(wxPayV3Config.getAppid()); request.setMchid(wxPayV3Config.getMerchantId()); request.setDescription(paymentInfo.getContent()); request.setNotifyUrl(wxPayV3Config.getNotifyUrl()); request.setOutTradeNo(paymentInfo.getOrderNo()); com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse response = service.prepay(request); System.out.println(response.getCodeUrl()); Map result = new HashMap <>(); result.put("codeUrl" , response.getCodeUrl()); return result; } catch (Exception e) { e.printStackTrace(); } return null ; }