Area: Episerver Commerce
Applies to versions: 10 and higher
Other versions:

Promotions [Legacy]

Recommendations [hide]

This topic describes promotions in Episerver Commerce.

Note: This functionality is obsolete and will be removed in a future release. It is being replaced by the new  Marketing system.

How it works

A promotion is a marketing tool used to increase sales of certain products or product lines. Promotions provide a way to apply discounts to the products, order totals, or shipping.

In this topic

Classes in this topic are available in the following namespaces:

  • Mediachase.Commerce.Orders
  • Mediachase.Commerce.Workflow.Activities.Cart
  • Mediachase.Commerce.Website.Helpers

How it works

You can apply a promotion to an individual SKU or Cart. The ECF/Mediachase.Commerce.Website/Helpers/StoreHelper.cs class (Mediachase.Commerce.Website.Helpers namespace) provides a convenient means to determine the discounted price for an SKU using the GetDiscountPrice() method. This calculates promotions that are specific to an individual SKU (such as buy SKU x, get $y off), if any exist.

GetDiscountPrice() returns the discounted price for an SKU but does not include quantity-based promotions for the SKU (such as buy 3 SKU xs and get %20 off the SKU price) and does not include order-wide promotion discounts (such as if your cart subtotal before taxes and shipping is over $100, shipping is free). The StoreHelper calls the promotion engine to calculate the price for a SKU by calling PromotionHelper.Eval().

You also can apply a promotion to a whole cart during checkout using Mediachase.Commerce.Workflow/Activities/OrderGroupActivities/Legacy/LegacyCalculateDiscountActivity.cs
 (Mediachase.Commerce.Workflow.Activities namespace). This activity is included in two workflows: LegacyCartValidateActivityFlow and LegacyCartPrepareActivityFlow (Mediachase.Commerce.Workflow namespace).

The legacy CartValidateWorkflow is designed to calculate the subtotal for a cart, including promotions; the subtotal does not include calculating taxes and shipping charges. The legacy CartPrepareWorkflow is designed to calculate the final state of a cart before the customer confirms the purchase, including taxes and shipping charges. Discounts are set in the cart by setting discount properties and calculating the cart total with deductions for the discounts.

The discount properties include:

  • LineItem.LineItemDiscountAmount. Total decimal amount of promotions applicable to that LineItem.
  • LineItem.LineItemDiscounts. Stores a collection of promotions, including names of each promotion and the amount.
  • LineItem.OrderLevelDiscountAmount. Amount of discount applicable from the order. This is applies if the promotion RewardType is for the whole order, and the promotion group is not shipping.
  • LineItem.OrderLevelDiscounts. Same as LineItemDiscountAmount except for promotion discounts applicable on the order level to an individual LineItem.
  • Shipment.ShippingDiscountAmount. Same as LineItemDiscountAmount except for promotion discounts applicable to a Shipment.
  • Shipment.Discounts. Same as LineItemDiscounts except for shipments.
  • OrderForm.DiscountAmount. Same as LineItemDiscountAmount except for promotion discounts applicable to the order as a whole, such as $50 off an order when certain conditions are met.
  • OrderForm.Discounts. Same as LineItemDiscounts except for the entire order.

The legacy CalculateDiscountActivity performs the discount calculations in the CalculateDiscounts() method.

After the legacy CalculateDiscountActivity activity runs, the CalculateTotalsActivity calculates the totals for each LineItem, OrderForm, and Shipment. It performs the following calculations to apply discounts:

  • LineItem.ExtendedPrice. Calculated by multiplying the ListPrice for a SKU by the quantity being purchased and then subtracting the LineItemDiscountAmount and OrderLevelDiscountAmount.
  • OrderForm.SubTotal. The sum of the SKUs price; in this case PlacedPrice times the Quantity minus the LineItemDiscountAmount.
  • OrderForm.Total. The sum of the subtotal, shipping charges, taxes, minus the shipping discounts and LineItem order-level discounts.


