Integrating the shopping cart
This page is part one of the Implementing a checkout process series.
The first step towards building a robust checkout process is the implementation of a shopping cart where customers can place desired products. To facilitate shopping cart integration, Xperience provides the IShoppingService service (CMS.Ecommerce namespace) used to manage shopping cart interaction and order creation.
Managing a shopping cart
ShoppingService simplifies the implementation of all basic shopping cart related activities customers need to perform to complete the checkout process. The service manipulates the shopping cart of the current user, and enables you to create an order from all included items.
To begin, create a new controller class utilizing ShoppingService and implemet functionality needed to manage the shopping cart:
Tip: View the full code of a functional example in the LearningKit project on GitHub. You can also run the LearningKit website by connecting the project to a Xperience database. Follow the instructions in the repository’s README file.
Open your live site project in Visual Studio.
Create a new controller for the shopping cart. In this series, we will use a controller named CheckoutController to house all action methods.
Initialize an instance of IShoppingService in the controller’s constructor.
/// <summary> /// Initializes a instances of services required to manage the checkout process. /// </summary> public CheckoutController(IShoppingService shoppingService, IPaymentOptionInfoProvider paymentOption, IShippingOptionInfoProvider shippingOption, ISKUInfoProvider skuInfo, IAddressInfoProvider addressInfo, ICountryInfoProvider countryInfo, IStateInfoProvider stateInfo, IPageRetriever pageRetriever, IPageUrlRetriever pageUrlRetriever) { this.shoppingService = shoppingService; this.pageRetriever = pageRetriever; this.pageUrlRetriever = pageUrlRetriever; this.skuInfo = skuInfo; this.paymentOption = paymentOption; this.shippingOption = shippingOption; this.addressInfo = addressInfo; this.countryInfo = countryInfo; this.stateInfo = stateInfo; }
We recommend using a dependency injection container to initialize instances of used API services (e.g. IShoppingService).
The CheckoutController needs to process basic actions that customers require, namely:
- Displaying the contents of the shopping cart
- Adding a product to the shopping cart
- Updating a product’s quantity in the shopping cart
- Removing a product from the shopping cart
- Removing all products from the shopping cart
- Moving to the next step of the order creation process
Displaying the shopping cart
To display a customer’s current shopping cart, add an action method that retrieves their cart, fills a view model representation of the cart, and displays it in a related view. To retrieve the shopping cart for the current customer, use the IShoppingService.GetCurrentShoppingCart method:
/// <summary>
/// Displays the customer's current shopping cart.
/// </summary>
public ActionResult ShoppingCart()
{
// Gets the current user's shopping cart
ShoppingCartInfo currentCart = shoppingService.GetCurrentShoppingCart();
// Initializes the shopping cart model
ShoppingCartViewModel model = new ShoppingCartViewModel(currentCart);
// Displays the shopping cart
return View(model);
}
Accessing the current shopping cart
You can customize how Xperience retrieves the current shopping cart, for example:
- When the same shopping cart is used across multiple sites running under one Xperience instance
- When an older saved shopping cart overrides the current shopping cart
- What shopping cart information is removed when the shopping cart is retrieved from the database
See Retrieving the current shopping cart to learn more.
Create a view model (the example uses a ShoppingCartViewModel) to store select properties of the ShoppingCartInfo object that you want to display or otherwise require in a related view.
public class ShoppingCartViewModel
{
public IEnumerable<ShoppingCartItemViewModel> CartItems { get; set; }
public string CurrencyFormatString { get; set; }
public IEnumerable<string> CouponCodes { get; set; }
public decimal TotalTax { get; set;}
public decimal TotalShipping { get; set; }
public decimal GrandTotal { get; set; }
public decimal RemainingAmountForFreeShipping { get; set; }
public bool IsEmpty { get; set; }
/// <summary>
/// Constructor for the ShoppingCartViewModel.
/// </summary>
/// <param name="cart">A shopping cart object.</param>
public ShoppingCartViewModel(ShoppingCartInfo cart)
{
// Creates a collection containing all lines from the given shopping cart
CartItems = cart.CartProducts.Select((cartItemInfo) =>
{
return new ShoppingCartItemViewModel()
{
CartItemUnits = cartItemInfo.CartItemUnits,
SKUName = cartItemInfo.SKU.SKUName,
TotalPrice = cartItemInfo.TotalPrice,
CartItemID = cartItemInfo.CartItemID,
SKUID = cartItemInfo.SKUID,
SKUImageUrl = string.IsNullOrEmpty(cartItemInfo.SKU.SKUImagePath) ? null : new FileUrl(cartItemInfo.SKU.SKUImagePath, true)
.WithSizeConstraint(SizeConstraint.MaxWidthOrHeight(100))
.RelativePath
};
});
CurrencyFormatString = cart.Currency.CurrencyFormatString;
CouponCodes = cart.CouponCodes.AllAppliedCodes.Select(x => x.Code);
TotalTax = cart.TotalTax;
TotalShipping = cart.TotalShipping;
GrandTotal = cart.GrandTotal;
RemainingAmountForFreeShipping = cart.CalculateRemainingAmountForFreeShipping();
IsEmpty = cart.IsEmpty;
}
}
The ShoppingCartViewModel stores the following ShoppingCartInfo properties:
CartItems – A collection of items (products) contained in the shopping cart.
Note that each product is itself represented by a view model. We strongly recommend creating a separate view model for each database object you want to send to a view template. View models give you full control over the data transferred to and from your views, and can help avoid potential security vulnerabilities. Fill the model only with data you wish to display or otherwise require for the functionality of your view.
The ShoppingCartItemViewModel contains the following properties:
SKUName – full name of the product as specified during its creation.
SKUID – identifier of the product.
SKUImageUrl – link to the product’s image, provided it has one.
CartItemUnits – quantity of the item in the current shopping cart.
TotalPrice – total sum of the item given in the cart’s currency.
CartItemID – ID of the given item, used when creating an order from the shopping cart during the final step.
public class ShoppingCartItemViewModel { public string SKUName { get; set; } public int SKUID { get; set; } public string SKUImageUrl { get; set; } public int CartItemUnits { get; set; } public decimal TotalPrice { get; set; } public int CartItemID { get; set; } }
CurrencyFormatString – a string literal used to format a numeric value based on the shopping cart’s selected currency.
CouponCodes – a collection of applied coupon code string literals. See Working with coupon codes for more information about coupon codes in general.
See Adding coupon code inputs to pages to learn more about implementing coupon code support for the shopping cart.
TotalTax – stores the total tax amount that needs to be paid for products currently in the shopping cart. The total tax value is computed from tax classes assigned to particular products, product variants, and shipping options.
TotalShipping – total shipping to be paid for the products in the current shopping cart. This value is calculated based on the cart’s selected shipping option.
GrandTotal – total amount to be paid after all taxes, discounts, and gift cards have been applied.
RemainingAmountForFreeShipping – holds the remaining amount required for the order to qualify for any free shipping offers.
IsEmpty – indicates whether the shopping cart contains any products.
In the connected view, use ShoppingCartViewModel to display the shopping cart’s contents. For example:
@if (Model.IsEmpty)
{
<span>Your shopping cart is empty.</span>
}
else
{
<ul>
@* Loops through all shopping cart items. *@
@foreach (ShoppingCartItemViewModel cartItem in Model.CartItems)
{
@* Displays the shopping cart item's properties. *@
<li>
@if (cartItem.SKUImageUrl != null)
{
<img src="@cartItem.SKUImageUrl" alt="@cartItem.SKUName">
}
@cartItem.CartItemUnits× @cartItem.SKUName ... @String.Format(Model.CurrencyFormatString, cartItem.TotalPrice)
@* Allows the item to be removed. *@
@using (Html.BeginForm("RemoveItem", "Checkout", FormMethod.Post))
{
@Html.Hidden("ItemId", cartItem.CartItemID)
<input type="submit" value="Remove" />
}
</li>
}
</ul>
@* Allows all items to be removed. *@
@Html.ActionLink("Remove all products", nameof(CheckoutController.RemoveAllItems))
}
The excerpt above iterates over all products contained in the shopping cart and displays the name, quantity, and total price of each product.
Evaluating and saving the shopping cart
Methods from the default implementation of the IShoppingService interface handle shopping cart saving and evaluation automatically.
When working with objects of the CMS.Ecommerce.ShoppingCartInfo type, you may need to manually handle recalculation and saving of the shopping cart in certain cases (e.g. when extending the default functionality, or implementing a custom variant of the IShoppingService interface). Otherwise your shopping carts may display or store incorrect values.
- ShoppingCartInfo.Evaluate() – fully recalculates the shopping cart’s values and totals based on its current content and properties (discounts, taxes, shipping, etc.). You need to call the Evaluate method after you modify the shopping cart content or set any properties that could affect the calculation results.
- IShoppingService.SaveCart() – validates and saves the current user’s shopping cart object to the database. For example, call this method prior to checkout – the validation may remove items which get sold out before an order is completed.
Adding a product to the shopping cart
Adding a product to the user’s current shopping cart can be done via the IShoppingService.AddItemToCart method. The method automatically performs basic validation (valid product, quantity, etc.) on the product, inserts it into the shopping cart, and evaluates the cart. For example, this action can be triggered when a user adds an item to the shopping cart from the product catalogue or product details page.
/// <summary>
/// Adds products to the customer's current shopping cart.
/// </summary>
/// <param name="itemSkuId">ID of the added item (its SKU object).</param>
/// <param name="itemUnits">Quantity of the item to be added.</param>
[HttpPost]
public ActionResult AddItem(int itemSkuId, int itemUnits)
{
// Adds the specified number of units of a given product to the current shopping cart
shoppingService.AddItemToCart(itemSkuId, itemUnits);
// Displays the shopping cart
return RedirectToAction("ShoppingCart");
}
Updating a shopping cart item
Update of an item from the user’s current shopping cart is facilitated by the IShoppingService.UpdateItem method. The method updates the specified item’s quantity and evaluates the cart. This action can be triggered by, for example, a click on an Update button in a view displaying the shopping cart’s contents.
/// <summary>
/// Updates the quantity of an item in the customer's current shopping cart.
/// </summary>
/// <param name="itemID">ID of the shopping cart item to update.</param>
/// <param name="itemUnits">Desired quantity of the shopping cart item being updated.</param>
[HttpPost]
public ActionResult UpdateItem(int itemID, int itemUnits)
{
// Updates the quantity of a given product with the specified number of units
// If the quantity is set to zero, removes the product from the shopping cart
shoppingService.UpdateItemQuantity(itemID, itemUnits);
// Displays the shopping cart
return RedirectToAction("ShoppingCart");
}
Removing a shopping cart item
Removal of a an item from the user’s current shopping cart is handled by IShoppingServiceService.RemoveItemFromCart. When removing a bundle, the method automatically removes all products belonging to the bundle. For example, you can provide this option when displaying an overview of all items in a shopping cart.
/// <summary>
/// Removes a shopping cart item from the customer's current shopping cart.
/// </summary>
/// <param name="itemID">ID of the item to be removed.</param>
[HttpPost]
public ActionResult RemoveItem(int itemID)
{
// Removes a specified product from the shopping cart
shoppingService.RemoveItemFromCart(itemID);
// Displays the shopping cart
return RedirectToAction("ShoppingCart");
}
Removing all items from the shopping cart
Behaving similarly to the example above, IShoppingService.RemoveAllItemsFromCart removes all products from the user’s current shopping cart. For example, you can provide this option when displaying an overview of all items in a shopping cart.
/// <summary>
/// Removes all products from the customer's current shopping cart.
/// </summary>
public ActionResult RemoveAllItems()
{
// Removes all products from the current shopping cart
shoppingService.RemoveAllItemsFromCart();
// Displays the shopping cart
return RedirectToAction(nameof(CheckoutController.ShoppingCart));
}
Moving to the next step of the checkout process
Implement an action enabling users to continue with the checkout process. The shopping cart needs to be validated and saved before continuing to the next step. This ensures the shopping cart’s contents are still valid at the time the action is executed (all contained products are available for purchase, there is a sufficient quantity of them in stock, discounts and coupon codes are correctly applied, etc.):
/// <summary>
/// Validates the shopping cart and proceeds to the next checkout step.
/// </summary>
[HttpPost]
[ActionName("ShoppingCart")]
public ActionResult ShoppingCartCheckout()
{
// Gets the current user's shopping cart
ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart();
// Validates the shopping cart
var cartValidator = ShoppingCartInfoProvider.ValidateShoppingCart(cart);
// If the validation is successful, redirects to the next step of the checkout process
if (!cartValidator.Any())
{
// Saves the validated shopping cart before redirecting the checkout step
// This prevents loss of data between requests.
shoppingService.SaveCart();
return RedirectToAction("DeliveryDetails");
}
// If the validation fails, redirects back to the shopping cart
return RedirectToAction("ShoppingCart");
}
Continue with the checkout process implementation in the next part of this series: Gathering customer details during checkout.