Documentation

Ucommerce includes full API reference documentation and lots of helpful articles to help you build your e-commerce site as effortlessly as possible.

Topics Payment Providers
v7.18

Extend Marketing Foundation: Add a custom Discount

Out of the box Ucommerce comes with a number of handy discount types e.g. % off order line total, % off order total, fixed amount off, etc., but you can also add your own if you have a specific requirement Ucommerce doesn't meet out of the box.

To build a new discount you'll need:

  • Business logic, this is the target itself
  • User interface, the UI a customer will use to configure your target
  • Data access, creating new instances of your target and loading existing ones

Discounts

A discount is responsible for modifying the order or order lines, e.g. adding the discount type, new order line, or something else.

The discount is triggered upon successful evaluation of the Criteria configured for a promotion.

Discounts are automatically applied to the order in the basket pipeline. Specifically by the Basket.ApplyAwards task, which is executed whenever you run the pipeline:

    ObjectFactory.Instance.Resolve<ITransactionLibrary>().ExecuteBasketPipeline();
    			

It's important to mention that whenever the basket pipeline is executed Marketing Foundation will remove any existing discounts on the order and re-apply everything from scratch. This is done to ensure that discounts no longer applicable to the order are removed.

Free Gift Discount

As an example of this extension, we are going to create a "free gift" discount, which will support a common merchandising technique: Offer a free product if a customer buys another product.

The "Free gift" discount is different from BOGOF, which is supported out of the box because it adds a completely new item to the basket rather than adjusting price levels.

image

Implement Discount

To implement the behavior we want from our discount we need to implement the IAward interface, which has a single method Apply. The Apply method receives the order and order lines, which satisfied the targets of the promotion and thus triggered the discount.

For example, if you target a specific product on the order, the Apply method will receive the order and the order lines, which have that particular product associated. If the product is not present on the order the targets are not satisfied and thus the discount will not be triggered.

    
    
    namespace Ucommerce.Marketing.Awards
    {
    	/// <summary>
    	/// Responsible for applying discounts to orders and order lines
    	/// whenever the <see cref="IOrderLineTarget">order line targets</see> 
    	/// or <see cref="IPurchaseOrderTarget"/> for a <see cref="CampaignItem"/> are satisfied.
    	/// </summary>
    	public interface IAward : IEntity
    	{
    		/// <summary>
    		/// Applies discounts to the order, order lines, or both.
    		/// </summary>
    		/// <param name="order">Order which satisfied the targets.</param>
    		/// <param name="orderLines">
    		///		Order lines which satisfies the targets, 
    		///		might be a subset of the order lines on the order
    		/// </param>
    		void Apply(PurchaseOrder order, IList<OrderLine> orderLines);
    	}
    }
    

Ucommerce already has an implementation of IAward you can inherit called Award. You want to inherit this class if you want Ucommerce to store discount data for you. If you want to deal with data access yourself you can implement IAward directly. More on this in the "Store Discount" section of this article.

Free Gift Discount Implementation

For our particular case, we do not need order context for the Apply method because we're generating a completely new order line to hold the free gift.

Because Marketing Foundation will re-apply discounts whenever the basket pipeline is executed, we need to make sure that we do not keep generate order lines once the award was already applied.

