A note from 2026: This article was published in 2016, when SAP Commerce was still commonly called hybris. The promotion engine, Drools integration, and RAO APIs have changed across SAP Commerce Cloud releases, so treat the code as a legacy proof of concept and verify class names and extension points in your target version.

Introduction

In one of our projects, we had a requirement to exclude certain products from the total cart value calculation. For example, we would have a custom condition that would trigger a free gift action if the order total is > 200, without including product A and product B in the order. In other words, the dollar values of the cart entries that contain products A and B need to be excluded from the cart total calculation.

It appears that this dynamic condition cannot be supported with the promotion engine, since when CartRAO is populated, the promotion condition parameters are not known.

The problem is accessing the products at runtime, because in the default implementation, all the condition constraints are compiled into static Drools rules. It is impossible, or very difficult, to extract condition values such as a list of products to be excluded from the calculation. When CartRAO is created, we do not even know which promotion could potentially trigger, so we cannot get the condition parameters from it.

Solution

My solution uses two promotions: one to calculate a “new cart total” if the specified products are present in the cart. The calculated value is stored in CartRAO and can be used in subsequent promotions in the same promotion calculation session. The second promotion depends on the first one, so it will work only if the specified products are found and a “new cart total” is calculated. Then you can combine them using stackability, so only one promotion fires at a time.

Promotion engine configuration for excluding products from cart total calculation

Demonstration

Technical details

public class CalcTotalPriceAction implements RuleExecutableAction {
    private CalculatedTotalsRao calculatedTotalsRao;
    private ProductService productService;
    private PriceService priceService;

    @Override
    public void executeAction(RuleActionContext ruleActionContext, Map<String, Object> map) throws RuleEvaluationException {
        String orderEntryRAOCode = "de.hybris.platform.ruleengineservices.rao.OrderEntryRAO";
        String cartEntryRAOCode = "de.hybris.platform.ruleengineservices.rao.CartRAO";;
        long totalPrice = calculateTotalPrice((Set<OrderEntryRAO>) (((DefaultDroolsRuleActionContext) ruleActionContext).getVariables().get(orderEntryRAOCode)));
        Set<CartRAO> carts = (Set<CartRAO>) (((DefaultDroolsRuleActionContext) ruleActionContext).getVariables().get(cartEntryRAOCode));
        CartRAO cart = carts.iterator().next();

        // I use cart.setTotal in the PoC, but I recommend having a designated attribute for the recalculated cart totals.
        // However, you will need to extend the cart totals condition to use the recalculated value.
        // For demonstration purposes, I used an OOTB attribute, "Total".
        cart.setTotal(BigDecimal.valueOf(cart.getTotal().longValue() - BigDecimal.valueOf(totalPrice).longValue()));
    }

    // For demonstration purposes, I use "long" for the price values.
    private long calculateTotalPrice(Set<OrderEntryRAO> entries) {
        long totalPrice = 0;

        for (OrderEntryRAO orderEntryRao : entries) {
            ProductModel productModel = productService.getProductForCode(orderEntryRao.getProduct().getCode());
            List<PriceInformation> prices = priceService.getPriceInformationsForProduct(productModel);
            double productPrice = prices.get(0).getPriceValue().getValue();

            // For demonstration purposes, I use "long" for the price values.
            totalPrice = totalPrice + Math.round(orderEntryRao.getQuantity() * productPrice);
        }

        return totalPrice;
    }
}

© Rauf Aliev, January 2017