This content is archived. See latest version here

Last updated: Oct 21 2014

The shopping cart (or basket) is where the shopping and ordering process starts. This document outlines the main components of the shopping cart in EPiServer Commerce.

Classes referred to here are available in the following namespaces:

Key files and controls

The EPiServer 7. 5 Commerce sample website contains a number of template controls and helper classes for managing carts and the checkout procedure. Refer to the items below for more information.

  • Commerce front-end website under \wwwroot\Templates\Sample\Units\CartCheckout, for cart and checkout examples.
  • Commerce front-end website under \wwwroot\Templates\Sample\Pages for wishlist examples.
  • Cart.cs located in theMediachase.Commerce.Orders namespace, is the object containing the references to related object collections. It is a meta class, and it inherits from OrderGroup which contains a collection of OrderForm and OrderAddress objects.
  • CartHelper.cs in the Mediachase.Commerce.Website package in the Commerce tools and code sample download package. The cart helper class allows you to do common tasks with carts efficiently like add a line item to a cart or add an address to a cart (for instance shipping or billing).

Cart object

Multiple carts can be associated with a single customer. Each cart associated with a customer is named with a string value. By default, the sample EPiServer Commerce site uses the static string Cart.DefaultName property to name the default cart. The wishlist is named using the static string CartHelper.WishListName property. However, there is no reason you have to follow that convention.

Accessing and adding cart Items

A cart contains one or more OrderForminstances in the OrderForms property; a single OrderForm is usually sufficient for most sites.When a SKU is added to the cart, information from the SKU is put into a LineItem object in one of the Cart's OrderForms. CartHelper demonstrates creating a LineItem instance and adding it to a cart in the AddEntry() method.

Example: creating a LineItem, adding it to an OrderFrom, and then adding the OrderForm to a Cart

C#
//If GetCart can't find the cart, it will create it for you
Cart defaultCart = OrderContext.Current.GetCart(Cart.DefaultName, SecurityContext.Current.CurrentUserId);

//manually create an orderform and add a lineitem to it

orderForm = new OrderForm();
lineItem = new LineItem();
lineItem.CatalogEntryId = "theCodeForOurSku";

lineItem.DisplayName = "mySKUDisplayName";
orderForm.LineItems.Add(lineItem);

//add the orderform to the cart

defaultCart.OrderForms.Add(orderForm);

//now save the changes to the database

defaultCart.AcceptChanges();

Example: using CartHelper to do the same task

C#
//retrieve the default cart with the helper
            //when initialized, it adds an orderform to the cart

             helper = new CartHelper(Cart.DefaultName);

             //this method automatically transfers built-in properties (not metafield values);
             //This method automatically saves the changes to the database
             //the SKU variable is an Entry object representing a SKU

             helper.AddEntry(SKU);

Calculating totals workflow

To calculate shopping cart and order totals, ECF uses Microsoft Workflow Foundation. The workflows separates the business logic associated with calculating cart totals and validating the cart. The workflow associated with the shopping cart is CartValidate.

It is executed every time the cart is loaded in the CartView.ascx control. Because carts are persisted in the database, the availability and pricing for items can change over time. Workflows are executing the Cart.RunWorkflow() method, passing in the name of the workflow to be executed.

The CartValidate workflow addresses these issues as well as calculates totals by doing the following:

  • Determines whether the item is still available, based on the Active status and the start and end dates associated with each item. If they are not available, they are removed from the cart and an error message is returned which is displayed in the CartView.ascx.
  • Whether the items in the cart are still available (based on remaining stock in inventory, reserved inventory stock, and whether backordering is permitted). If they are not available, they are removed and an error message is returned.
  • The price for each item in the cart, based on tiered pricing. If pricing has changed, a message is returned that can be displayed to the user regarding the change.
  • Calculates the extended price for an item in the cart, for instance multiplying the price by the quantity purchased.
  • Calculates the discounts that apply to the items in the cart.

The workflow updates a number of properties of the cart to indicate various totals. These properties allow you to easily display the price for a line item, the discount amount, and the after-discount-price.

These include:

  • Cart.SubTotal is the total for the cart, after discounts are applied.
  • LineItem.ListPrice can be used to store mrsp for calculating margins in reports or the placed price.
  • LineItem.PlacedPrice is the price before the discount
  • LineItem.ExtendedPrice is the price for a lineitem, given the quantity of the item and applicable discounts.
  • LineItem.LineItemDiscountAmount isthe total amount of discount for a LineItem.

