The same pattern presented here can be used to set new fields on any entity in the commerce ASP.net core engine.

The example is based on the following business case, a cart needs to be flagged as belonging to a VIP customer and a 20% discount must be given on the subtotal of the cart if the flag is true. If the flag is false no VIP discount is given.

In this example, we will be modifying the Sitecore commerce engine SDK and the Sitecore commerce reference storefront. If you would like to use the retail demo site (Habitat\Helix) the steps will be exactly the same as only the “manager” classes are changed on the front end.

We will be adding our custom code for the commerce engine to the existing “adventure works” plugin. You can use your own plugin, however, I suggest you get the example working with the standard example plugin first then move and rename things as necessary later on.

At a high level, we will be setting the new field on the cart by using a new command created within the commerce engine API. A new pipeline handler will be created within Commerce connect that will execute the new command when a cart line is added in the storefront. The new field will be set on the front end by modifying the managers within the reference storefront code.

Step 1 : Modify the Commerce engine

The Sitecore commerce engine is modified using the SDK. Refer to developers guide for a basic run through here. You can download it here.

Open the main solution “Customer.Sample.Solution.sln” from the root of the SDK and make the following changes…

Add the following to the “Sitecore.Commerce.Plugin.AdventureWorks” project.

  • Add a new component, this component will be responsible for the definition of the new flag “ApplyVIPPricing” on the cart header.
      1. Add a new class named “CartVIPStatusComponent” to a folder named “Components”.
      2. Copy the following code into the class…


    namespace Sitecore.Commerce.Plugin.AdventureWorks.Components
    {
    using Sitecore.Commerce.Core;
    /// <summary>
    /// The cart VIP pricing component.
    /// </summary>
    public class CartVIPStatusComponent : Component
    {
    /// <summary>
    /// Gets or sets a value indicating whether to apply VIP pricing.
    /// </summary>
    public bool ApplyVipPricing { get; set; }
    /// <summary>
    /// Gets or sets the VIP discount amount.
    /// </summary>
    public decimal TotalVipDiscountAmount { get; set; }
    }
    }

  • Add a new command, the command will be responsible for executing the code that sets the new field on the cart header.
      1. Add a new class named “SetCartVIPStatusCommand” to a folder named “Commands”.
      2. Copy the following code into the class…


    namespace Sitecore.Commerce.Plugin.AdventureWorks.Commands
    {
    using System;
    using System.Threading.Tasks;
    using Sitecore.Commerce.Core;
    using Sitecore.Commerce.Core.Commands;
    using Sitecore.Commerce.Plugin.AdventureWorks.Components;
    using Sitecore.Commerce.Plugin.Carts;
    /// <summary>
    /// The set cart vip status command.
    /// </summary>
    public class SetCartVIPStatusCommand : CommerceCommand
    {
    /// <summary>
    /// The _calculate cart pipeline.
    /// </summary>
    private readonly ICalculateCartPipeline _calculateCartPipeline;
    /// <summary>
    /// The _get cart pipeline.
    /// </summary>
    private readonly IGetCartPipeline _getCartPipeline;
    private readonly IPersistEntityPipeline _persistEntityPipeline;
    /// <summary>
    /// Initializes a new instance of the <see cref="SetCartVIPStatusCommand"/> class.
    /// </summary>
    /// <param name="calculateCartPipeline">
    /// </param>
    /// <param name="getCartPipeline">
    /// </param>
    /// <param name="serviceProvider">
    /// The service provider.
    /// </param>
    /// <param name="persistEntityPipeline"></param>
    public SetCartVIPStatusCommand(
    ICalculateCartPipeline calculateCartPipeline,
    IGetCartPipeline getCartPipeline,
    IServiceProvider serviceProvider,
    IPersistEntityPipeline persistEntityPipeline) : base(serviceProvider)
    {
    this._calculateCartPipeline = calculateCartPipeline;
    this._getCartPipeline = getCartPipeline;
    this._persistEntityPipeline = persistEntityPipeline;
    }
    /// <summary>
    /// The process of the command
    /// </summary>
    /// <param name="commerceContext">
    /// The commerce context
    /// </param>
    /// <param name="cartId">
    /// The cart Id.
    /// </param>
    /// <param name="applyVIPPricing">
    /// The apply VIP Pricing.
    /// </param>
    /// <returns>
    /// The <see cref="Task"/>.
    /// </returns>
    public async Task<Cart> Process(CommerceContext commerceContext, string cartId, bool applyVIPPricing)
    {
    try
    {
    // Set the new fields on the component attached to the cart
    var resolveCartArgument = new ResolveCartArgument(
    commerceContext.CurrentShopName(),
    cartId,
    commerceContext.CurrentShopperId());
    var cart =
    await this._getCartPipeline.Run(resolveCartArgument, commerceContext.GetPipelineContextOptions());
    if (cart == null)
    {
    return null;
    }
    // Set the custom fields on the cart
    cart.GetComponent<CartVIPStatusComponent>().ApplyVipPricing = applyVIPPricing;
    // Save the cart here to make sure the new flag is set
    var result = await this._persistEntityPipeline.Run(new PersistEntityArgument(cart), commerceContext.GetPipelineContextOptions());
    return result.Entity as Cart;
    }
    catch (Exception e)
    {
    return await Task.FromException<Cart>(e);
    }
    }
    }
    }

  • Add a new command controller, the command controller will be responsible for executing the command. This is the route and is what is exposed to the outside world via the API.
      1. Add a new class named “CommandsController” to a folder named “Controller”
      2. Copy the following code into the class


    namespace Sitecore.Commerce.Plugin.AdventureWorks.Controller
    {
    using System;
    using System.Threading.Tasks;
    using System.Web.Http.OData;
    using Microsoft.AspNetCore.Mvc;
    using Sitecore.Commerce.Core;
    using Sitecore.Commerce.Plugin.AdventureWorks.Commands;
    using Sitecore.Commerce.Plugin.Orders;
    /// <summary>
    /// The resend order controller.
    /// </summary>
    public class CommandsController : CommerceController
    {
    /// <summary>
    /// Initializes a new instance of the <see cref="CommandsController"/> class.
    /// </summary>
    /// <param name="serviceProvider">
    /// The service provider.
    /// </param>
    /// <param name="globalEnvironment">
    /// The global environment.
    /// </param>
    public CommandsController(IServiceProvider serviceProvider, CommerceEnvironment globalEnvironment)
    : base(serviceProvider, globalEnvironment)
    {
    }
    /// <summary>
    /// The resend order to erp.
    /// </summary>
    /// <param name="value">
    /// The value.
    /// </param>
    /// <returns>
    /// The <see cref="Task"/>.
    /// </returns>
    [HttpPut]
    [Route("SetCartVIPStatus()")]
    public async Task<IActionResult> SetCartVIPStatus([FromBody] ODataActionParameters value)
    {
    var cartId = value["cartId"].ToString();
    var applyVipStatus = value["applyVIPStatus"].ToString();
    var setCartVipStatusCommand = this.Command<SetCartVIPStatusCommand>();
    await setCartVipStatusCommand.Process(this.CurrentContext, cartId, Convert.ToBoolean(applyVipStatus));
    return new ObjectResult(setCartVipStatusCommand);
    }
    }
    }

  • Add a new pipeline block, this block is responsible for executing the logic to calculate the new adjustment (VIP discount) based on the flag that has been set on the header. It will run each time a cart is calculated.
      1. Add a new class named “CalculateCartVIPBlock” to a folder named “Pipelines\Blocks”
      2. Copy the following code into the class


    namespace Sitecore.Commerce.Plugin.AdventureWorks.Pipelines.Blocks
    {
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using Sitecore.Commerce.Core;
    using Sitecore.Commerce.Plugin.AdventureWorks.Components;
    using Sitecore.Commerce.Plugin.Carts;
    using Sitecore.Commerce.Plugin.Pricing;
    using Sitecore.Framework.Pipelines;
    /// <summary>
    /// The calculate cart block.
    /// </summary>
    public class CalculateCartVIPBlock : PipelineBlock<Cart, Cart, CommercePipelineExecutionContext>
    {
    /// <summary>
    /// Initializes a new instance of the <see cref="CalculateCartVIPBlock"/> class.
    /// </summary>
    /// <param name="persistEntityPersistEntityPipeline">
    /// The persist entity persist entity pipeline.
    /// </param>
    /// <param name="findEntityPipeline">
    /// The find entity pipeline.
    /// </param>
    /// <param name="serviceProvider">
    /// The service provider.
    /// </param>
    public CalculateCartVIPBlock(
    IPersistEntityPipeline persistEntityPersistEntityPipeline,
    IFindEntityPipeline findEntityPipeline,
    IServiceProvider serviceProvider)
    {
    }
    /// <summary>
    /// The run.
    /// </summary>
    /// <param name="arg">
    /// The arg.
    /// </param>
    /// <param name="context">
    /// The context.
    /// </param>
    /// <returns>
    /// The <see cref="Task"/>.
    /// </returns>
    public override Task<Cart> Run(Cart arg, CommercePipelineExecutionContext context)
    {
    /*
    * This block will be called anytime the cart is recalculated. We can now
    * retrieve the flag on the cart and if its true do something like call a webservice that contains the VIP discount logic.
    * You could also have another block on the calculate line if the discount was set at a line level
    */
    const string AdjustmentType = "VIP";
    const string AdjustementName = "VIP Discount";
    var vipDiscountApplied = arg.Adjustments.FirstOrDefault(x => x.AdjustmentType == AdjustmentType);
    if (arg.GetComponent<CartVIPStatusComponent>().ApplyVipPricing == false)
    {
    if (vipDiscountApplied != null)
    {
    arg.Adjustments.Remove(vipDiscountApplied);
    }
    return Task.FromResult(arg);
    }
    // Do your custom discount calculation here
    var amount = arg.Totals.SubTotal.Amount * ((decimal)0.20);
    var adjustment = new Money(context.CommerceContext.CurrentCurrency(), amount * 1);
    if (vipDiscountApplied != null)
    {
    vipDiscountApplied.Adjustment = adjustment;
    }
    else
    {
    // All the setup information should come from a policy or another system
    var awarededAdjustment = new CartLevelAwardedAdjustment
    {
    Name = AdjustementName,
    DisplayName = AdjustementName,
    Adjustment = adjustment,
    AdjustmentType = AdjustmentType,
    AwardingBlock = this.Name,
    IncludeInGrandTotal = true,
    IsTaxable = false
    };
    arg.Adjustments.Add(awarededAdjustment);
    }
    return Task.FromResult(arg);
    }
    }
    }

  • Add a new pipeline block, this block is responsible for registering the route for the new command.
      1. Add a new class named “ConfigureServiceApiBlock” to a folder named “Pipelines\Blocks”.
      2. Copy the following code into the class


    namespace Sitecore.Commerce.Plugin.AdventureWorks.Pipelines.Blocks
    {
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.OData.Builder;
    using Sitecore.Commerce.Core;
    using Sitecore.Commerce.Core.Commands;
    using Sitecore.Framework.Conditions;
    using Sitecore.Framework.Pipelines;
    /// <summary>
    /// The Configure Service Api Block.
    /// </summary>
    public class ConfigureServiceApiBlock : PipelineBlock<ODataConventionModelBuilder, ODataConventionModelBuilder, CommercePipelineExecutionContext>
    {
    /// <summary>
    /// The _pipeline.
    /// </summary>
    private readonly IPersistEntityPipeline _pipeline;
    public ConfigureServiceApiBlock(IPersistEntityPipeline persistEntityPipeline)
    {
    this._pipeline = persistEntityPipeline;
    }
    public override Task<ODataConventionModelBuilder> Run(ODataConventionModelBuilder modelBuilder, CommercePipelineExecutionContext context)
    {
    Condition.Requires(modelBuilder).IsNotNull($"{base.Name}: The argument can not be null");
    var configuration = modelBuilder.Action("SetCartVIPStatus");
    configuration.Parameter<string>("cartId");
    configuration.Parameter<bool>("applyVIPStatus");
    configuration.ReturnsFromEntitySet<CommerceCommand>("Commands");
    return Task.FromResult(modelBuilder);
    }
    }
    }

  • Modify the existing classes named “ConfigureSitecore” and “ServiceCollectionExtensions”, these classes are responsible for registering all the custom code with the engine.
      1. Paste or merge the following code into the “ConfigureSitecore” class.


    namespace Sitecore.Commerce.Plugin.AdventureWorks
    {
    using System.Reflection;
    using Microsoft.Extensions.DependencyInjection;
    using Sitecore.Commerce.Core;
    using Sitecore.Framework.Configuration;
    using Sitecore.Framework.Pipelines.Definitions.Extensions;
    /// <summary>
    /// Defines the configure sitecore class for the AdventureWorks plugin.
    /// </summary>
    /// <seealso cref="Sitecore.Framework.Configuration.IConfigureSitecore" />
    public class ConfigureSitecore : IConfigureSitecore
    {
    /// <summary>
    /// The configure services.
    /// </summary>
    /// <param name="services">
    /// The services.
    /// </param>
    public void ConfigureServices(IServiceCollection services)
    {
    var assembly = Assembly.GetExecutingAssembly();
    services.RegisterAllPipelineBlocks(assembly);
    services.Sitecore().Pipelines(config => config
    .ConfigurePipeline<IBootstrapPipeline>(d => d.Add<BootstrapAwEnvironmentBlock>())
    .ConfigurePipeline<IInitializeEnvironmentPipeline>(d =>
    {
    d.Add<BootstrapManagedListsBlock>()
    .Add<InitializeEnvironmentRegionsBlock>()
    .Add<InitializeEnvironmentPolicySetsBlock>()
    .Add<InitializeEnvironmentShopsBlock>()
    .Add<InitializeEnvironmentSellableItemsBlock>()
    .Add<InitializeEnvironmentPricingBlock>()
    .Add<InitializeEnvironmentPromotionsBlock>();
    })
    .ConfigurePipeline<IStartEnvironmentPipeline>(d => { })
    .ConfigurePipeline<IRunningPluginsPipeline>(c => { c.Add<RegisteredPluginBlock>().After<RunningPluginsBlock>(); })
    // Registers the route for our custom command
    .ConfigurePipeline<IConfigureServiceApiPipeline>(c => c.Add<Pipelines.Blocks.ConfigureServiceApiBlock>()));
    services.ConfigureCartPipelines();
    services.ConfigureOrdersPipelines();
    services.RegisterAllCommands(assembly);
    }
    }
    }

      1. Paste or merge the following code into the “ServiceCollectionExtensions” class.


    namespace Sitecore.Commerce.Plugin.AdventureWorks
    {
    using Microsoft.Extensions.DependencyInjection;
    using Sitecore.Commerce.Plugin.AdventureWorks.Pipelines.Blocks;
    using Sitecore.Commerce.Plugin.Carts;
    using Sitecore.Commerce.Plugin.Catalog;
    using Sitecore.Commerce.Plugin.Catalog.Cs;
    using Sitecore.Commerce.Plugin.Coupons;
    using Sitecore.Commerce.Plugin.Fulfillment;
    using Sitecore.Commerce.Plugin.Inventory.Cs;
    using Sitecore.Commerce.Plugin.Orders;
    using Sitecore.Commerce.Plugin.Payments;
    using Sitecore.Commerce.Plugin.Promotions;
    using Sitecore.Commerce.Plugin.Tax;
    using Sitecore.Framework.Pipelines.Definitions.Extensions;
    /// <summary>
    /// The services extensions.
    /// </summary>
    public static class ServiceCollectionExtensions
    {
    /// <summary>
    /// The configure cart pipelines.
    /// </summary>
    /// <param name="services">
    /// The services.
    /// </param>
    /// <returns>
    /// The <see cref="IServiceCollection"/>.
    /// </returns>
    public static IServiceCollection ConfigureCartPipelines(this IServiceCollection services)
    {
    services.Sitecore().Pipelines(config => config
    .ConfigurePipeline<ICalculateCartLinesPipeline>(builder => builder
    .Add<PopulateCartLineItemsBlock>()
    .Add<CalculateCartLinesPriceBlock>()
    .Add<ValidateCartLinesPriceBlock>()
    .Add<CalculateCartLinesSubTotalsBlock>()
    .Add<CalculateCartLinesFulfillmentBlock>()
    .Add<ValidateCartCouponsBlock>()
    .Add<CalculateCartLinesPromotionsBlock>()
    .Add<CalculateCartLinesTaxBlock>()
    .Add<CalculateCartLinesTotalsBlock>())
    .ConfigurePipeline<ICalculateCartPipeline>(builder => builder
    .Add<CalculateCartSubTotalsBlock>()
    .Add<CalculateCartFulfillmentBlock>()
    .Add<CalculateCartPromotionsBlock>()
    .Add<CalculateCartTaxBlock>()
    // Registers the block that executes the custom pricing logic based on our new flag
    .Add<CalculateCartVIPBlock>()
    .Add<CalculateCartTotalsBlock>()
    .Add<CalculateCartPaymentsBlock>())
    .ConfigurePipeline<IAddPaymentsPipeline>(builder => builder.Add<ValidateCartHasFulfillmentBlock>().After<ValidateCartAndPaymentsBlock>()));
    return services;
    }
    /// <summary>
    /// The configure orders pipelines.
    /// </summary>
    /// <param name="services">
    /// The services.
    /// </param>
    /// <returns>
    /// The <see cref="IServiceCollection"/>.
    /// </returns>
    public static IServiceCollection ConfigureOrdersPipelines(this IServiceCollection services)
    {
    services.Sitecore().Pipelines(config => config
    .ConfigurePipeline<IItemOrderedPipeline>(builder => builder
    .Add<UpdateItemsOrderedInventoryBlock>()));
    return services;
    }
    }
    }

