Building the order review step
This page is part three of the Implementing a checkout process series.
In this part of the series, we look at the final step in a conventional checkout process – order review. An order review step usually details selected products and entered address, payment, and shipping details to the customer. After a customer reviews these details and finalizes the order, the page often redirects them to a summary, displaying the order ID and a thank you message. If the customer selected a suitable payment method, this might be preempted by a redirection to a payment gateway.
On this page, we will implement the part of the checkout process that:
- Displays customer information collected in the previous step alongside the contents of the customer’s shopping cart.
- Allows customers to select a payment method and complete their order.
- Displays a thank you page summarizing the order.
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 and:
Create a view model for payment methods. The model stores select properties of the PaymentOptionInfoobject type from the CMS.Ecommerce namespace.
public class PaymentMethodViewModel { [DisplayName("Payment method")] public int PaymentMethodID { get; set; } public SelectList PaymentMethods { get; set; } /// <summary> /// Creates a payment method model. /// </summary> /// <param name="paymentMethod">Selected payment method.</param> /// <param name="paymentMethods">List of all available payment methods.</param> public PaymentMethodViewModel(PaymentOptionInfo paymentMethod, SelectList paymentMethods) { PaymentMethods = paymentMethods; if (paymentMethod != null) { PaymentMethodID = paymentMethod.PaymentOptionID; } } /// <summary> /// Creates an empty payment method model. /// Required by the MVC framework for model binding during form submission. /// </summary> public PaymentMethodViewModel() { } }
Create a view model used to aggregate data collected in the previous steps. This model aggregates the DeliveryDetailsViewModel and ShoppingCartViewModel models created in the previous parts of this series. It also holds the PaymentMethodViewModel created in the previous step.
public class PreviewAndPayViewModel { public DeliveryDetailsViewModel DeliveryDetails { get; set; } public ShoppingCartViewModel Cart { get; set; } public PaymentMethodViewModel PaymentMethod { get; set; } }
Add new actions to the CheckoutControllerclass that:
Retrieve valid payment methods.
/// <summary> /// Gets all applicable payment methods assigned to the current site. /// </summary> /// <param name="cart">Shopping cart of the site</param> /// <returns>Collection of applicable payment methods</returns> private IEnumerable<PaymentOptionInfo> GetApplicablePaymentMethods(ShoppingCartInfo cart) { // Gets all enabled payment methods from Xperience IEnumerable<PaymentOptionInfo> enabledPaymentMethods = paymentOption.GetBySite(SiteContext.CurrentSiteID, true).ToList(); // Returns all applicable payment methods return enabledPaymentMethods.Where(paymentMethod => PaymentOptionInfoProvider.IsPaymentOptionApplicable(cart, paymentMethod)); }
To limit available payment methods based on the selected shipping option, see Making payment methods dependent on shipping options.
Fill the PreviewAndPayViewModel and send it to the related view template.
/// <summary> /// Display the preview checkout process step. /// </summary> public ActionResult PreviewAndPay() { // If the current shopping cart is empty, returns to the shopping cart action if (shoppingService.GetCurrentShoppingCart().IsEmpty) { return RedirectToAction("ShoppingCart"); } // Prepares a model from the preview step PreviewAndPayViewModel model = PreparePreviewViewModel(); // Displays the preview step return View(model); } /// <summary> /// Prepares a view model of the preview checkout process step including the shopping cart, /// the customer details, and the payment method. /// </summary> /// <returns>View model with information about the future order.</returns> private PreviewAndPayViewModel PreparePreviewViewModel() { // Gets the current user's shopping cart ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart(); // Prepares the customer details DeliveryDetailsViewModel deliveryDetailsModel = new DeliveryDetailsViewModel { Customer = new CustomerViewModel(shoppingService.GetCurrentCustomer()), BillingAddress = new BillingAddressViewModel(shoppingService.GetBillingAddress(), null, null), ShippingOption = new ShippingOptionViewModel() { ShippingOptionID = cart.ShippingOption.ShippingOptionID, ShippingOptionDisplayName = shippingOption.Get(cart.ShippingOption.ShippingOptionID).ShippingOptionDisplayName } }; // Prepares the payment method PaymentMethodViewModel paymentViewModel = new PaymentMethodViewModel { PaymentMethods = new SelectList(GetApplicablePaymentMethods(cart), "PaymentOptionID", "PaymentOptionDisplayName") }; // Gets the selected payment method PaymentOptionInfo paymentMethod = cart.PaymentOption; if (paymentMethod != null) { paymentViewModel.PaymentMethodID = paymentMethod.PaymentOptionID; } // Prepares a model from the preview step PreviewAndPayViewModel model = new PreviewAndPayViewModel { DeliveryDetails = deliveryDetailsModel, Cart = new ShoppingCartViewModel(cart), PaymentMethod = paymentViewModel }; return model; } /// <summary> /// Decides whether the specified payment method is valid on the current site. /// </summary> /// <param name="paymentMethodID">ID of the applied payment method.</param> /// <returns>True if the payment method is valid.</returns> private bool IsPaymentMethodValid(int paymentMethodID) { // Gets the current shopping cart ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart(); // Gets a list of all applicable payment methods to the current user's shopping cart List<PaymentOptionInfo> paymentMethods = GetApplicablePaymentMethods(cart).ToList(); // Returns whether an applicable payment method exists with the entered payment method's ID return paymentMethods.Exists(p => p.PaymentOptionID == paymentMethodID); }
Add an action to the controller that processes the order. If the processing is successful, the action displays a payment page.
/// <summary> /// Validates all collected information, creates an order, /// and redirects the customer to payment. /// </summary> /// <param name="model">View model with information about the future order.</param> [HttpPost] public ActionResult PreviewAndPay(PreviewAndPayViewModel model) { // Gets the current shopping cart ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart(); // Validates the shopping cart var validationErrors = ShoppingCartInfoProvider.ValidateShoppingCart(cart); // Gets the selected payment method, assigns it to the shopping cart, and evaluates the cart shoppingService.SetPaymentOption(model.PaymentMethod.PaymentMethodID); // If the validation was not successful, displays the preview step again if (validationErrors.Any() || !IsPaymentMethodValid(model.PaymentMethod.PaymentMethodID)) { // Prepares a view model from the order review step PreviewAndPayViewModel viewModel = PreparePreviewViewModel(); // Displays the order review step again return View("PreviewAndPay", viewModel); } // Creates an order from the current shopping cart // If the order was created successfully, empties the cart OrderInfo order = shoppingService.CreateOrder(); // Redirects to the payment gateway return RedirectToAction("Index", "Payment", new { orderID = order.OrderID }); }
If you provide only manual payment methods (and therefore do not require connections to any payment gateways), you can redirect your customers directly to a thank-you page. For example:
/// <summary> /// Displays a thank-you page where user is redirected after creating an order. /// </summary> /// <param name="orderID">ID of the created order.</param> public ActionResult ThankYou(int orderID = 0) { ViewBag.OrderID = orderID; return View(); }
The related view template may, for example, display a short ‘thank you’ message and an order summary:
@{ Layout = "~/Views/Shared/_Layout.cshtml"; ViewBag.Title = "Thank-you page"; } <h2>Thank-you page</h2> <p>Order number: @ViewBag.OrderID</p> <p>Your order was succesfully created. Thank you for your patronage!</p>
Create a view for the order review step. For example:
@* Displays the customer details. *@ <div id="customerDetails"> <h2>Customer Details</h2> <div> @Html.LabelFor(m => m.DeliveryDetails.Customer.FirstName): @Html.DisplayFor(m => m.DeliveryDetails.Customer.FirstName) </div> <div> @Html.LabelFor(m => m.DeliveryDetails.Customer.LastName): @Html.DisplayFor(m => m.DeliveryDetails.Customer.LastName) </div> <div> @Html.LabelFor(m => m.DeliveryDetails.Customer.Company): @if (!(String.IsNullOrEmpty((string)Model.DeliveryDetails.Customer.Company))) { Html.DisplayFor(m => m.DeliveryDetails.Customer.Company); } else { Html.Raw("none"); } </div> </div> @* Lists all products from the shopping cart. *@ <div id="cartContent"> <h3>Ordered products</h3> <ul> @* Loops through all shopping cart items. *@ @foreach (ShoppingCartItemViewModel cartItem in Model.Cart.CartItems) { @* Displays the shopping cart item's properties. *@ <li> @cartItem.CartItemUnits× @cartItem.SKUName ... @String.Format(Model.Cart.CurrencyFormatString, cartItem.TotalPrice) </li> } </ul> </div> @* Displays additional order details. *@ <div id="shoppingCartTotals"> <p>Selected shipping method: @Model.DeliveryDetails.ShippingOption.ShippingOptionDisplayName</p> <p>Total tax: @String.Format(Model.Cart.CurrencyFormatString, Model.Cart.TotalTax)</p> <p>Total shipping: @String.Format(Model.Cart.CurrencyFormatString, Model.Cart.TotalShipping)</p> <p>Total (incl. tax): @String.Format(Model.Cart.CurrencyFormatString, Model.Cart.GrandTotal)</p> </div> @* Invokes a POST action finalizing order creation. *@ @using (Html.BeginForm("PreviewAndPay", "Checkout", FormMethod.Post)) { <div id="paymentMethod"> @Html.LabelFor(m => m.PaymentMethod.PaymentMethodID): @Html.DropDownListFor(m => m.PaymentMethod.PaymentMethodID, Model.PaymentMethod.PaymentMethods) </div> <input type="submit" value="Create an order" /> }
Customers are now able to select their preferred payment method, and review and confirm their order. When a customer creates an order, Xperience sends a notification email to the email address specified in the Send order notification setting.