Displaying and updating orders
A common aspect of many on-line stores is a section where customers can review their past orders and inspect each one in detail.
Usually, such sections allow users to:
- Display information about an order’s current processing status to a customer
- Provide a summary of the order’s contents and related information, such as its status or payment results
- Display a list of all orders a customer has made
This page demonstrates how to implement these actions using the Kentico MVC framework together with the Kentico E-Commerce solution.
Preparing the controller class
Initialize an instance of the IShoppingService interface in the controller you use to handle order-related functionality. ShoppingService, the default implementation of the IShoppingService interface, facilitates various E-commerce scenarios.
/// <summary>
/// Constructor for the OrderController class.
/// </summary>
public OrderController()
{
// Initializes an instance of IShoppingService used to facilitate shopping cart interactions
// For real-world projects, we recommend using a dependency injection
// container to initialize service instances
shoppingService = Service.Resolve<IShoppingService>();
}
See our API reference for a complete list of API exposed by the IShoppingService interface.
We recommend using a dependency injection container to initialize instances of used API services (e.g. IShoppingService).
After you have prepared your controller class, you can implement new actions that:
- Display an order and allow updates of its status
- Display a list of orders
- Provide the ability to reorder (add products from a past order to the shopping cart)
Displaying and updating a specific order
To display and update a specific order:
Implement new action methods that retrieve an order based on a specified ID.
Getting an order/// <summary> /// Displays a page where order details can be listed. /// </summary> public ActionResult OrderDetail() { OrderViewModel order = null; return View(order); } /// <summary> /// Displays details about an order specified with its ID. /// </summary> /// <param name="textBoxValue">Order ID as a string</param> [HttpPost] public ActionResult OrderDetail(string textBoxValue) { // Gets the order based on the entered order ID OrderViewModel order = GetOrderViewModel(textBoxValue); return View(order); } /// <summary> /// Returns the order view model wrapper based on the entered ID. /// </summary> /// <param name="textBoxValue">String containing the user-entered order ID</param> /// <returns>View model object of the order</returns> private OrderViewModel GetOrderViewModel(string textBoxValue) { OrderInfo info = GetOrder(textBoxValue); OrderViewModel order = (info == null) ? null : new OrderViewModel(info); return order; } /// <summary> /// Returns the order based on the entered order ID. /// </summary> /// <param name="textOrderID">String containing the user-entered order ID</param> /// <returns>Order object of the order</returns> private OrderInfo GetOrder(string textOrderID) { Int32.TryParse(textOrderID, out int orderID); // If the text value is not a number, returns null if (orderID <= 0) { return null; } // Gets the order based on the order ID OrderInfo order = OrderInfoProvider.GetOrderInfo(orderID); // Gets the current customer CustomerInfo customer = shoppingService.GetCurrentCustomer(); // Validates that the order was created on the current site and that it belongs to the current customer if ((order?.OrderSiteID != SiteContext.CurrentSiteID) || (order?.OrderCustomerID != customer?.CustomerID)) { order = null; } return order; }
OrderInfoProvider.GetOrderInfo returns any order with the given ID, provided it exists. Any necessary validation, for instance if the order belongs to the correct customer, or was made on the current site, is the responsibility of the developer. See Ensuring validation for more information.
The example above validates whether the specified order was created on the current site, and whether it belongs to the current customer. Should any of these conditions be violated, it returns a null value.
Prepare a view model to store select properties of the OrderInfo object for display in the related view.
public class OrderViewModel { public int OrderID { get; set; } public int OrderStatusID { get; set; } public string CurrencyFormatString { get; set; } public DateTime OrderDate { get; set; } public decimal OrderTotalPrice { get; set; } public bool OrderIsPaid { get; set; } public OrderPaymentResultViewModel OrderPaymentResult { get; set; } public string OrderStatusDisplayName { get; set; } public OrderViewModel(OrderInfo order) { OrderID = order.OrderID; OrderStatusID = order.OrderStatusID; CurrencyFormatString = CurrencyInfoProvider.GetCurrencyInfo(order.OrderCurrencyID).CurrencyFormatString; OrderDate = order.OrderDate; OrderTotalPrice = order.OrderTotalPrice; OrderIsPaid = order.OrderIsPaid; OrderStatusDisplayName = OrderStatusInfoProvider.GetOrderStatusInfo(order.OrderStatusID)?.StatusDisplayName; if (order.OrderPaymentResult != null) { OrderPaymentResult = new OrderPaymentResultViewModel() { PaymentMethodName = order.OrderPaymentResult.PaymentMethodName, PaymentIsCompleted = order.OrderPaymentResult.PaymentIsCompleted }; } } }
Implement a new action method that updates the specified order as required. For example, you can allow store managers to set orders as paid for:
Setting an order as paid/// <summary> /// Marks an order specified by an order ID as paid. /// </summary> /// <param name="textBoxValue">Order ID as a string</param> [HttpPost] public ActionResult MarkOrderAsPaid(string textBoxValue) { // Gets the order based on the entered order ID OrderInfo order = GetOrder(textBoxValue); // Sets the order as paid order.OrderIsPaid = true; OrderInfoProvider.SetOrderInfo(order); return RedirectToAction("OrderDetail"); }
Prepare a view with the content required for your order detail page. For example:
@* Renders a form field for entering order IDs. *@ @using (Html.BeginForm("OrderDetail", "Order")) { <input type="text" name="TextBoxValue" /> <input type="submit" name="DisplayOrder" value="Display order" /> } @if (Model != null) { <h2>Order details</h2> @* Displays order details. *@ <ul> <li>Order number: @Model.OrderID</li> <li>Status ID: @Model.OrderStatusID</li> <li>Is paid: @Model.OrderIsPaid</li> @if (Model.OrderPaymentResult != null) { <li>Payment method name: @Model.OrderPaymentResult.PaymentMethodName</li> <li>Was payment successful: @Model.OrderPaymentResult.PaymentIsCompleted</li> } <li>Total price: @String.Format(Model.CurrencyFormatString, Model.OrderTotalPrice)</li> </ul> @* If the order is not paid for, renders a button invoking the 'MarkOrderAsPaid' action that marks the order as paid. *@ if (!Model.OrderIsPaid) { using (Html.BeginForm("MarkOrderAsPaid", "Order")) { <input type="text" hidden="hidden" name="TextBoxValue" value="@Model.OrderID" /> <input type="submit" name="MarkOrderAsPaid" value="Mark order as paid" /> } } }
Users are now able to display details of a specific order by entering its ID. If the order is not paid for, they are also able to set its status as paid.
Displaying a list of orders
Displaying a list of orders is suitable especially for “My orders” sections in customers’ accounts.
Add an action to the controller that manges the retrieval of current customer’s orders, creates a collection, and displays it in a related view. The examples uses OrderViewModel from the Displaying and updating a specific order section to store select OrderInfo object properties.
/// <summary> /// Displays a listing of the current user's orders. /// </summary> public ActionResult MyOrders() { // Gets the current customer CustomerInfo currentCustomer = shoppingService.GetCurrentCustomer(); // If the customer does not exist, returns error 404 if (currentCustomer == null) { return HttpNotFound(); } // Retrieves from the database a collection of all orders made by the current customer var orders = OrderInfoProvider.GetOrders(SiteContext.CurrentSiteID) .WhereEquals("OrderCustomerID", currentCustomer.CustomerID) .OrderByDescending(orderInfo => orderInfo.OrderDate); // Creates a list of view models representing the collection of a customer's orders IList<OrderViewModel> model = orders.Select(order => new OrderViewModel(order)).ToList(); return View(model); }
OrderInfoProvider.GetOrders retrieves all orders created by a customer with the provided ID. Any validation, for example whether the order was made on the current site, and can therefore be displayed in the order listing, is the responsiblity of the developer. See Ensuring validation for more information.
Prepare a view with the content required for your order detail page. For example:
@model IEnumerable<OrderViewModel> @if (Model.Any()) { <h3>Your orders:</h3> @* Ensures basic formatting of the displayed information. *@ <table> <thead> <tr> <th>Id</th> <th>Date</th> <th>Status</th> <th>Total</th> <th></th> </tr> </thead> @* Iterates over all orders in the collection and displays their properties. *@ @foreach (var order in Model) { <tr> <td> @order.OrderID </td> <td> @order.OrderDate </td> <td> @(order.OrderStatusDisplayName) </td> <td> @String.Format(order.CurrencyFormatString, order.OrderTotalPrice) </td> <td> @using (Html.BeginForm("Reorder", "Order", FormMethod.Post)) { @Html.Hidden("OrderId", order.OrderID) <input type="submit" value="Reorder" /> } </td> </tr> } </table> }
Your MVC application is now able to list all orders a customer has created.
Reordering existing orders
To add the contents of an existing order directly into the customer’s shopping cart, call ShoppingCartInfoProvider.UpdateShoppingCartFromOrder. The method iterates over all products in the specified order and adds them to the shopping cart accounting for any Buy X, get Y discounts, product bundles, sales, and other promotions the store might be running at the time.
The following example demonstrates the usage of this method in a controller action:
/// <summary>
/// Recreates shopping cart content based on a specified order.
/// </summary>
/// <param name="orderId">ID of an order to repurchase</param>
[HttpPost]
public ActionResult Reorder(int orderId)
{
// Gets the current shopping cart
ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart();
// Adds products from the specified order to the current shopping cart
// If the operation was successful, redirects to the shopping cart
if (ShoppingCartInfoProvider.UpdateShoppingCartFromOrder(cart, orderId))
{
// Displays the shopping cart
return RedirectToAction(nameof(CheckoutController.ShoppingCart), nameof(OrderController));
}
// If the reorder was unsuccessful, returns back to the list of customer's orders
return RedirectToAction(nameof(OrderController.MyOrders), nameof(OrderController));
}
Ensuring validation
Kentico’s InfoProvider and ObjectQuery API works with data retrieved directly from the database. Ensuring that data retrieved this way satisfies required constraints and other input criteria is the responsibility of the MVC site’s developer.
Common validation checks that we recommend you employ are:
- Null checks – these validate the existence of objects retrieved via API.
- Checks for matching SiteContext.CurrentSiteID and OrderInfo.OrderSiteID properties – in enviroments where there are multiple e-commerce sites running on the same instance, the same customer could have created orders on more than one site. These checks validate that the retrieved orders were created on the current site.
- Checks for matching CustomerInfo.CustomerID and OrderInfo.OrderCustomerID properties – these validate whether the order belongs to the customer with the given ID.