Build the modified commerce engine and the start solution. The default behavior is for a IIS express site to start on port 5000.  Refer to the “Test the solution” section below to validate that these steps have been followed correctly.

Step 2 : Rebuild the proxy

Commerce connect uses a service proxy to communicate with the commerce engine. We must rebuild the proxy to include the new command we just added to the engine.

Open the proxy solution “Sitecore.Commerce.ServiceProxy.sln” that is part of the SDK, it is located in the root of the SDK folder.

For example…

“C:\Development\Sitecore.Commerce.SDK.1.1.819\Sitecore.Commerce.ServiceProxy.sln”

To rebuild the proxy follow these steps

  1. Open the file named “CommerceApiClient.tt” make any small change to the file and undo it e.g. a new line at the top of the file and remove it.
  2. Save the file, you will be prompted that the code will be rebuilt. Select “OK. If you have any issues regarding the “Microsoft.OData.XXX.dll” not being found you can copy them from the Sitecore directory into the location mentioned in the error.
  3. Once the proxy has successfully been rebuilt copy the resultant assembly into your Sitecore “Bin” folder.

You will now reference this new proxy from a new pipeline handler.

Step 3 : Call the new code from commerce connect

Next, we will create a custom pipeline handler for the “AddCartLine” pipeline and register it using Sitecore configuration. Our pipeline handler will execute our custom command in the engine before each call to the to add a line.

    1. Open the source code for the reference store front, the retail demo site or your custom solution and add a new Class library project called “Sitecore.Commerce.Examples”.
    2. Add the references below from either your Sitecore Bin directory (Not recommended), nuget server or other location. Note : The proxy assembly must be the new one we built earlier.
      VIP example references
    1. Add a new class named “SetCustomFieldsByCommand” under a folder named “Pipelines\Carts”.
    2. Copy the following code into the new class…


