Customizing application of Buy X Get Y discounts

If you want to modify the way Buy X Get Y discounts apply the discount on the shopping cart items, you need to override the GetMultiBuyDiscountsApplicatorInternal method in theMultiBuyDiscountInfoProviderclass of the CMS.Ecommerce namespace.




/// <summary>
/// Returns an instance of the MultiBuyDiscountsApplicator class that applies
/// the Buy X Get Y discount on a shopping cart items.
/// </summary>
protected virtual MultiBuyDiscountsApplicator GetMultiBuyDiscountsApplicatorInternal()
{
    return new MultiBuyDiscountsApplicator();
}


Since the GetMultiBuyDiscountsApplicatorInternal method returns a MultiBuyDiscountsApplicator object, you need to create your own class that inherits from the MultiBuyDiscountsApplicator class.

How it works by default

When the system needs to apply the specific Buy X Get Y discount, the MultiBuyDiscountsEvaluator class initializes the MultiBuyDiscountsApplicator class.

  • The ApplyDiscount method remembers how many units of a specific item applied for the given Buy X Get Y discount.
    • The process of creating a record occurs in the RememberDiscountApplication method. The method adds the information about the discounted item and its number of units to the complete list of discounts application.
      • The process of getting the information for the specific discount occurs in the GetDiscountsApplication method. In case the discount has not been applied yet, it creates a new record for the discount.
  • The GetDiscountReport method returns a MultiBuyDiscountsReport object with information about the discount and the items on which the discount was applied on.
  • The GetDiscountsSummary method loops through all items on which the discount was applied on and adds them to the DiscountSummaryItem object, which contains the name of the discount, the saving and the saving in the main currency. Then, the method returns a list of the DiscountSummaryItem objects.
    • The process of retrieving the savings occurs in the GetSavings method.

The information with all applications of all Buy X Get Y discounts is kept in the DiscountApplications property. The DiscountApplications is a dictionary that has every Buy X Get Y discount (of the IMultiBuyDiscount interface) as the key and another dictionary as the value. The other dictionary then has a specific shopping cart item (of the ShoppingCartItemInfo class) as the key and the number of the used items for the specific discount (int) as the value.

Source code

See the whole source code of the class




using System.Collections.Generic;
using System.Linq;

namespace CMS.Ecommerce
{
    // Dictionary with records of all applied Buy X Get Y discounts. The records than have another
    // dictionary with information about the application of specific items.
    using MultiBuyDiscountApplication = Dictionary<IMultiBuyDiscount, Dictionary<ShoppingCartItemInfo, int>>;

    /// <summary>
    /// Class applying a Buy X Get Y discount on the given shopping cart items.
    /// </summary>
    public class MultiBuyDiscountsApplicator : IMultiBuyDiscountsApplicator
    {
        #region "Variables and properties"
        private readonly MultiBuyDiscountApplication mDiscountApplications = new MultiBuyDiscountApplication();

        /// <summary>
        /// Records of all applied Buy X Get Y discounts that have records
        /// of the used items and the number of the used units of the items.
        /// </summary>
        protected MultiBuyDiscountApplication DiscountApplications
        {
            get
            {
                return mDiscountApplications;
            }
        }
        #endregion

        #region "IMultiBuyDiscountsApplicator methods"
        /// <summary>
        /// Resets the applicator to its initial state.
        /// </summary>
        public void Reset()
        {
        }

        /// <summary>
        /// Applies the discount to the given number of units of the given shopping cart item.
        /// </summary>
        /// <param name="discount">Discount to be applied.</param>
        /// <param name="itemToBeDiscounted">Shopping cart item on which the discount is applied on.</param>
        /// <param name="units">Number of units to be discounted.</param>
        public void ApplyDiscount(IMultiBuyDiscount discount, ShoppingCartItemInfo itemToBeDiscounted, int units)
        {
            // Creates a record that the discount was applied and what it was applied on.
            RememberDiscountApplication(discount, itemToBeDiscounted, units);
        }

        /// <summary>
        /// Checks whether the discount should add an autoadded product.
        /// The MultiBuyDiscountsAutoAdder class takes care of autoadding.
        /// </summary>
        /// <param name="discount">Discount to be checked.</param>
        public bool AcceptsMissedDiscount(IMultiBuyDiscount discount)
        {
            return false;
        }
        #endregion

        #region "Discount application summary"
        /// <summary>
        /// Returns the discount report.
        /// </summary>
        /// <returns>Returns the discount report.</returns>
        internal MultiBuyDiscountsReport GetDiscountReport()
        {
            return new MultiBuyDiscountsReport(GetDiscountsSummary(), mDiscountApplications);
        }

        /// <summary>
        /// Returns a list of all items on which the discount was applied.
        /// </summary>
        /// <returns>Returns a list of all items on which the discount was applied.</returns>
        protected virtual List<DiscountSummaryItem> GetDiscountsSummary()
        {
            var report = new List<DiscountSummaryItem>();

            // Loop through all applications of the discount.
            foreach (var discount in mDiscountApplications.Keys)
            {
                var discountedItems = mDiscountApplications[discount];

                // Calculate savings for the discount. The saving is calculated for every discounted item
                // as unit discount * number of units.
                var saved = discountedItems.Sum(item => GetSavings(discount, item.Key, item.Value, true));
                var savedInMainCurrency = discountedItems.Sum(item => GetSavings(discount, item.Key, item.Value, false));

                // Add the savings to the discount report.
                var log = new DiscountSummaryItem
                {
                    Name = discount.DiscountName,
                    Value = saved,
                    ValueInMainCurrency = savedInMainCurrency
                };
                report.Add(log);
            }

            return report;
        }

