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: To view the full code of a functional example, you can inspect and download the LearningKit project on GitHub. You can also run the LearningKit website by connecting the project to a Kentico database.

Open your MVC project in Visual Studio and:

  1. 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()
             {
             }
         }
    
    
    
     
  2. 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; }
    
         }
    
    
    
     
  3. Add new actions to the CheckoutController class 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 Kentico
                    IEnumerable<PaymentOptionInfo> enabledPaymentMethods = PaymentOptionInfoProvider.GetPaymentOptions(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 = ShippingOptionInfoProvider.GetShippingOptionInfo(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);
                }
      
      
      
        
  4. 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>
    
    
    
     
  5. 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&times; @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, Kentice sends a notification email to the email address specified in the Send order notification setting.