namespace Sitecore.Commerce.Examples.Pipelines.Cart
{
using System;
using Sitecore.Commerce.Engine;
using Sitecore.Commerce.Engine.Connect;
using Sitecore.Commerce.Engine.Connect.Pipelines;
using Sitecore.Commerce.Pipelines;
using Sitecore.Commerce.Plugin.AdventureWorks.Commands;
using Sitecore.Commerce.ServiceProxy;
using Sitecore.Commerce.Services;
using Sitecore.Commerce.Services.Carts;
using Sitecore.Diagnostics;
/// <summary>
/// The set custom fields by command.
/// </summary>
public class SetCustomFieldsByCommand : PipelineProcessor<ServicePipelineArgs>
{
/// <summary>
/// The process.
/// </summary>
/// <param name="args">
/// The args.
/// </param>
public override void Process(ServicePipelineArgs args)
{
var request = args.Request as AddCartLinesRequest;
var result = args.Result as CartResult;
try
{
Assert.IsNotNull(request.Cart, "request.Cart");
Assert.IsNotNullOrEmpty(request.Cart.UserId, "request.Cart.UserId");
Assert.IsNotNull(request.Lines, "request.Lines");
// Get the custom property set in the front end code
var applyVipStatus = Convert.ToBoolean(request.Properties["applyVIPStatus"]);
var cart = request.Cart;
// Apply the headers
var container = this.GetContainer(cart.ShopName, cart.UserId, cart.CustomerId, string.Empty, string.Empty, null);
var command = Proxy.DoCommand(container.SetCartVIPStatus(cart.ExternalId, applyVipStatus));
result.HandleCommandMessages(command);
}
catch (ArgumentException exception)
{
result.Success = false;
result.SystemMessages.Add(this.CreateSystemMessage(exception));
}
catch (AggregateException exception2)
{
result.Success = false;
result.SystemMessages.Add(this.CreateSystemMessage(exception2));
}
}
/// <summary>
/// The get container.
/// </summary>
/// <param name="shopName">
/// The shop name.
/// </param>
/// <param name="userId">
/// The user id.
/// </param>
/// <param name="customerId">
/// The customer id.
/// </param>
/// <param name="language">
/// The language.
/// </param>
/// <param name="currency">
/// The currency.
/// </param>
/// <param name="effectiveDate">
/// The effective date.
/// </param>
/// <returns>
/// The <see cref="Container"/>.
/// </returns>
public Container GetContainer(
string shopName,
string userId,
string customerId = "",
string language = "",
string currency = "",
DateTime? effectiveDate = new DateTime?())
{
return EngineConnectUtility.GetShopsContainer(
string.Empty,
shopName,
userId,
customerId,
language,
currency,
effectiveDate);
}
/// <summary>
/// The create system message.
/// </summary>
/// <param name="ex">
/// The ex.
/// </param>
/// <returns>
/// The <see cref="SystemMessage"/>.
/// </returns>
public SystemMessage CreateSystemMessage(Exception ex)
{
var message = new SystemMessage
{
Message = ex.Message
};
if ((ex.InnerException != null) && !ex.Message.Equals(ex.InnerException.Message, StringComparison.OrdinalIgnoreCase))
{
message.Message = message.Message + "" + ex.InnerException.Message;
}
return message;
}
}
}

    1. Add a new xml file named “ZZZ.Sitecore.Commerce.Examples.config” to a folder named “Configs”.
    2. Copy the following markup into the new config file.


