公众号merlinsea
- 背景
- 用户第一次点击下单操作的时候,会弹出支付页面等待用户支付的页面。但可能存在支付异常的情况,比如用户发现金额不够关闭了支付页面,后续通过选择其他支付方式(比如由微信支付转为支付宝支付)或者 不同的端类型(比如第一次是用电脑端支付,后面选择app支付等等)来进行支付,这个时候就会出现二次支付的场景。
- 解决二次支付问题的思路1:
- 由于用户支付的时候的支付页面是个html文件或者是一个支付二维码的图片,可以选择将支付页面存在数据库中,用户二次支付时通过查询数据库的支付页面,将页面返回给用户进行再次使用。【特别需要注意的是这个支付页面有没有过期!!】
- 这种解决思路后台需要自己存储用户的支付页面或者二维码,增加了维护成本;如果支付信息超时了,那么依旧需要二次调用第三方支付。
- 解决二次支付问题的思路2:
- 用户第二次支付的时候继续调用第三方支付,让第三方根据是否超时等情况来判断是返回原来的支付页面还是生成一个新的支付页面返回。这种方案便于实现,减轻了自己后台下单的维护成本。
- 注意点:用户二次支付的时候,订单微服务中是存储了用户第一次下单支付的基本信息的。因此第二次支付的时候,可以通过查询第一次支付的一些基本信息来调用第三方支付,尤其是该订单的【剩余过期时间】。
- 剩余过期时间:后台调用第三方支付,第三方支付从收到请求信息->处理请求信息->响应请求信息是存在一定的时延的,因此一定不 能死死卡住过期时间来调用第三方支付。需要预留一些时间给第三方支付处理。比如支付过期时间是30分钟,当用户二次支付到达我们下单服务的时候是29分钟那么就拒绝支付。
- 订单超时支付的策略
- 策略一:前端显示订单30分钟内需要支付,后端中对第三方支付实际上是31分钟内不能支付 【预留时间给后端和第三方支付交互】
- 策略二:前端显示订单30分钟内需要支付,后端对第三方的支付实际上是当用户支付请求在地29分钟到后端就不给支付了
- 思路2的核心代码实现
@Override @Transactional public JsonData repay(RepayOrderRequest repayOrderRequest) { LoginUser loginUser = LoginInterceptor.threadLocal.get(); //根据订单流水号查询第一次支付的订单信息 ProductOrderDO productOrderDO = productOrderMapper.selectOne(new QueryWrapper<ProductOrderDO>().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(); //创建订单是临界点在预留一分钟时间,比如订单实际已经存活了28分钟了,我们就对外说订单已经存活了29分钟。 orderLiveTime = orderLiveTime + 60*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); } } } }