The introduction

Hello, this is Anyin.

In the previous article based on Vue3+TS+ElementPlus+Qiankun to build a micro-application project said that a ordering wechat small program, and this ordering will definitely involve the ordering process. The amount calculation of this ordering process will involve the following businesses:

  1. Calculate the amount payable without deducting any concessions
  2. Decide whether to use coupons or participate in discounts. Coupons and discounts are mutually exclusive
  3. Calculate to add purchase commodity, add purchase commodity price will be more favorable than the price set by the commodity, and there are purchase restriction conditions
  4. Calculate the integral deduction, the integral can deduct the amount of goods
  5. Determine whether the balance is paid. If the balance is paid, the balance amount should be larger than the current payment amount
  6. Check if it’s 0 yuan, 0 yuan can also be paid

The above is the basic process of order amount calculation of wechat mini program, of course, the order process has other calculations, such as: judging the inventory of goods, judging the state of stores, judging the status of coupons, and so on, here is just to the amount of calculation as an example; In addition, here is the sorted business logic, which might not be the order in which the business would be implemented in real development.

OK, let’s look at how this complex business can be implemented to improve the readability of the code.

implementation

Before implementing the business, let’s do a simple analysis.

  1. A list of items is entered at the front end
  2. We need to calculate the amount payable based on the item selected by the front end
  3. If there is a coupon or product participating in the discount, the discount amount or discount amount needs to be calculated: Amount payable = Amount payable – The amount
  4. To calculate the amount of goods purchased, amount payable = Amount payable + the amount
  5. Calculate the credit amount, the amount payable = the amount payable – the credit amount
  6. Determine whether the balance is paid. If the balance is paid and the balance is greater than the amount payable, the amount payable is reset to 0

Based on the above, we will design a behavior interface to calculate each business amount:

public interface ComputeAppOrderPayAmount {
    AppOrderPayAmountResp getAppOrderPayAmount();
}
Copy the code

The AppOrderPayAmountResp entity class is all the calculation information we want to return to the order logic. It will contain the following information:

@Data
public class AppOrderPayAmountResp {
    /** * actual order amount * amount payable minus discountAmount */
    private BigDecimal orderAmount;
    /** * Amount payable for order * Unit price * Quantity + Specification markup */
    private BigDecimal dueAmount;
    /** * Discount amount = coupon amount or discount amount or credit amount */
    private BigDecimal discountAmount = BigDecimal.ZERO;