<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"&gt;
<sitecore>
<pipelines>
<commerce.carts.addCartLines>
<processor patch:before="processor[@type='Sitecore.Commerce.Engine.Connect.Pipelines.Carts.AddCartLines, Sitecore.Commerce.Engine.Connect']"
type="Sitecore.Commerce.Examples.Pipelines.Cart.SetCustomFieldsByCommand, Sitecore.Commerce.Examples">
</processor>
</commerce.carts.addCartLines>
</pipelines>
</sitecore>
</configuration>

  1. Build the new project and copy the resultant assembly into your Sitecore Bin folder.
  2. Copy the .config file into the Sitecore App_Config folder under “…\Include\Y.Commerce.Engine”. For example… “C:\inetpub\sc821u3\Website\App_Config\Include\Y.Commerce.Engine\ZZZ.Sitecore.Commerce.Examples.config”

The config file will register our new pipeline handler to run before the standard handler that adds lines to the cart. In a real word example you could call the new the command from any pipeline that suites your needs.

Step 3: Modify the front end code

The final step is to set the new field named “applyVIPPricing” on the cart header from the site implementation itself. This value will then flow through commerce connect and be set on the cart via the API. The custom logic we created earlier will then be triggered within the engine if the new field is true.

This is done using the “Properties” collection on the request.

    1. Open the reference storefront solution
    2. In the “Commerce.Storefront” project open the “CartManager” class.
    3. Find the “AddLineItemsToCart” method and replace it or merge it with following code.