Expressions, core technology behind the marketing system, allow flexible and standards-based ways of extending different aspects of the system. Promotions, Customer Segments and Policies rely on expressions. The architecture of Expression Manager is similar to the Provider model used in .NET Framework. The engine relies on an external class to process the expression that you can specify in the configuration file. That class needs to implement the IExpressionValidator interface. The actual implementation is up to that class.

Episerver Commerce  has an implementation that relies on Windows for Workflow Foundation Rules engine, so if you pick that, you can use the Episerver Expression Editor to create expressions. The Expression Editor is located in ECF/ExpressionEditor in your solution. See also Introduction to the Windows Workflow Foundation Rules Engine.

Promotion rules engine

Episerver Commerce includes a Promotion Rules Engine designed to provide a flexible and fast framework to create different types of promotions that are displayed on the front-end.

The Promotion Engine has two distinct use cases:

  • Design mode. To configure promotions.
  • Runtime mode. To display and apply promotions.

Design mode

You can create a promotion using the graphical user interface in the administration console or by API. You can create most promotions by selecting the Custom Promotion type. In cases where promotions are very specific, a developer may need to create custom expressions and user interfaces.

The following groups of promotions determine in which scenario they are executed:

  • CatalogEntry. Executed against individual entry; you can apply it to items in the catalog. It is shown during search or catalog browsing.
  • Order. Executed against the whole order and displays only on the shopping cart/order pages.
  • Shipping. Executed against individual shipments and is shown only on the order/checkout pages.

Runtime mode

There are two distinct ways that promotions are applied to the product: while browsing or searching the catalog, and while viewing a shopping cart or checking out. They both rely on the MarketingContext.EvaluatePromotions method, which cycles through each promotion, prioritizing and filtering them.

The execution sequence for EvaluatePromotion is as follows:

  1. Checks global exclusivity (if a global promotion is applied, the execution stops).
  2. Checks target group.
  3. Checks group exclusivity.
  4. Checks limits (applied only once).
  5. Checks dates and status.
  6. Verifies coupon.
  7. Performs basic catalog/node/entry filtering.
  8. Checks conditions (expressions).
  9. Checks policies.
  10. Commits all new records (promotions added).

Browsing catalog

During catalog browsing, you typically use relatively simple promotions that apply to individual products only.

Catalog browsing execution sequence:

  1. StoreHelper.GetDiscountPrice(Entry entry, string catalogName)
  2. PromotionHelper.Eval(PromotionFilter filter)
  3. MarketingContext.Current.EvaluatePromotions(true, this.PromotionContext, filter)

Step 1: Get the sale price

During step 1, get the sale price used to calculate the base price for which discounts (if any) are added.

decimal minQuantity = 1;

            // get min quantity attribute
            if (entry.ItemAttributes != null)
               minQuantity = entry.ItemAttributes.MinQuantity;

            // we can't pass qauntity of 0, so make it default to 1
            if (minQuantity <= 0)
               minQuantity = 1;

            // Get sale price for the current user
            Price price = StoreHelper.GetSalePrice(entry, minQuantity);

Note: Minimum quantity is passed when browsing the item in the catalog, which typically defaults to 1.

Next, set up the PromotionContext, which provides context for rules to execute against. Because there is just one product in this case, create a new PromotionEntry object, which represents one product for our marketing system, and populate it with all the attributes (meta-fields from the catalog entry) that might be used during promotion evaluation.

Other key concepts during this stage are:

  • MarketingContext. You can add extra objects to be used by the expression engine.
  • TargetGroup. We set it to the entry, since this is the only promotion group we should be executing when browsing catalogs.
  • IPromotionEntryPopulate. This interface lets you override how properties are copied from Entry object to the PromotionEntry object. This is configured in the ecf.marketing.config file using PromotionEntryPopulateFunctionType config section. By default, this is set to Mediachase.Commerce.Marketing.Validators.PromotionEntryPopulate, Mediachase.Commerce.Marketing.Validators.

Example: creating PromotionEntry object