    /** * Integral deduction - integral value *@sinceV1.2.5 * /
    private Long deductionScore = 0L;

    /** * Credits deducted - amount *@sinceV1.2.5 * /
    private BigDecimal deductionAmount = BigDecimal.ZERO;

    /** * Balance payment amount *@sinceV1.2.7.0 * /
    private BigDecimal balancePayAmount = BigDecimal.ZERO;

}
Copy the code

Calculate the amount payable for goods

For the payable amount of goods, the front end will transfer the ID and quantity of goods, and the amount needs to be obtained from the database and then calculated. The core logic is as follows:

    @Override
    public AppOrderPayAmountResp getAppOrderPayAmount(a) {
        // List of commodity ids passed by the front end
        List<Long> spuIdList = CollectionUtils.map(this.payAmountReq.getReq().getSpuList(), AppOrderPayAmountReq.Spu::getSpuId);
        // Query the commodity information in the database
        List<FoodSpu> spuList = spuService.listByIds(spuIdList);
        
        // Calculate the sum of goods according to the goods information, including the amount of feeding
        PayableAmountHandler handler = new PayableAmountHandler(this.payAmountReq.getReq().getSpuList(), spuList);
        BigDecimal amount = handler.handle().amount();
        // Set the corresponding information back
        AppOrderPayAmountResp resp = new AppOrderPayAmountResp();
        resp.setDueAmount(amount);
        resp.setPayType(this.payAmountReq.getReq().getPayType());
        return resp;
    }
Copy the code

Where PayableAmountHandler is the specific computational logic processor. The PayableAmountHandler#handle method returns an Amount interface, which is an abstraction of an Amount, which could be the Amount due, the discount, the credit deduction, and so on.

At this point, the main logic code can use the class we implemented to calculate the amount due as follows:

        ComputeAppOrderPayAmount dueOrderPayAmount = new DueOrderPayAmount(new PayAmountReq(contextReq.getReq()), spuService);
        AppOrderPayAmountResp resp = dueOrderPayAmount.getAppOrderPayAmount();
Copy the code

Calculate coupons or discounts

In terms of business, it is stipulated that the coupon and discount activities are mutually exclusive. If the product selected by the user participates in the discount activity, the coupon will not be counted. Therefore, we will calculate whether the order is a discount order before calculating the amount.

Then let’s look at the processing logic of the coupon order. In fact, the coupon order also distinguishes the situation in 2: there are coupons and there are no coupons. Let’s directly look at the code:

    @Override
    public AppOrderPayAmountResp getAppOrderPayAmount(a) {
        // Get a list of coupons available to the user
        List<AppCouponListItemResp> couponList = this.getUsableCouponList(this.payAmountReq.getAmount().amount(),
                this.payAmountReq.getCustomerId().getCustomerId(),
                this.payAmountReq.getSysUserId().getSysUserId(), this.payAmountReq.getReq());

        // Whether there are coupons to calculate differently
        Handler handler = CollectionUtils.isEmpty(couponList) ?
                new NotCouponHandler(this.payAmountReq.getAmount().amount()) :
                new CouponHandler(this.payAmountReq.getAmount().amount(), couponList, this.payAmountReq.getReq());

        return handler.handle();
    }
Copy the code

Both NotCouponHandler and CouponHandler implement the Handler interface. There are two different implementations of NotCouponHandler:

        @Override
        public AppOrderPayAmountResp handle(a) {
            // Initialize information
            AppOrderPayAmountResp resp = new AppOrderPayAmountResp();
            resp.setDueAmount(this.amount);
            resp.setCouponList(this.couponList);
            resp.setHasDiscountSpu(AppOrderPayAmountResp.HasDiscountSpuEnum.NOT_DISCOUNT.getVal());
            // Calculate the coupon amount
            FoodCouponCustomer couponCustomer = this.req.getCouponId() == null ? null : couponService.getFoodCouponCustomer(this.req.getCouponId());
            CouponAmountHandler couponAmountHandler = new CouponAmountHandler(this.couponList, couponCustomer);
            CouponAmountHandler.CouponAmount couponAmount = couponAmountHandler.handle();
            // Amount payable - Coupon Amount of coupon
            BigDecimal orderAmount = this.amount.subtract(couponAmount.amount());

            // 0 yuan can also be paid (the coupon amount may be greater than the order amount)
            if(orderAmount.compareTo(BigDecimal.ZERO) <= 0) {
                orderAmount = BigDecimal.ZERO;
            }
            resp.setOrderAmount(orderAmount);
            resp.setDiscountAmount(resp.getDueAmount().subtract(orderAmount));
            return resp;
        }
Copy the code

The above is the processing logic of coupon orders. However, the processing logic of participating in discount activities will be complicated, because there will be two cases of discount activities: limited goods and not limited goods, that is to say, two goods may participate in different discount activities; In addition, there will be two kinds of discount activities: direct discount and indirect discount, direct discount is the product directly hit a few discount this way, indirect discount is the purchase of several items will be a few discount.

The calculation logic of the whole discount activity is more complicated, so I will not paste the code here. For details, I can see the source code, and the address is attached at the end of the article.

After we have realized the calculation of the relevant coupon order or discount order, the code in the main logic is as follows:

        // Determine whether to calculate coupons or discounts
        // Coupon and discount are mutually exclusive
        ComputeAppOrderPayAmount couponOrDiscountOrderPayAmount = contextReq.getIsCouponOrder() ?
                new CouponOrderPayAmount(payAmountReq, couponService) : new DiscountOrderPayAmount(payAmountReq, contextReq.getAcDiscountMap());
        resp = couponOrDiscountOrderPayAmount.getAppOrderPayAmount();
Copy the code

Calculate the amount of goods to be purchased

In terms of business, there will also be a scene of adding goods on the user’s ordering interface (see Luckin coffee APP). When the user selects a certain product, the recommended product will appear on the ordering interface for the user to choose, and the price will be lower than that of the normal commodity purchasing interface.

Therefore, when users choose to purchase additional goods, they need to calculate the amount of added goods, and the added purchase does not participate in the calculation of coupons or discounts, and may limit the purchase. The core code is as follows:

 @Override
    public AppOrderPayAmountResp getAppOrderPayAmount(a) {
        // Check whether the merchant is open to add purchase
        if(! moreBuyService.isOpenMoreBuyFlag(payAmountReq.getSysUserId().getSysUserId())){return this.resp;
        }
        // Check whether the list of added items is empty
        if(CollectionUtils.isEmpty(payAmountReq.getReq().getMoreBuySpuList())){
            return this.resp;
        }
        // Query the information of the added goods
        Map<Long,MoreBuySpuOrderDto> moreBuySpuList = CollectionUtils.toMap(payAmountReq.getReq().getMoreBuySpuList(), MoreBuySpuOrderDto::getId);
        MoreBuyQryService moreBuyQryService = moreBuyService.getMoreBuyQryService();
        List<FoodMoreBuySpu> modelMoreBuySpuList = moreBuyQryService.getMoreBuySpuList(Lists.newArrayList(moreBuySpuList.keySet()));

        // Check the purchase limit
        for(FoodMoreBuySpu entity : modelMoreBuySpuList){
            MoreBuySpuOrderDto moreBuySpu = moreBuySpuList.get(entity.getId());
            if(moreBuySpu ! =null) {this.checkLimitNum(moreBuySpu, entity); }}// Calculate the price
        BigDecimal total = BigDecimal.ZERO;
        for(FoodMoreBuySpu entity : modelMoreBuySpuList){
            MoreBuySpuOrderDto moreBuySpu = moreBuySpuList.get(entity.getId());
            if(moreBuySpu ! =null){
                BigDecimal price = newBigDecimal(moreBuySpu.getQuantity()).multiply(entity.getPrice()); total = total.add(price); }}// Add the price of the added goods
        BigDecimal orderAmount = resp.getOrderAmount().add(total);
        BigDecimal dueAmount = resp.getDueAmount().add(total);
        resp.setOrderAmount(orderAmount);
        resp.setDueAmount(dueAmount);
        return resp;
    }
Copy the code

The code in the main logic is as follows:

        ComputeAppOrderPayAmount moreBuySpuOrderPayAmount = new MoreBuySpuOrderPayAmount(payAmountReq, resp, moreBuyService);
        resp = moreBuySpuOrderPayAmount.getAppOrderPayAmount();
Copy the code

Calculate the amount of credits deducted

In business, if the merchant opens the point deduction, the user will also have the choice of point deduction on the ordering interface. After a user buys a product, he or she will earn points, which can be deducted from the next purchase. The relevant logic of integral deduction is as follows:

    @Override
    public AppOrderPayAmountResp getAppOrderPayAmount(a) {
        // Return directly without integral deduction
        if(! ScoreDeductionFlag.DEDUCTION.getVal().equals(payAmountReq.getReq().getDeductionScore())){return resp;
        }
        // Query integral configuration
        ScoreQryService scoreQryService = scoreService.getScoreQryService();
        FoodScoreConfig config = scoreQryService.getScoreConfig(this.payAmountReq.getSysUserId().getSysUserId());

        // Whether there is a configuration
        if(config == null) {return this.resp;
        }
        // Whether the integral configuration is on
        if(FoodScoreOpenFlagEnum.CLOSE.getVal().equals(config.getOpenFlag())){
            return this.resp;
        }
        // Whether the credit deduction configuration is enabled
        if(FoodScoreDeduFlagEnum.CLOSE.getVal().equals(config.getDeduFlag())){
            return this.resp;
        }
        // Get user credits available
        Long usableScore = scoreService.getScoreUsable(this.payAmountReq.getSysUserId().getSysUserId(),
                this.payAmountReq.getCustomerId().getCustomerId());
        ScoreAmountHandler amountHandler = new ScoreAmountHandler(usableScore,
                this.payAmountReq.getAmount().amount(),
                config.getDeduRule());
        // Calculate the integral deduction
        ScoreAmountHandler.ScoreAmount scoreAmount = amountHandler.handle();
        this.resp.setOrderAmount(resp.getOrderAmount().subtract(scoreAmount.getAmount()));
        this.resp.setDiscountAmount(resp.getDueAmount().subtract(resp.getOrderAmount()));
        this.resp.setDeductionScore(scoreAmount.getScore());
        this.resp.setDeductionAmount(scoreAmount.getAmount());
        return this.resp;
    }
Copy the code

The main logic is as follows

        // Calculate the integral deduction
        ComputeAppOrderPayAmount scoreDeduction = new ScoreComputeAppOrderPayAmount(scoreService, payAmountReq, resp);
        resp = scoreDeduction.getAppOrderPayAmount();
Copy the code

Calculate the balance payment amount

In our business, when after the completion of all business computing if merchants to open the can use the balance payment, the user in the interface can choose to use the balance payment, in the use of the balance payment, we need to put the amount payable to reset to zero, because the field will be used to judge whether the front-end and back-end to arouse WeChat payments and WeChat pre order operation.

In the scenario of balance payment, only when the balance is greater than or equal to the payable amount can the balance payment be made, because it involves refund. If partial payment occurs, the refund scenario will be troublesome, so the business stipulated that the balance payment can only be paid in full.

    @Override
    public AppOrderPayAmountResp getAppOrderPayAmount(a) {
        // If the order amount is less than 0, return directly
        if(resp.getOrderAmount().compareTo(BigDecimal.ZERO) <=0) {return resp;
        }
        BigDecimal balanceAmount = this.getCustomerBalance();
        // If the balance is smaller than the order amount, return it directly
        if(balanceAmount.compareTo(resp.getOrderAmount()) < 0) {return resp;
        }
        // Non-balance payment, return directly
        if(! PayTypeEnum.BALANCE.getVal().equals(req.getPayType())){return resp;
        }
        // The balance payment will be fully deducted from the order amount
        resp.setBalancePayAmount(resp.getOrderAmount());
        resp.setOrderAmount(BigDecimal.ZERO);
        return resp;
    }
Copy the code

The main business logic is as follows:

        // Balance payment
        BalancePayAmountReq balancePayAmountReq = BalancePayAmountReq.builder().customerId(customerId).sysUserId(sysUserId)
                .payType(contextReq.getReq().getPayType())
                .build();
        ComputeAppOrderPayAmount balanceOrderPay = new BalanceOrderPayAmount(resp, balancePayAmountReq, balanceV2Service);
        resp = balanceOrderPay.getAppOrderPayAmount();
Copy the code

Master business logic

So far, we have dealt with the basic amount calculation logic, we can look at the code of the main business logic, very clear, different business are divided into the corresponding class to deal with, and eliminate a lot of if cases.

The last

The above is to generalize the processing logic of different businesses into different classes through basic demand analysis in the case of complex business, and to abstract the code of the primary logic through unified behavior processing method, so as to make the code of the primary logic clear and lay a solid foundation for the subsequent business if other logic needs to be added.

Of course, the above business processing and the whole project still need a lot of optimization, but the code is always step by step, the main logic is also after 2-3 iterations to optimize this way, if there is anything wrong, please feel free to comment.

Source code address: gitee.com/anyin/anyin…

If you find the project valuable to you, please give it a thumbs up.