You are here: Order System > Shopping Cart

Shopping Cart

Key files and controls

All of these controls are located in the \Templates\Everything\BusinessControls\CheckoutControls folder of the sample package.

CartMiniViewModule.ascx contains an example of a simple view displaying the number of items in the shopping cart.

CartViewModule.ascx contains an example of displaying the cart line items, how to apply discount coupons, and update cart totals.

WishListViewModule.ascx contains an example of the multiple-cart support provided by EPiServer Commerce. It demonstrates how to access/update a "named" cart and how to move items between carts.

CartHelper.cs (located in the Mediachase.Commerce.Website.Helpers namespace) allows you to do common tasks with Carts efficiently like add a lineitem to a cart or add an address to a cart (e.g. shipping or billing).

Cart.cs (located in the Mediachase.Commerce.Orders namespace) is the object containing the references to related object collections. It is a meta class. It inherits from OrderGroup, which contains a collection of OrderForm and OrderAddress objects.

Overview of 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's no reason you have to follow that convention.

Accessing/adding cart items

A cart contains one or more OrderForm instances 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. Here's an example of creating a LineItem, adding it to an OrderFrom, and then adding the OrderForm to a Cart.

//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();

Here's an example of using CartHelper to do the same task:

//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);
			

Workflows/How totals are calculated

To calculate shopping cart and order totals, EPiServer Commerce 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 called "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 calculate totals by doing the following :

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 is the price before discount

LineItem.ExtendedPrice is the price for a lineitem, given the quantity of the item and applicable discounts.

LineItem.LineItemDiscountAmount is the total amount of discount for a LineItem.

Here's 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. It removes items the cart if they're not available.

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 ASP.NET's anonymous personalization feature. This is configured in the web.config file (using the anonymousIdentification element). Here 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.For more information about anonymous personalization, see http://msdn.microsoft.com/en-us/library/91ka2e6a.aspx. The cookie lifetime is configured in the authenication element of the web.config file. Here is the config setting for the cookie lifetime:

<authentication mode="Forms">
<forms timeout="4320" loginUrl="~/Login.aspx" name="Mediachase-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's 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. Here's a code example displaying retrieving a wishlist cart:

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

 


Version: EPiServer Commerce 1 R2 SP2| Last updated: 2012-06-29 | Copyright © EPiServer AB | Send feedback to us