3订单设计-支付.md 5.5 KB

我们的支付是不允许在订单的支付接口传订单金额的,所以我们采用了订单号进行支付的形式

支付

我们来到PayController ,这里就是统一支付的接口。

我们直接看一下核心代码:

PayInfoDto payInfo = payInfoService.pay(userId, payParam);

再看看里面的代码:

PayInfo payInfo = new PayInfo();
payInfo.setPayNo(payNo);
payInfo.setUserId(userId);
payInfo.setPayAmount(orderAmount.getPayAmount());
payInfo.setPayScore(orderAmount.getPayScore());
payInfo.setPayStatus(PayStatus.UNPAY.value());
payInfo.setPayType(payParam.getPayType());
payInfo.setVersion(0);
// 保存多个支付订单号
payInfo.setOrderNumbers(payParam.getOrderNumbers());
payInfo.setPayEntry(PayEntry.ORDER.value());
payInfo.setCreateTime(new Date());
// 保存预支付信息
payInfoMapper.insert(payInfo);

支付的时候将所有的订单号都存入payInfo中,在支付回调中再通过分单的方式更新订单结算表。

订单金额:

OrderAmountVO orderAmount = orderService.getOrdersAmountAndIfNoCancel(Arrays.asList(orderNumbers), payParam.getPayType());

这里面应支付的金额是通过数据库中获取的订单金额,是不接受任何前端传入的订单金额的。

orderMapper.getOrdersActualAmount(orderNumbers)

支付回调

我们回到PayController

payInfo.setApiNoticeUrl("/notice/pay/" + paySettlementConfig.getPaySettlementType() + "/" + PayEntry.ORDER.value() + "/" + payParam.getPayType());

并且payManager.doPay方法中有一段

String domainName = shopConfig.getDomain().getApiDomainName();
String notifyUrl = domainName + payInfo.getApiNoticeUrl();

这里面规定的,订单回调的地址,这也就是为什么需要在平台端的后台配置的基础配置中设置api接口域名的原因

其中不管是订单回调,还是余额充值或是购买会员,均回调至PayNoticeController

  • 验签

因为订单的已经决定的订单已经支付成功,所以订单的回调是需要做一些验证的。不然谁都可以调用订单回调的地址,实在是十分危险。

其实PayNoticeController这里对订单的签名进行了校验

payInfoResultBO = payManager.validateAndGetPayInfo(paySysType, request, PayType.instance(payType), data);
  • 更新支付状态

我们看看payInfoService.noticeOrder这里的业务核心方法:

// 真正的支付成功
orders = paySuccess(payInfoResultBO, orderNumberList, payInfo);
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<Order> paySuccess(PayInfoResultBO payInfoResultBO, List<String> orderNumbers, PayInfo payInfo) {
        String payNo = payInfoResultBO.getPayNo();
        String bizPayNo = payInfoResultBO.getBizPayNo();
        // 标记payinfo为支付成功
        updatePayInfo(payInfoResultBO, payNo, bizPayNo);

        List<Order> orders = orderNumbers.stream().map(orderNumber -> {
            Order order = orderService.getOrderAndOrderItemByOrderNumber(orderNumber);
            allOrderItem.addAll(order.getOrderItems());
            return order;
        }).collect(Collectors.toList());
        
        for (Order order : orders) {
            // 更新订单结算信息中的支付单号,在这里实现了分单支付
            OrderSettlement orderSettlement = orderSettlementMapper.selectOne(new LambdaQueryWrapper<OrderSettlement>().eq(OrderSettlement::getOrderNumber, order.getOrderNumber()).last("limit 1"));
            orderSettlement.setPayNo(payNo);
            orderSettlement.setPayType(payInfo.getPayType());
            orderSettlementMapper.updateById(orderSettlement);
            // 省略
        }

        OrderSettlement orderSettlement = orderSettlementMapper.selectOne(Wrappers.lambdaQuery(OrderSettlement.class).eq(OrderSettlement::getPayNo, payNo).last("limit 1"));
        
		// 简略版本... 
        // 修改订单结算信息
        if (orderSettlementMapper.updateToOrderNumbers(orderNumbers, bizPayNo, orderSettlement.getVersion()) < 1) {
            // 结算信息已更改
            throw new YamiShopBindException("yami.modified.settlement.information");
        }
        // 将订单改为已支付状态
        orderService.updateByToPaySuccess(orderNumbers, payInfo.getPayType());
        
       
        if (CollectionUtils.isEmpty(successOrderList)) {
            return orders;
        }
        // 处理下相关活动、分账、分销推送,库存的解锁和消息推送
        handlerStockAndMsg(payInfo.getPayNo(), orderNumbers, allOrderItem, successOrderList);
        return orders;
    }

handlerStockAndMsg方法中可以看到方法markerStockUse也用到了lua脚本OrderMakerUseStock.lua进行库存解锁,具体操作可以看脚本里面的备注内容。至于秒杀订单比较特殊这里不进行处理,可以看同目录下的秒杀订单设计文档。

private void handlerStockAndMsg(String payNo, List<String> orderNumbers, List<OrderItem> allOrderItem, List<Order> successOrderList, Integer paySysType) {
    ...
    // 非秒杀订单,共用的库存
    if (!Objects.equals(orderType, OrderType.SECKILL.value())) {
        skuStockLockService.markerStockUse(allOrderItem);
    }
    ...
}

这里无非就是找到原来的订单,将订单变成已支付的状态。

而这里同样有事件支付成功的事件,包括了一些套餐,优惠券,团购之类的订单处理

eventPublisher.publishEvent(new PaySuccessOrderEvent(successOrderList, allOrderItem, payNo));