/// <summary>
/// Adds the line item to cart.
/// </summary>
/// <param name="storefront">The storefront.</param>
/// <param name="visitorContext">The visitor context.</param>
/// <param name="inputModelList">The input model.</param>
/// <returns>
/// The manager response where the result is retuned indicating the success or failure of the operation.
/// </returns>
public virtual ManagerResponse<CartResult, bool> AddLineItemsToCart([NotNull] CommerceStorefront storefront, [NotNull] VisitorContext visitorContext, IEnumerable<AddCartLineInputModel> inputModelList)
{
Assert.ArgumentNotNull(inputModelList, "inputModelList");
var cartResult = this.LoadCartByName(storefront.ShopName, storefront.DefaultCartName, visitorContext.UserId, false);
if (!cartResult.Success || cartResult.Cart == null)
{
var message = StorefrontManager.GetSystemMessage(StorefrontConstants.SystemMessages.CartNotFoundError);
cartResult.SystemMessages.Add(new SystemMessage { Message = message });
return new ManagerResponse<CartResult, bool>(cartResult, cartResult.Success);
}
var lines = new List<CartLine>();
foreach (var inputModel in inputModelList)
{
Assert.ArgumentNotNullOrEmpty(inputModel.ProductId, "inputModel.ProductId");
Assert.ArgumentNotNullOrEmpty(inputModel.CatalogName, "inputModel.CatalogName");
Assert.ArgumentNotNull(inputModel.Quantity, "inputModel.Quantity");
var quantity = (uint)inputModel.Quantity;
//// Special handling for a Gift Card
if (inputModel.ProductId.Equals(storefront.GiftCardProductId, StringComparison.OrdinalIgnoreCase))
{
inputModel.VariantId = inputModel.GiftCardAmount.Value.ToString("000", CultureInfo.InvariantCulture);
}
var cartLine = new CommerceCartLine(inputModel.CatalogName, inputModel.ProductId, inputModel.VariantId == "-1" ? null : inputModel.VariantId, quantity);
cartLine.Properties["ProductUrl"] = inputModel.ProductUrl;
cartLine.Properties["ImageUrl"] = inputModel.ImageUrl;
//// UpdateStockInformation(storefront, visitorContext, cartLine, inputModel.CatalogName);
lines.Add(cartLine);
}
var cartCache = CommerceTypeLoader.CreateInstance<CartCacheHelper>();
cartCache.InvalidateCartCache(visitorContext.GetCustomerId());
var cart = cartResult.Cart as CommerceCart;
var addLinesRequest = new AddCartLinesRequest(cart, lines);
addLinesRequest.RefreshCart(true);
// Set the custom properties here, get them from a cookie or any other mechanism
addLinesRequest.Properties["applyVIPStatus"] = true;
var addLinesResult = this.CartServiceProvider.AddCartLines(addLinesRequest);
if (addLinesResult.Success && addLinesResult.Cart != null)
{
cartCache.AddCartToCache(addLinesResult.Cart as CommerceCart);
}
this.AddBasketErrorsToResult(addLinesResult.Cart as CommerceCart, addLinesResult);
Helpers.LogSystemMessages(addLinesResult.SystemMessages, addLinesResult);
return new ManagerResponse<CartResult, bool>(addLinesResult, addLinesResult.Success);
}

