> 我们的支付是不允许在订单的支付接口传订单金额的,所以我们采用了订单号进行支付的形式 ## 支付 我们来到`PayController` ,这里就是统一支付的接口。 我们直接看一下核心代码: ```java PayInfoDto payInfo = payInfoService.pay(userId, payParam); ``` 再看看里面的代码: ```java 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中,在支付回调中再通过分单的方式更新订单结算表。 订单金额: ```java OrderAmountVO orderAmount = orderService.getOrdersAmountAndIfNoCancel(Arrays.asList(orderNumbers), payParam.getPayType()); ``` 这里面应支付的金额是通过数据库中获取的订单金额,是不接受任何前端传入的订单金额的。 ```java orderMapper.getOrdersActualAmount(orderNumbers) ``` ## 支付回调 我们回到`PayController` ```java payInfo.setApiNoticeUrl("/notice/pay/" + paySettlementConfig.getPaySettlementType() + "/" + PayEntry.ORDER.value() + "/" + payParam.getPayType()); ``` 并且`payManager.doPay`方法中有一段 ```java String domainName = shopConfig.getDomain().getApiDomainName(); String notifyUrl = domainName + payInfo.getApiNoticeUrl(); ``` 这里面规定的,订单回调的地址,这也就是为什么需要在平台端的后台配置的基础配置中设置api接口域名的原因 其中不管是订单回调,还是余额充值或是购买会员,均回调至`PayNoticeController` - 验签 因为订单的已经决定的订单已经支付成功,所以订单的回调是需要做一些验证的。不然谁都可以调用订单回调的地址,实在是十分危险。 其实`PayNoticeController`这里对订单的签名进行了校验 ```java payInfoResultBO = payManager.validateAndGetPayInfo(paySysType, request, PayType.instance(payType), data); ``` - 更新支付状态 我们看看`payInfoService.noticeOrder`这里的业务核心方法: ```java // 真正的支付成功 orders = paySuccess(payInfoResultBO, orderNumberList, payInfo); ``` ```java @Override @Transactional(rollbackFor = Exception.class) public List paySuccess(PayInfoResultBO payInfoResultBO, List orderNumbers, PayInfo payInfo) { String payNo = payInfoResultBO.getPayNo(); String bizPayNo = payInfoResultBO.getBizPayNo(); // 标记payinfo为支付成功 updatePayInfo(payInfoResultBO, payNo, bizPayNo); List 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().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 orderNumbers, List allOrderItem, List successOrderList, Integer paySysType) { ... // 非秒杀订单,共用的库存 if (!Objects.equals(orderType, OrderType.SECKILL.value())) { skuStockLockService.markerStockUse(allOrderItem); } ... } ``` 这里无非就是找到原来的订单,将订单变成已支付的状态。 而这里同样有事件支付成功的事件,包括了一些套餐,优惠券,团购之类的订单处理 ```java eventPublisher.publishEvent(new PaySuccessOrderEvent(successOrderList, allOrderItem, payNo)); ```