Extend Marketing Foundation: Add an Award
Out of the box Ucommerce comes with a number of handy discount types or awards as they are called in Ucommerce, 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 award 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
Awards
An award is responsible for modifying the order or order lines, e.g. adding a discount, new order line, or something else.
The award is triggered upon successful evaluation of the Act targets configured for a campaign item.
Awards 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 Award
As an example of this extension, we are going to create a "free gift" award, which will support a common merchandising technique: Offer a free product if a customer buys another product.
The "Free gift" award 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.
Implement Award
To implement the behavior we want from our award 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 campaign item and thus triggered the award.
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 award 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 award data for you. If you want to deal with data access yourself you can implement IAward
directly. More on this in the "Store Award" section of this article.
Free Gift Award 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 awards 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, 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. OrderLine generatedOrderline = order.AddProduct( priceGroup, freeGiftProduct, quantity: 1, addToExistingLine: false); // Indicate the order line is generated. generatedOrderline["_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 = generatedOrderline.Price + generatedOrderline.VAT }; generatedOrderline.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?
Award Configuration UI
To enable our new award 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 award by creating new instances of it.
To know which marketing campaign and campaign item the award 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 award.
But how does it know to do this?
Store Award
With the award properly set up, we need to store it: We will need a mapping and a new table to support this. Because our award 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 Award to the List of Possible Awards
The new award type must be available in the award drop-down list when editing a campaign item. To achieve this we need to insert a row in the table Ucommerce_EntityUi
.
The following parameters are required:
- Type: The class containing the award 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 award 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 award 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 award ready to use in the Ucommerce back-end, which will work with all of the existing awards and targets, which come out of the box.
This means that our free gift award 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 award to be configured many times for a single campaign item so you can trigger multiple results for the same discount.