Below is an example of a workflow activity (CheckInventoryActivity.cs) which performs a check on the availability of SKUs in the cart based on their inventory status, and then removes items in the cart if they are not available.

Example: checking availability, inventory status and cart update

C#
using System;
            using System.ComponentModel;
            using System.ComponentModel.Design;
            using System.Collections;
            using System.Drawing;
            using System.Workflow.ComponentModel.Compiler;
            using System.Workflow.ComponentModel.Serialization;
            using System.Workflow.ComponentModel;
            using System.Workflow.ComponentModel.Design;
            using System.Workflow.Runtime;
            using System.Workflow.Activities;
            using System.Workflow.Activities.Rules;
            using Mediachase.Commerce.Orders;
            using Mediachase.Commerce.Marketing;
            using Mediachase.Commerce.Marketing.Objects;
            using System.Collections.Generic;
            using Mediachase.Commerce.Catalog;
            using Mediachase.Commerce.Catalog.Dto;
            using Mediachase.Commerce.Customers.Profile;
            using System.Collections.Specialized;
            using Mediachase.Commerce.Catalog.Managers;
            using System.Web.Security;
            namespace Mediachase.Commerce.Workflow.Activities.Cart
            {
            public partial class CheckInventoryActivity : Activity
            {
            public static DependencyProperty OrderGroupProperty = DependencyProperty.Register("OrderGroup", typeof(OrderGroup), typeof(CheckInventoryActivity));
            public static DependencyProperty WarningsProperty = DependencyProperty.Register("Warnings", typeof(StringDictionary), typeof(CheckInventoryActivity));
            ///
            /// Gets or sets the order group.
            ///
            /// The order group.
            [ValidationOption(ValidationOption.Required)]
            [BrowsableAttribute(true)]
            public OrderGroup OrderGroup
            {
            get
            {
            return (OrderGroup)(base.GetValue(CheckInventoryActivity.OrderGroupProperty));
            }
            set
            {
            base.SetValue(CheckInventoryActivity.OrderGroupProperty, value);
            }
            }
            ///
            /// Gets or sets the warnings.
            ///
            /// The warnings.
            [ValidationOption(ValidationOption.Required)]
            [BrowsableAttribute(true)]
            public StringDictionary Warnings
            {
            get
            {
            return (StringDictionary)(base.GetValue(CheckInventoryActivity.WarningsProperty));
            }
            set
            {
            base.SetValue(CheckInventoryActivity.WarningsProperty, value)
            }
            }


            ///
            /// Initializes a new instance of the  class.///
            public CheckInventoryActivity()
            {
            InitializeComponent();
            }

            ///
            /// Called by the workflow runtime to execute an activity.
            ///
            /// The  to associate with this  and execution.
            ///
            /// The  of the run task, which determines whether the activity remains in the executing state, or transitions to the closed state.
            ///
            protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
            {
            try
            {


            // Validate the properties at runtime
            this.ValidateRuntime();

            // Calculate order discounts
            this.ValidateItems();
            // Retun the closed status indicating that this activity is complete.
            return ActivityExecutionStatus.Closed;

            }

            catch
            {
            // An unhandled exception occured.  Throw it back to the WorkflowRuntime.

            throw;

            }

            }

            ///
            ///
             Validates the items.

            ///
            private void ValidateItems()
            {
            foreach (OrderForm form in OrderGroup.OrderForms)
            {
            if (form.Name != Mediachase.Commerce.Orders.Cart.DefaultName) //We don't need to validate quantity in the wishlist
            continue;

            foreach (LineItem lineItem in form.LineItems)
            {
            if (lineItem.CatalogEntryId != "0" && !String.IsNullOrEmpty(lineItem.CatalogEntryId) && !lineItem.CatalogEntryId.StartsWith("@")) // ignore custom entries
            {
            if (lineItem.InventoryStatus != InventoryStatus.Enabled)
            continue;
            // Check Inventory
            // item exists with appropriate quantity
            if (lineItem.InStockQuantity >= lineItem.Quantity)
            {
            continue;
            }

            else if (lineItem.InStockQuantity > 0) // there still exist items in stock
            {

            // check if we can backorder some items
            if (lineItem.AllowBackordersAndPreorders)
            {

            if (lineItem.InStockQuantity + lineItem.BackorderQuantity >= lineItem.Quantity)
            {

            continue;

            }

            else
            {
            lineItem.Quantity = lineItem.InStockQuantity + lineItem.BackorderQuantity;
            Warnings.Add("LineItemQtyChanged-" + lineItem.Id.ToString(), String.Format("Item \"{0}\" quantity has been changed, some items might be backordered.", lineItem.DisplayName));
            continue;
            }
            }

            else
            {
            lineItem.Quantity = lineItem.InStockQuantity;

            Warnings.Add("LineItemQtyChanged-" + lineItem.Id.ToString(), String.Format("Item \"{0}\" quantity has been changed.", lineItem.DisplayName));
            continue;

            }
            }

            else if (lineItem.InStockQuantity == 0)
            {
            if (lineItem.AllowBackordersAndPreorders && lineItem.PreorderQuantity > 0)
            {
            if (lineItem.PreorderQuantity >= lineItem.Quantity)
            continue;
            else
            {
            lineItem.Quantity = lineItem.PreorderQuantity;
            Warnings.Add("LineItemQtyChanged-" + lineItem.Id.ToString(), String.Format("Item \"{0}\" quantity has been changed.", lineItem.DisplayName));
            continue;
            }
            }
            else if (lineItem.AllowBackordersAndPreorders && lineItem.BackorderQuantity > 0)
            {
            if (lineItem.BackorderQuantity >= lineItem.Quantity)
            continue;
            else
            {
            lineItem.Quantity = lineItem.BackorderQuantity;
            Warnings.Add("LineItemQtyChanged-" + lineItem.Id.ToString(), String.Format("Item \"{0}\" quantity has been changed.", lineItem.DisplayName));
                    continue;
            }
            }
            }
            // Remove item if it reached this stage
            Warnings.Add("LineItemRemoved-" + lineItem.Id.ToString(), String.Format("Item \"{0}\" has been removed from the cart because it is no longer available.", lineItem.DisplayName));
            // Delete item
            lineItem.Delete();
            }
            }
            }
            }
            ///
            /// Validates the runtime.
            ///
            ///
            private bool ValidateRuntime()
            {
            // Create a new collection for storing the validation errors
            ValidationErrorCollection validationErrors = new ValidationErrorCollection();
            // Validate the Order Properties
            this.ValidateOrderProperties(validationErrors);
            // Raise an exception if we have ValidationErrors
            if (validationErrors.HasErrors)
            {
              string validationErrorsMessage = String.Empty;
              foreach (ValidationError error in validationErrors)
              {
                validationErrorsMessage +=
                string.Format("Validation Error:  Number {0} - '{1}' \n",
                error.ErrorNumber, error.ErrorText);
               }
               // Throw a new exception with the validation errors.
               throw new WorkflowValidationFailedException(validationErrorsMessage, validationErrors);
               }
             // If we made it this far, then the data must be valid.

               return true;
             }
             ///
             /// Validates the order properties.
             ///
             /// The validation errors.
             private void ValidateOrderProperties(ValidationErrorCollection validationErrors)
             {
              // Validate the To property
             if (this.OrderGroup == null)
             {
               ValidationError validationError = ValidationError.GetNotSetValidationError(CheckInventoryActivity.OrderGroupProperty.Name);
               validationErrors.Add(validationError);
              }
            }
            }
                    }