// Create new promotion helper, which will initialize PromotionContext object for us and setup context dictionary
        PromotionHelper helper = new PromotionHelper();

        // Get current context
        Dictionary<string, object> context = MarketingContext.Current.MarketingProfileContext;

        // Create filter
        PromotionFilter filter = new PromotionFilter();
        filter.IgnoreConditions = false;
        filter.IgnorePolicy = false;
        filter.IgnoreSegments = false;
        filter.IncludeCoupons = false;

        // Create new entry
        PromotionEntry promotEntry = new PromotionEntry(catalogName, String.Empty, entry.ID, price.Amount);

        // Populate entry parameters
        ((IPromotionEntryPopulate)MarketingContext.Current.PromotionEntryPopulateFunctionClassInfo.CreateInstance()).Populate(ref promotEntry, entry);

        PromotionEntriesSet sourceSet = new PromotionEntriesSet();

        // Configure promotion context
        helper.PromotionContext.SourceEntriesSet = sourceSet;
        helper.PromotionContext.TargetEntriesSet = sourceSet;

        // Only target entries
        helper.PromotionContext.TargetGroup = PromotionGroup.GetPromotionGroup(PromotionGroup.PromotionGroupKey.Entry).Key;

Example: calling the eval method and returning the discounted price

// Execute the promotions and filter out basic collection of promotions, we need to execute with cache disabled, so we get latest info from the database

        // Check the count, and get new price
        if (helper.PromotionContext.PromotionResult.PromotionRecords.Count > 0)
            return ObjectHelper.CreatePrice(price.Amount - GetDiscountPrice(helper.PromotionContext.PromotionResult), price.CurrencyCode);
            return price;

Step 2: Execute the promotion engine

Execute the promotion engine using all information collected during step 1.

Example: executing the promotion engine

MarketingContext.Current.EvaluatePromotions(true, this.PromotionContext, filter);

Step 3: Validate the expressions

Goes through each promotion, checks the policies, customer segments, dates and validates the expressions. When the promotion is successfully applied, it is typically added to the PromotionResult object as a PromotionItemRecord object, which contains information about affected items and discounts applied.

Cart checkout

During checkout, more complex promotions can be applied. Now, you are working in the context of the order system. You no longer calculate discount directly, but through the use of workflow activities.

Execution sequence:

  1. CartHelper.RunWorkflow("CartValidate") CalculateDiscountsActivity
  2. CalculateDiscounts

Step 1: Initiate the workflow

Typically, the workflow checks whether entries exist and are available, removes existing discounts, then executes the Legacy CalculateDiscountsActivity. This occurs during each step in the checkout process or when accessing the shopping cart.

Step 2: Calculate the discounts

Discount activity logic is very similar to the one used during browsing, with exception of two additional discount groups: Order and Shipment. The engine first calculates line item discounts, then order, then shipments.

Example: calculating discount for each individual order form

#region Determine Order level discounts
            foreach (OrderForm form in order.OrderForms)
                // Now process global order discounts
                // Now start processing it
                // Create source from current form
                sourceSet = CreateSetFromOrderForm(form);
                promoContext.SourceEntriesSet = sourceSet;
                promoContext.TargetEntriesSet = sourceSet;
                promoContext.TargetGroup = PromotionGroup.GetPromotionGroup(PromotionGroup.PromotionGroupKey.Order).Key;

            // Evaluate conditions
            MarketingContext.Current.EvaluatePromotions(useCache, promoContext, filter);
Just like in previous step, the legacy CalculateDiscountsActivity relies on IPromotionEntryPopulate interface to populate properties for each item. This means that there is only one place to customize the logic related to populating custom properties, and it can be completely replaced without changing the core logic.

Example: IPromotionEntryPopulate interface

((IPromotionEntryPopulate)MarketingContext.Current.PromotionEntryPopulateFunctionClassInfo.CreateInstance()).Populate(ref entry, lineItem);


This read-only property of the CustomerContact class is the customer group used by the promotion engine to determine price and the CustomerGroup property one would use in a Customer Segment condition expression.

For information about EffectiveCustomerGroup, see the Customers section.

Do you find this information helpful? Please log in to provide feedback.

Last updated: Oct 24, 2016

Recommendations [hide]