项目
博客
文档
归档
资源链接
关于我
项目
博客
文档
归档
资源链接
关于我
39| 订单微服务下单链路完善和支付整合测试
2024-09-18
·
·
原创
·
·
本文共 1,171个字,预计阅读需要 4分钟。
### 下单支付链路和超时未支付定时关单功能开发完善 * 下单支付功能开发confirmOrder ```java //创建支付 PayInfoVO payInfoVO = new PayInfoVO(orderOutTradeNo, productOrder.getPayAmount(),request.getPayType(), request.getClientType(), orderItemList.get(0).getProductTitle(),"", TimeConstant.ORDER_PAY_TIMEOUT_MILLS); String payResult = payFactory.pay(payInfoVO); if(StringUtils.isNotBlank(payResult)){ log.info("创建支付订单成功:payInfoVO={},payResult={}",payInfoVO,payResult); return JsonData.buildSuccess(payResult); }else { log.error("创建支付订单失败:payInfoVO={},payResult={}",payInfoVO,payResult); return JsonData.buildResult(BizCodeEnum.PAY_ORDER_FAIL); } ``` * 消费者功能完善closeProductOrder ```java //向第三方支付查询订单是否真的未支付 todo PayInfoVO payInfoVO = new PayInfoVO(); payInfoVO.setPayType(productOrderDO.getPayType()); payInfoVO.setOutTradeNo(orderMessage.getOutTradeNo()); String payResult = payFactory.queryPaySuccess(payInfoVO); ``` * 查询订单状态 ### 订单微服务下单支付全链路多场景测试准备工作 * 下单支付全链路测试-支付-超时未支付 - 设置:支付订单的有效时长:5min,关单mq:5min - 商品库存与优惠券记录的延迟消息:6min * 登录-加入购物车-使用优惠券-下单-支付 * 登录-加入购物车-使用优惠券-下单-不支付 * 测试准备工作 * 修改多个微服务的死信队列 * 订单5分钟内支付未支付则关单 * 延迟消息6分钟 * **修改了延迟队列的属性,记得先删除下全部交换机和队列** * 检查优惠券记录和商品库存 * 注意: * bug修改:saveProductOrder方法 * 初次启动微服务记得先调用下,防止超时 ### 订单微服务下单支付全链路多场景测试 * 登录-加入购物车-使用优惠券-下单-支付 * 代码本身有问题-比如真的少了参数 * 代码bug修改下单协议:total_amount、timeout_express * 支付沙箱环境抽风(偶尔出现) * 下单支付全链路测试-支付-超时未支付(优惠券记录释放正常、商品库存释放正常、订单关闭正常) * 登录-加入购物车-使用优惠券-下单-不支付(数据正常) ```json // /odr/product/v1/confirm { "address_id": 40, //订单地址 "client_type": "H5", "coupon_record_id": 14, "pay_type": "ALIPAY", //支付宝支付 "product_ids": [ 1,2 ], "real_pay_amount": 505, "token": "text", "total_amount": 510 } ``` 在提交订单前,需要修改: 领取优惠券接口:`/add/promotion/{coupon_id}` 使用: 新人注册 : `couponService.addCoupon(couponId, CouponCategoryEnum.NEW_USER)` 同时下单前要添加购物车接口添加商品:`/pdt/cart/v1/add`,调用两次:商品1/2每个2件 ```json { "buy_num": 2, "product_id":1 } { "buy_num": 2, "product_id":2 } ``` 下单完成之后会返回地址:请求该地址使用沙箱账号去支付 ```javascript
\n
\n
\n
\n ``` ### 订单微服务订单列表和订单项功能开发 * 分页个人查询订单功能开发 ```java @ApiOperation("分页查询我的订单列表") @GetMapping("page") public JsonData pagePOrderList( @ApiParam(value = "当前页") @RequestParam(value = "page", defaultValue = "1") int page, @ApiParam(value = "每页显示多少条") @RequestParam(value = "size", defaultValue = "10") int size, @ApiParam(value = "订单状态") @RequestParam(value = "state", required = false) String state) { return JsonData.buildSuccess(orderService.page(page, size, state)); } ``` 业务查询 ```java @Override public PageResult
page(int page, int size, String state) { LoginUser loginUser = LoginInterceptor.threadLocal.get(); Page
pageInfo = new Page<>(page, size); IPage
orderDOPage = null; if (StringUtils.isBlank(state)) { orderDOPage = baseMapper.selectPage(pageInfo, new QueryWrapper
().eq("user_id", loginUser.getId())); } else { orderDOPage = baseMapper.selectPage(pageInfo, new QueryWrapper
().eq("user_id", loginUser.getId()) .eq("state", state)); } //获取订单列表 List
productOrderDOList = orderDOPage.getRecords(); List
productOrderVOList = productOrderDOList.stream().map(orderDO -> { List
itemDOList = productOrderItemMapper.selectList( new QueryWrapper
().eq("product_order_id", orderDO.getId())); List
itemVOList = itemDOList.stream().map(item -> { OrderItemVO itemVO = new OrderItemVO(); BeanUtils.copyProperties(item, itemVO); return itemVO; }).collect(Collectors.toList()); ProductOrderVO productOrderVO = new ProductOrderVO(); BeanUtils.copyProperties(orderDO, productOrderVO); productOrderVO.setOrderItemList(itemVOList); return productOrderVO; }).collect(Collectors.toList()); return new PageResult<>(orderDOPage.getTotal(), orderDOPage.getPages(),productOrderVOList); } ``` ### 未支付订单二次支付业务逻辑设计和编码实战 * controller开发 ```java @ApiOperation("重新支付订单") @PostMapping("repay") public void repay(@ApiParam("订单对象") @RequestBody RepayOrderRequest repayOrderRequest, HttpServletResponse response){ JsonData jsonData = orderService.repay(repayOrderRequest); if(jsonData.getCode() == 0){ String client = repayOrderRequest.getClientType(); String payType = repayOrderRequest.getPayType(); //如果是支付宝网页支付,都是跳转网页,APP除外 if(payType.equalsIgnoreCase(ProductOrderPayTypeEnum.ALIPAY.name())){ log.info("重新支付订单成功:{}",repayOrderRequest.toString()); if(client.equalsIgnoreCase(ClientType.H5.name())){ writeData(response,jsonData); }else if(client.equalsIgnoreCase(ClientType.APP.name())){ //APP SDK支付 TODO } } else if(payType.equalsIgnoreCase(ProductOrderPayTypeEnum.WECHAT.name())){ //微信支付 TODO } } else { log.error("重新支付订单失败{}",jsonData.toString()); CommonUtil.sendJsonMessage(response,jsonData); } } ``` * service开发 ```java @Override public JsonData repay(RepayOrderRequest repayOrderRequest) { LoginUser loginUser = LoginInterceptor.threadLocal.get(); ProductOrderEntity productOrderDO = baseMapper.selectOne(new QueryWrapper
().eq("out_trade_no", repayOrderRequest.getOutTradeNo()).eq("user_id",loginUser.getId())); log.info("订单状态:{}",productOrderDO); if(productOrderDO==null){ return JsonData.buildResult(BizCodeEnum.PAY_ORDER_NOT_EXIST); } //订单状态不对,不是NEW状态 if(!productOrderDO.getState().equalsIgnoreCase(ProductOrderStateEnum.NEW.name())){ return JsonData.buildResult(BizCodeEnum.PAY_ORDER_STATE_ERROR); }else { //订单创建到现在的存活时间 long orderLiveTime = CommonUtil.getCurrentTimestamp() - productOrderDO.getCreateTime().getTime(); //创建订单是临界点,所以再增加1分钟多几秒,假如29分,则也不能支付了 orderLiveTime = orderLiveTime + 70*1000; //大于订单超时时间,则失效 if(orderLiveTime>TimeConstant.ORDER_PAY_TIMEOUT_MILLS){ return JsonData.buildResult(BizCodeEnum.PAY_ORDER_PAY_TIMEOUT); }else { //记得更新DB订单支付参数 payType,还可以增加订单支付信息日志 TODO //总时间-存活的时间 = 剩下的有效时间 long timeout = TimeConstant.ORDER_PAY_TIMEOUT_MILLS - orderLiveTime; //创建支付 PayInfoVO payInfoVO = new PayInfoVO(productOrderDO.getOutTradeNo(), productOrderDO.getPayAmount(),repayOrderRequest.getPayType(), repayOrderRequest.getClientType(), productOrderDO.getOutTradeNo(),"",timeout); log.info("payInfoVO={}",payInfoVO); String payResult = payFactory.pay(payInfoVO); if(StringUtils.isNotBlank(payResult)){ log.info("创建二次支付订单成功:payInfoVO={},payResult={}",payInfoVO,payResult); return JsonData.buildSuccess(payResult); }else { log.error("创建二次支付订单失败:payInfoVO={},payResult={}",payInfoVO,payResult); return JsonData.buildResult(BizCodeEnum.PAY_ORDER_FAIL); } } } } ``` ### 未支付订单二次支付全链路测试 * 全链路测试 * 加入购物车 * 下单不支付 * 我的订单列表 * 二次支付 * 备注 * 测试的时候可以快速下两笔订单,3分钟内可以支付,3分钟后就不行 * 订单支付超时,可以往前推,也可以往后推1分钟 ### 订单微服务-避免重复下单token令牌机制+lua脚本原子操作 * 问题 * 前端下单按钮重复点击导致订单创建多次 * 前端有限制,后端也需要有限制 * 任何提交表单的时候,都可以采用token令牌机制避免重复点击 * token令牌机制开发 * 下单前先获取令牌-存储redis * 下单时一并把token提交并检验和删除-lua脚本 ```shell String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; ``` 获取订单token ```java @Autowired private StringRedisTemplate redisTemplate; @ApiOperation("获取提交订单令牌") @GetMapping("get_token") public JsonData getOrderToken(){ LoginUser loginUser = LoginInterceptor.threadLocal.get(); String key = String.format(CacheKey.SUBMIT_ORDER_TOKEN_KEY,loginUser.getId()); String token = CommonUtil.getStringNumRandom(32); redisTemplate.opsForValue().set(key,token,30, TimeUnit.MINUTES); return JsonData.buildSuccess(token); } ``` 在下单业务中惊醒原子操作 ```java //原子操作 校验令牌,删除令牌 String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; Long result = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), List.of(String.format(CacheKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getId())), orderToken); if (result == 0L) { throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL); } ```