Cart persistence

Carts are always stored in the database. To save a cart, simply call the AcceptChanges() method on the Cart object. Anonymous users are managed using the ASP.NET anonymous personalization feature. This is configured in the web.config file, using the anonymousIdentification element.

This is the web.config element: <anonymousIdentification enabled="true" />

This stores a unique ID (GUID) in a cookie, which is used to provide a temporary ID for the user. The cookie lifetime is configured in the authentication element of the web.config file.

This is the config setting for the cookie lifetime:

<authentication mode="Forms">
<forms timeout="4320" loginUrl="~/Login.aspx" name="EPiServer-CMS">
</forms> </authentication>

Here, the cookie lifespan is configured for 4,320 seconds, or 3 days.The anonymous ID is used as the userId when storing a cart in the database for anonymous users.You can access the user ID (anonymous or logged in) using SecurityContext.Current.CurrentUserId.

If an anonymous user adds items and then logs in, the previous cart associated with that logged-in user will be merged. This is done in the Global.asax, Profile_MigrateAnonymous event handler.

Wishlist

The wishlist is just another cart with a different name.

Example: displaying and retrieving a wishlist cart

C#
Cart wishlistCart = OrderContext.Current.GetCart(CartHelper.WishListName, SecurityContext.Current.CurrentUserId);

Do you have feedback on this documentation? Send an email to documentation@episerver.com. For development-related questions and discussions, refer to our Forums on https://world.episerver.com/forum/