view raw

CartManager.cs

hosted with ❤ by GitHub

  1. Deploy the customized assembly to your Sitecore Bin folder.

The entire project is located here.

Test the example

Test the commerce engine changes first by using postman to call the new command following these steps :-

  1. Open postman
  2. Import the all the postman examples from “Postman” folder in the Commerce engine SDK folder. For example, “C:\Development\Sitecore.Commerce.SDK.1.1.819\Postman”. Refer to the documentation on the developers guide located on the dev.sitecore.net located here
  3. Import the “SetCartVIPStatus” postman sample located in the “Postman” folder of the “Sitecore.Commerce.Examples” project within the source code for this example.
  4. Execute the “Get Cart” call in the “CartAPISamples” folder. This will create a cart named “Cart01”.
  5. Execute the new “Set cart VIP status” call (Commerce examples folder). This will set the “applyVIPPricing” flag on the cart “Cart01”.
  6. Execute the “Add Mira laptop” call. This will add a line to the cart, run the new logic when the cart is calculated and create a new adjustment of 20% on the cart “Cart01”.
  7. Execute the “Get Cart” call again, you should see the new adjustment values in the response. For example…VIP postman test

Test the pipeline and front end change simply by opening the reference storefront and adding a line to the cart. If the solution is working correctly the Total for the cart should be discounted by 20%.