库存扣减在提交订单时,还是支付之后?
库存扣减与买票类似,这里通过买票环境阐述:
在提交订单时扣减组数,需要考虑的因素:
1.用户下单,但取消支付,前端需要捕获到取消支付的动作,并调用后端接口将对应组数相加
2.用户下单,进入输入支付密码时,出现网络故障,或手机关机等因素导致进程杀死,前端无法向后端调接口,导致该场次组数发生异常
3.用户下单,但未支付,这时后台管理操作人员更改了组数,需要自动计算剩余组数,在计算剩余组数时,可能将用户下单减一的组数吞掉了,再次加一时,可能就多了一组
优点:场次信息是实时的,没有了就是没有了,不会出现剩余一组,两人还能同时支付的情况
缺点:上述因素的第二条无法避免,需要考虑取消支付增加组数的情况,以及第三条比较特殊的情况,很容易出现剩余组数错误
在支付后扣减组数,需要考虑的因素:
1.并发问题,剩余一组后,用户a进入下单支付界面,此时剩余还是一组,在a未支付的同时用户b也能进入下单,最终会导致a,b都支付成功
2.处理退款时需要实时查询该场次下订单已支付数量,更新剩余场次
3.从用户角度考虑,需要通知用户退款的原因为场次数不足,否则用户直接懵了
优点: 支付之后扣减就不用考虑用户取消支付,或网络故障导致前端无法调接口问题
缺点:库存数不实时,a和b都能买,但只剩一组,这时需要将为竞争到锁的用户进行提示,并自动退款,对用户体验不好,但能保证组数不出现错误数据
支付后扣减组数实现
微信支付成功后回调接口
// 支付成功处理
if (WxPayConstants.WxpayTradeStatus.SUCCESS.equals(result.getTradeState())) { //返回的result为成功
// 处理时间
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
String formattedDate = sdf.format(date);
appOrderMapper.updateOrderPayById(OrderStatusEnum.PAID.getValue(), result.getTransactionId(), formattedDate, order.getId());
if (sessionService.deductInventory(order.getSessionId())) {
log.info("剩余组数扣减成功");
} else {
//订单改为异常状态,客户取消支付,直接退款
this.refundOrder(new RefundForm().setOrderCode(order.getOrderCode())
.setRefundAmount(order.getAmount())
.setReason("场次剩余不足,自动退款"));
}
}
扣减库存
// 扣减库存操作
public boolean deductInventory(Long sessionId) {
lock.lock();
try {
Session session = this.getOne(new LambdaQueryWrapper<Session>().eq(Session::getId,sessionId));
Long inventory = session.getRestAmount();
if (inventory > 0) {
appOrderMapper.updateSessionById(inventory-1,sessionId);
return true;
} else {
return false;
}
} finally {
lock.unlock();
}
}
退款成功回调接口
public void handleWxPayRefundNotify(SignatureHeader signatureHeader, String notifyData) throws WxPayException {
log.info("开始处理退款结果通知");
log.info("notifyData----"+notifyData);
// 解密支付通知内容
final WxPayRefundNotifyV3Result.DecryptNotifyResult result = this.wxPayService.parseRefundNotifyV3Result(notifyData, signatureHeader).getResult();
log.info("退款通知解密成功:[{}]", result.toString());
// 根据商户退款单号查询订单
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(Order::getOrderCode, result.getOutTradeNo());
Order order = orderService.getOne(wrapper);
// 退款成功处理
if (WxPayConstants.RefundStatus.SUCCESS.equals(result.getRefundStatus())) {
ProcessOrderStatus(result, order); //处理订单状态
//更新剩余组数(总组数-剩余组数)
Session session = sessionService.findById(order.getSessionId());
Long lessSession = orderService.count(new QueryWrapper<Order>()
.eq("SESSION_ID",order.getSessionId())
.apply("STATE in ({0},{1})",OrderStatusEnum.PAID.getValue(),OrderStatusEnum.COMPLETE.getValue())); //剩余数量
Long restAmount = session.getAmount() - lessSession;
appOrderMapper.updateSessionById(restAmount, order.getSessionId());
}
log.info("场次信息更新成功");
log.info("账单更新成功");
}
实现效果:
总结:
在提交订单时扣减组数,我们并不能捕获到用户是否关闭应用或取消支付时断网,这种情况发生概率较大,在该业务中,使用这种方法风险较大,而且不好解决这种情况,而在支付后扣减组数,我们需要考虑的是并发的问题,以及对使用者的角度而言的感受,两者都有各自的优点和缺点,实际的开发中,也需要根据自己的业务需求去权衡利弊,而非哪一种一定就是最好的