商城积分系统的代码实现(下)-- 积分订单的退款与结算

一、接着上文

用户在消耗积分的时候,需要根据一定的逻辑,除了扣减账户的当前余额,还需要依次消费积分订单的余额。

private void updatePointsOrderByUse(Integer schoolId, Long userId, String pointsType, int usingPoints) {
        List<PointsOrder> pointsOrders = pointsOrderService.listAvailablePointsOrder(schoolId, userId, pointsType);

        if (CollectionUtils.isNotEmpty(pointsOrders)) {
            for (int i = 0; i < pointsOrders.size() && usingPoints > 0; i++) {
                PointsOrder pointsOrder = pointsOrders.get(i);

                int thisUsedPoints = pointsOrder.getAvailablePoints() >= usingPoints ? usingPoints : pointsOrder.getAvailablePoints();

                boolean updateSuccess = this.optimisticUpdateOrder(USE_POINTS_ORDER, pointsOrder.getId(),
                        pointsOrder.getUsedPoints(), pointsOrder.getAvailablePoints(), 0,
                        0, pointsOrder.getAvailableSettlePoints(), thisUsedPoints,
                        pointsOrder.getVersion());

                if (!updateSuccess) {
                    if (log.isWarnEnabled()) {
                        log.warn("积分订单处理使用事件出错, [orderNo={}, points={}]", pointsOrder.getOrderNo(), usingPoints);
                    }

                    Precondition.isTrue(false, "积分订单[%s]处理使用事件处理出错", pointsOrder.getOrderNo());
                }

                usingPoints -= thisUsedPoints;
            }
        }
    }

查询其所有的积分订单(可用余额大于0),按创建时间升序排列,也就是说,优先扣减最早的积分订单,直至全部扣减。

方法optimisticUpdateOrder()是一个乐观锁更新积分订单,和上文的方法optimisticUpdateAccount()实现类似,这里就不重复赘述了。

二、积分订单的更新逻辑

它有三个更新途径:

    // 消耗/使用积分
    private static final int USE_POINTS_ORDER = 1;
    // 积分订单的退款
    private static final int REFUND_POINTS_ORDER = 2;
    // 积分订单的结算
    private static final int SETTLE_POINTS_ORDER = 3;

在这里插入图片描述

1、消耗积分

更新订单的可用积分数、已使用积分数、可结算积分数

    @Modifying
    @Query(value = "update PointsOrder set availablePoints = :availablePoints, usedPoints = :usedPoints, " +
            " availableSettlePoints = :availableSettlePoints, version = version + 1, modifiedDate = now() " +
            " where id = :id and version = :oldVersion ")
    int modifyPointsOrderByUse(@Param("id") long id,
                               @Param("availablePoints") int availablePoints,
                               @Param("usedPoints") int usedPoints,
                               @Param("availableSettlePoints") int availableSettlePoints,
                               @Param("oldVersion") long oldVersion);

2、积分订单的退款

更新积分订单的已退款积分数、可用积分数

@Modifying
    @Query(value = "update PointsOrder set refundedPoints = :refundedPoints, " +
            " availablePoints = :availablePoints, version = version + 1, modifiedDate = now() " +
            " where id = :id and version = :oldVersion ")
    int modifyPointsOrderByRefund(@Param("id") long id,
                                  @Param("refundedPoints") int refundedPoints,
                                  @Param("availablePoints") int availablePoints,
                                  @Param("oldVersion") long oldVersion);

3、积分订单的结算

更新积分订单的可结算积分数、已结算积分数

    @Modifying
    @Query(value = "update PointsOrder set settledPoints = :settledPoints, " +
            " availableSettlePoints = :availableSettlePoints, version = version + 1, modifiedDate = now() " +
            " where id = :id and version = :oldVersion ")
    int modifyPointsOrderBySettle(@Param("id") long id,
                                  @Param("settledPoints") int settledPoints,
                                  @Param("availableSettlePoints") int availableSettlePoints,
                                  @Param("oldVersion") long oldVersion);

三、积分订单的退款

积分订单的结算操作,不涉及积分账户和账户收支。用户消耗积分,更新积分订单,本文的开头就已详细交待(它是和上一篇紧密相关的)

积分订单的退款则不一样,它会涉及到积分账户和账户的收支。

为了减少复杂度,我们规定积分订单只能退款一次。

既然是只能退款一次,默认就是全额退款,传入积分订单的订单号,调用本接口。

@Lock(name = POINTS_DISTRIBUTE_LOCK_PRE, key = "'refund:orderNo:' + #orderNo")
    @Transactional(rollbackFor = Throwable.class, isolation = Isolation.READ_COMMITTED)
    public void dealRefund(String orderNo) {
        PointsOrder pointsOrder = pointsOrderService.findPointsOrder(orderNo);
        Precondition.notNull(pointsOrder, "订单[%s]不存在", orderNo);

        // 本次退款的积分
        int thisPoints = pointsOrder.getAvailablePoints();
        if (thisPoints == 0) {
            if (log.isInfoEnabled()) {
                log.info("订单退款处理出现警告,可用积分数为0.[orderNo={}]", orderNo);
            }
            return;
        }

        // 1.更新账户的余额
        this.updateAccount(REFUND_POINTS_ACCOUNT, pointsOrder.getSchoolId(),
                pointsOrder.getUserId(), pointsOrder.getPointsType(), thisPoints);

        // 2.更新积分订单表
        boolean updateOrderSuccess = this.optimisticUpdateOrder(REFUND_POINTS_ORDER, pointsOrder.getId(),
                0, pointsOrder.getAvailablePoints(), pointsOrder.getRefundedPoints(),
                0, 0, thisPoints,
                pointsOrder.getVersion());

        if (!updateOrderSuccess) {
            if (log.isWarnEnabled()) {
                log.warn("订单退款出现错误, [orderNo={}, points={}]", orderNo, thisPoints);
            }

            Precondition.isTrue(false, "订单[%s]退款出现错误", orderNo);
        }

        //3.保存账户变更记录
        pointsAccountFlowService.savePointsAccountFlow(FlowTypeEnum.DECREASE,
                pointsOrder.getSchoolId(),
                pointsOrder.getUserId(),
                pointsOrder.getPointsType(), thisPoints,
                PointsChannelEnum.REFUND_ORDER.getCode(), PointsChannelEnum.REFUND_ORDER.getName(),
                orderNo, null,
                "订单号[" + orderNo + "]退款");

      //4.发布异步事件,通知用户其账户有变更
    }

四、总结

至此,关于商城的积分系统,其详细实现就介绍完了。

希望通过整个系列的五篇文章,帮助你搭建一套灵活多变的积分系统,服务于整个公司的所有业务。

  • 15
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天草二十六_简村人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值