        /// <summary>
        /// Returns discount saving for one particular item.
        /// </summary>
        /// <param name="discount">Applied discount.</param>
        /// <param name="item">Discounted item.</param>
        /// <param name="units">Discounted units count.</param>
        /// <param name="applyExchangeRate">Indicates whether the exchange rate from the main currency is applied.</param>
        /// <returns>Returns discount saving for one particular item.</returns>
        protected double GetSavings(IMultiBuyDiscount discount, ShoppingCartItemInfo item, int units, bool applyExchangeRate)
        {
            double unitDiscount = ECommerceHelper.GetDiscountValue(item.UnitTotalPriceInMainCurrency, discount.Value, discount.IsFlat);
            if (applyExchangeRate)
            {
                unitDiscount = item.ApplyExchangeRate(unitDiscount);
            }
            return units * unitDiscount;
        }

        /// <summary>
        /// Stores information about the application of the discount on the particular item.
        /// </summary>
        /// <param name="discount">Discount applied on the item.</param>
        /// <param name="item">Item on which the discount was applied.</param>
        /// <param name="units">Number of the discounted units.</param>
        private void RememberDiscountApplication(IMultiBuyDiscount discount, ShoppingCartItemInfo item, int units)
        {
            Dictionary<ShoppingCartItemInfo, int> discountedItems = GetDiscountApplications(discount);

            // Add a record for the item discounted for the first time.
            if (!discountedItems.ContainsKey(item))
            {
                discountedItems.Add(item, 0);
            }

            // Increase number of item units discounted by the given discount.
            discountedItems[item] += units;
        }

        /// <summary>
        /// Gets all items on which the specific Buy X Get Y discount was applied.
        /// </summary>
        /// <param name="discount">Discount which was applied.</param>
        /// <returns>Returns all items on which the discount was applied.</returns>
        private Dictionary<ShoppingCartItemInfo, int> GetDiscountApplications(IMultiBuyDiscount discount)
        {
            // Create a record for the discount if not used yet.
            if (!mDiscountApplications.ContainsKey(discount))
            {
                mDiscountApplications.Add(discount, new Dictionary<ShoppingCartItemInfo, int>());
            }

            // Return the application record for the specific discount.
            return mDiscountApplications[discount];
        }
        #endregion
    }
}


Creating a custom application process of Buy X Get Y discounts

To customize the Buy X Get Y discount application:

  1. Override theCMS.Ecommerce.MultiBuyDiscountInfoProvider.GetMultiBuyDiscountsApplicatorInternalmethod.
  2. Implement a class which inherits from theMultiBuyDiscountsApplicatorclass.

Customizing the MultiBuyDiscountInfoProvider class

Override the GetMultiBuyDiscountsApplicatorInternal virtual method to be able to integrate your custom MultiBuyDiscountsApplicator class.

  1. Create a new class file in your project in Visual Studio, for example CustomMultiBuyInfoProvider.cs that inherits from the MultiBuyDiscountInfoProvider class.

  2. Add the CMS and CMS.Ecommerce namespaces to the using block.

  3. Register the new InfoProvider class:

    
    
    
     [assembly: RegisterCustomProvider(typeof(CustomMultiBuyInfoProvider))]
    
    
     
  4. Override the GetMultiBuyDiscountsApplicatorInternalmethod with your custom (not yet existing) MultiBuyDiscountsApplicator class:

    
    
    
     public class CustomMultiBuyInfoProvider : MultiBuyDiscountInfoProvider
     {
         protected override MultiBuyDiscountsApplicator GetMultiBuyDiscountsApplicatorInternal()
         {
             return new CustomMultiBuyApplicator();
         }
     }
    
    
     

    Creating the CustomMultiBuyInfoProvider class

The system now returns yet non-existing CustomMultiBuyApplicator when applying Buy X Get Y discounts. Continue with the next steps to achieve the desired functionality.

You can apply a different logic and modify the shopping cart:




public class CustomShoppingCartInfoProvider : ShoppingCartInfoProvider
{
    protected override MultiBuyDiscountsApplicator EvaluateMultiBuyDiscountsInternal(ShoppingCartInfo cart)
    {
        // Discounts are applied in the applicator object.
        CustomMultiBuyApplicator applicator = base.EvaluateMultiBuyDiscountsInternal(cart);

        // ... Your logic with the applicator object ...

        // Return the applicator.
        return applicator;
    }
}


Customizing the MultiBuyDiscountsApplicator class

Implement a class inheriting from the MultiBuyDiscountsApplicator class to provide your custom logic of evaluating Buy X Get Y discounts.

  1. Create a new class file in your project in Visual Studio, for example CustomMultiBuyApplicator.cs that inherits from the MultiBuyDiscountsApplicator class.

  2. Add the CMS and CMS.Ecommerce namespaces to the using block.

  3. Assign the inheritance from the MultiBuyDiscountsEvaluator class to the CustomMultiBuyEvaluator class:

    
    
    
     public class CustomMultiBuyApplicator : MultiBuyDiscountsApplicator
     {
     }
    
    
     

    Creating the CustomMultiBuyApplicator class

  4. Override the methods you want to change. The MultiBuyDiscountsApplicator class contains the following virtual method:

    • protected virtual List<DiscountSummaryItem> GetDiscountsSummary() – Returns a list of all items on which the discount was applied.

      You can access the GetDiscountsSummary method and modify the list with the discounted items:

      
      
      
        protected override List<DiscountSummaryItem> GetDiscountsSummary()
        {
            // You can access the DiscountApplications property here and apply your custom logic.
            YourLogic(DiscountApplications);
      
            // Return the DiscountApplications property.
            return base.GetDiscountsSummary();
        }
      
      
        

See the complete default source code above.

The Buy X Get Y discounts are now applied according to your changes.