So the implementation of FreeGiftAward ends up like this:

    /// <summary>
    /// Adds a new order line to the order with the specified
    /// product on it and a unit price of zero.
    /// </summary>
    /// <remarks>
    /// Will check for existing generated order lines with the
    /// SKU in question and only add the new order line if one
    /// does not exist.
    /// </remarks>
    public class FreeGiftAward : Award
    {
    	/// <summary>
    	/// Identifier of the product family to add. May not be null.
    	/// </summary>
    	public string Sku { get; set; }
    
    	/// <summary>
    	/// Identifier of the product variant to add. Can be null.
    	/// </summary>
    	public string VariantSku { get; set; }
    
    	/// <summary>
    	/// Adds a new order line to the order, 
    	/// only if the product is not present on a generate order line.
    	/// </summary>
    	/// <param name="order"></param>
    	/// <param name="orderLines"></param>
    	public override void Apply(TargetingContext targetingContext, Ucommerce.EntitiesV2.PurchaseOrder order, IList<OrderLine> orderLines)
    	{
    		// Check for generated order lines using dynamic order properties.
    
    		// Get order lines directly from order to ensure that we look at
    		// all order lines, not just the ones that satisfied the campaign
    		// targets.
    
    		if (AnyGeneratedOrderLinesWithThisFreeGift(order.OrderLines)) return;
    
    		// Look up SKU
    		Product freeGiftProduct = Product.SingleOrDefault(
    			x => x.Sku == this.Sku && x.VariantSku == this.VariantSku);
    
    		// Guard against missing product
    		if (freeGiftProduct == null) return;
    
    		Guid priceGroupGuid = ObjectFactory.Instance.Resolve<ICatalogContext>().CurrentPriceGroup.Guid;
    		PriceGroup priceGroup = PriceGroup.All().First(x => x.Guid == priceGroupGuid);
    		
    		// Free gift is not on the order so let's create a new order line to hold it.
    		// The customer might have one these items in the basket
    		// so we create a new order line to hold this one.
    		//add to the basket
    		var addToBasketPipeline = ObjectFactory.Instance.Resolve<IPipeline<IPipelineArgs<AddToBasketRequest, AddToBasketResponse>>>();
    		
    		var addToBasketRequest = new AddToBasketRequest()
    		{
    			PriceGroup = Ucommerce.EntitiesV2.PriceGroup.All().FirstOrDefault(),
    			Product = Ucommerce.EntitiesV2.Product.All().FirstOrDefault(x => x.Sku == "sku"),
    			PurchaseOrder = order,
    			Quantity = 1,
    			AddToExistingOrderLine = true,
    			ExecuteBasketPipeline = false,
    		};
    		
    		var addToBasketResponse = new AddToBasketResponse();
    		addToBasketPipeline.Execute(new AddToBasketPipelineArgs(addToBasketRequest, addToBasketResponse));
    
    		// Indicate the order line is generated.
    		addToBasketResponse.OrderLine["_generated"] = "true";
    
    		// Now add a discount to new orderline to
    		// make it free.
    		var discount = new Discount
    		{
    			CampaignName = CampaignItem.Campaign.Name,
    			CampaignItemName = CampaignItem.Name,
    			Description = Name,
    			AmountOffTotal = addToBasketResponse.OrderLine.Price + addToBasketResponse.OrderLine.VAT
    		};
    
    		addToBasketResponse.OrderLine.AddDiscount(discount);
    		order.AddDiscount(discount);
    	}
    
    	private bool AnyGeneratedOrderLinesWithThisFreeGift(IEnumerable<OrderLine> orderLines)
    	{
    		return orderLines.Any(
    			x => x.Sku == this.Sku
    				 && this.VariantSku == x.VariantSku
    				 && x.OrderProperties.Any(y => y.Key == "_generated" && y.Value == "true"));
    	}
    }
    
    

You'll notice the code using a couple of properties Sku and VariantSku to figure out which product to use for the order line.

Where's this information coming from?

Discount Configuration UI

To enable our new discount to appear in the back-end of Ucommerce we need a UI to configure it so store managers can interact with it.

To do that we need a new UserControl, which implements IConfigurable to provide access to the discount by creating new instances of it.

To know which marketing campaign and promotion the discount is being set up for it will need to inherit ViewEnabledControl<IEditCampaignItemView>, which will give you access to the Campaign and CampaignItem properties.

    public class FreeGiftUi : ViewEnabledControl<IEditCampaignItemView>, IConfigurable
    {
    	/// <summary>
    	/// Factory method for creating a new 
    	/// instance of <see cref="FreeGiftAward"/>
    	/// and saving it to the database.
    	/// </summary>
    	/// <returns>Saved instance of <see cref="FreeGiftAward"/>.</returns>
    	public object New()
    	{
    		var freeGiftAward = new FreeGiftAward();
    		// Grab context from the view so we know
    		// which campaign item it goes with.
    		freeGiftAward.CampaignItem = View.CampaignItem;
    		freeGiftAward.Save();
    
    		// Marketing Foundation will use the 
    		// DataSource to display the award.
    		DataSource = freeGiftAward;
    
    		return DataSource;
    	}
    
    	public object DataSource { get; set; }
    }
    

The Save method employed above is inherited from the Award class and stores the information about your discount.

But how does it know to do this?

Store Discount

With the discount properly set up, we need to store it: We will need a mapping and a new table to support this. Because our discount inherits Award Ucommerce will know how to instantiate it once stored in the database and mapped properly.

Ucommerce comes with the ability to set up custom data mappings to store custom data through the data layer of the application.

Please read the specific article on how to achieve this.

Save custom data in the database.

Add Discount to the List of Possible Discount

The new discount type must be available in the discount drop-down list when editing a promotion. To achieve this we need to insert a row in the table Ucommerce_EntityUi.

The following parameters are required:

  • Type: The class containing the discount including namespace and assembly, e.g MyNamespace.MyClass, MyDll.
  • VirtualPathUi: Virtual path relative to the Marketing Folder in the Ucommerce folder of the site.
  • SortOrder: Position you want the discount displayed at.

The row we'll insert will look like this:

    
    
    insert into Ucommerce_EntityUi values
    (
    	'MyApp.FreeGiftAward, MyApp', -- type
    	'Awards/FreeGiftAwardUi.ascx', -- virtual path
    	5 -- sort order
    )
    
    

Ucommerce is a multi-lingual platform as thus you're also able to specify how the new discount should be displayed in others language. You can do so by adding new entries to the Ucommerce_EntityUiDescription table and link the entries to the EntityUi entry created above.

Summary

With this in place, you now have a new discount ready to use in the Ucommerce back-end, which will work with all of the existing discounts and targets, which come out of the box.

This means that our free gift discount can be used in combination with a product target to support "buy one product and get another free" or in combination with a promo code target to support "supply promo code to get a free product", e.g. a product voucher type scenario.

Of course, the discount to be configured many times for a single promotion so you can trigger multiple results for the same discount.