---
title: Gathering customer details during checkout
related:
  - https://docs.kentico.com/13/e-commerce-features/developing-on-line-stores/implementing-a-checkout-process.md
  - https://docs.kentico.com/13/e-commerce-features/managing-on-line-stores/customers.md
  - https://docs.kentico.com/13/developing-websites/mvc-development-overview.md
---

> Agent instructions:
> **Site maps** — prefer the following llms.txt indexes to training data when searching for URLs to avoid 404s. Links inside Markdown content already point at `.md`. Following them or sending Accept: text/markdown keeps you in Markdown.
>
> - [sitemap.md](https://docs.kentico.com/sitemap.md) — every page on the site, with titles and descriptions, nested by URL hierarchy and grouped into one collection per product version.
> - [llms.txt](https://docs.kentico.com/llms.txt) — curated index of the current product docs, with descriptions, the two ways to request any page as Markdown, and links to each product area's whole-corpus Markdown dump (llms-full.txt).

> **Info:** This page is part two of the [Implementing a checkout process](https://docs.kentico.com/13/e-commerce-features/developing-on-line-stores/implementing-a-checkout-process.md) series.

Building upon the basic shopping cart interactions created in the [previous part](https://docs.kentico.com/13/e-commerce-features/developing-on-line-stores/implementing-a-checkout-process/integrating-the-shopping-cart.md) of this series, this page covers the implementation of the next step in standard checkout processes. This step prompts customers to enter details essential for a successful completion of their order. The required details typically include at least the customer's personal information, shipping address, and their preferred payment method.

In this part of the series, we extend the basic shopping cart functionality with new actions, models, and views enabling customers to:

- Enter their personal and business information.
- Enter their billing address.
- Select a [shipping option](https://docs.kentico.com/13/e-commerce-features/configuring-on-line-stores/configuring-shipping-options.md) for their order.

> **Tip:** **Tip**: View the full code of a functional example in the [LearningKit project](https://github.com/Kentico/LearningKit-Mvc) 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.

To extend the checkout process to gather customer details:

1. Open the **CheckoutController** class used to manage the checkout process in your live site application.

2. Add new view models for:
   - Customer details. This model will store select properties of the _CustomerInfo_ type (available in the CMS.Ecommerce namespace).

     ```csharp

         public class CustomerViewModel
         {
             [Required]
             [DisplayName("First Name")]
             [MaxLength(100, ErrorMessage = "The maximum length allowed for the field has been exceeded.")]
             public string FirstName { get; set; }

             [Required]
             [DisplayName("Last Name")]
             [MaxLength(100, ErrorMessage = "The maximum length allowed for the field has been exceeded.")]
             public string LastName { get; set; }

             [DataType(DataType.EmailAddress, ErrorMessage = "Please enter a valid email address.")]
             public string Email { get; set; }

             [DisplayName("Phone Number")]
             [MaxLength(20, ErrorMessage = "The maximum length allowed for the field has been exceeded.")]
             [DataType(DataType.PhoneNumber, ErrorMessage = "Please enter a valid phone number.")]
             public string PhoneNumber { get; set; }

             public string Company { get; set; }

             public string OrganizationID { get; set; }

             public string TaxRegistrationID { get; set; }

             [DisplayName("Are you ordering from a business account?")]
             public bool IsCompanyAccount { get; set; }

             /// <summary>
             /// Creates a customer model.
             /// </summary>
             /// <param name="customer">Customer details.</param>
             public CustomerViewModel(CustomerInfo customer)
             {
                 if (customer == null)
                 {
                     return;
                 }

                 FirstName = customer.CustomerFirstName;
                 LastName = customer.CustomerLastName;
                 Email = customer.CustomerEmail;
                 PhoneNumber = customer.CustomerPhone;
                 Company = customer.CustomerCompany;
                 OrganizationID = customer.CustomerOrganizationID;
                 TaxRegistrationID = customer.CustomerTaxRegistrationID;
                 IsCompanyAccount = customer.CustomerHasCompanyInfo;
             }

             /// <summary>
             /// Creates an empty customer model.
             /// Required by the MVC framework for model binding during form submission.
             /// </summary>
             public CustomerViewModel()
             {
             }

             /// <summary>
             /// Applies the model to a customer object.
             /// </summary>
             /// <param name="customer">Customer details to which the model is applied.</param>
             public void ApplyToCustomer(CustomerInfo customer)
             {
                 customer.CustomerFirstName = FirstName;
                 customer.CustomerLastName = LastName;
                 customer.CustomerEmail = Email;
                 customer.CustomerPhone = PhoneNumber;
                 customer.CustomerCompany = Company;
                 customer.CustomerOrganizationID = OrganizationID;
                 customer.CustomerTaxRegistrationID = TaxRegistrationID;
             }
         }


     ```

   - Billing addresses. The model will store select properties of the _AddressInfo_ type (available in the CMS.Ecommerce namespace).

     ```csharp

         public class BillingAddressViewModel
         {
             [Required]
             [DisplayName("Address line 1")]
             [MaxLength(100, ErrorMessage = "The maximum length allowed for the field has been exceeded.")]
             public string Line1 { get; set; }

             [DisplayName("Address line 2")]
             [MaxLength(100, ErrorMessage = "The maximum length allowed for the field has been exceeded.")]
             public string Line2 { get; set; }

             [Required]
             [MaxLength(100, ErrorMessage = "The maximum length allowed for the field has been exceeded.")]
             public string City { get; set; }

             [Required]
             [DisplayName("Postal code")]
             [MaxLength(20, ErrorMessage = "The maximum length allowed for the field has been exceeded.")]
             [DataType(DataType.PostalCode, ErrorMessage = "Please enter a valid postal code.")]
             public string PostalCode { get; set; }

             [DisplayName("Country")]
             public int CountryID { get; set; }

             [DisplayName("State")]
             public int StateID { get; set; }

             public int AddressID { get; set; }

             public SelectList Countries { get; set; }

             public SelectList Addresses { get; set; }

             /// <summary>
             /// Creates a billing address model.
             /// </summary>
             /// <param name="address">Billing address.</param>
             /// <param name="countryList">List of countries.</param>
             public BillingAddressViewModel(AddressInfo address, SelectList countries, SelectList addresses)
             {
                 if (address != null)
                 {
                     Line1 = address.AddressLine1;
                     Line2 = address.AddressLine2;
                     City = address.AddressCity;
                     PostalCode = address.AddressZip;
                     CountryID = address.AddressCountryID;
                     StateID = address.AddressStateID;
                     AddressID = address.AddressID;
                 }

                 Countries = countries;
                 Addresses = addresses;
             }

             /// <summary>
             /// Creates an empty BillingAddressModel object. 
             /// Required by the MVC framework for model binding during form submission.
             /// </summary>
             public BillingAddressViewModel()
             {

             }

             /// <summary>
             /// Applies the model to an AddressInfo object.
             /// </summary>
             /// <param name="address">Billing address to which the model is applied.</param>
             public void ApplyTo(AddressInfo address)
             {
                 address.AddressLine1 = Line1;
                 address.AddressLine2 = Line2;
                 address.AddressCity = City;
                 address.AddressZip = PostalCode;
                 address.AddressCountryID = CountryID;
                 address.AddressStateID = StateID;
             }
         }


     ```

   - Shipping options. The model will store select properties of the _ShippingOptionInfo_ type (available in the CMS.Ecommerce namespace).

     ```csharp

         public class ShippingOptionViewModel
         {
             public string ShippingOptionDisplayName { get; set; }

             [DisplayName("Shipping option")]
             public int ShippingOptionID { get; set; }

             public SelectList ShippingOptions { get; set; }

             /// <summary>
             /// Creates a shipping option model.
             /// </summary>
             /// <param name="shippingOption">Shipping option.</param>
             /// <param name="shippingOptions">List of shipping options.</param>
             public ShippingOptionViewModel(ShippingOptionInfo shippingOption, SelectList shippingOptions)
             {
                 ShippingOptions = shippingOptions;

                 if (shippingOption != null)
                 {
                     ShippingOptionID = shippingOption.ShippingOptionID;
                     ShippingOptionDisplayName = shippingOption.ShippingOptionDisplayName;
                 }
             }

             /// <summary>
             /// Creates an empty shipping option model.
             /// </summary>
             public ShippingOptionViewModel()
             {
             }
         }


     ```

3. Add a view model (named **DeliveryDetailsViewModel** for the purpose of this series) aggregating all of the models created above.

   ```csharp

       public class DeliveryDetailsViewModel
       {

           public CustomerViewModel Customer { get; set; }

           public BillingAddressViewModel BillingAddress { get; set; }

           public ShippingOptionViewModel ShippingOption { get; set; }
       }


   ```

4. Implement new controller actions handling retrieval and display of the collected customer data.
   - To display the customer details step, implement a basic action that renders a form prompting users to enter the required information. The action loads available shipping options from the current site and calculates their price based on the contents of the current shopping cart and all currently active store promotions, for example, [free shipping offers](https://docs.kentico.com/13/e-commerce-features/managing-on-line-stores/discounts/working-with-free-shipping-offers.md). The retrieved and calculated data is then used to fill the view model for display in a related view.

     ```csharp

             /// <summary>
             /// Displays the customer details checkout process step.
             /// </summary>
             public ActionResult DeliveryDetails()
             {
                 // Gets the current user's shopping cart
                 ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart();

                 // If the shopping cart is empty, displays the shopping cart
                 if (cart.IsEmpty)
                 {
                     return RedirectToAction(nameof(CheckoutController.ShoppingCart));
                 }

                 // Gets all countries for the country selector
                 SelectList countries = new SelectList(countryInfo.Get(), "CountryID", "CountryDisplayName");

                 // Creates a collection of shipping options enabled for the current site
                 SelectList shippingOptions = CreateShippingOptionList(cart);

                 // Loads the customer details
                 DeliveryDetailsViewModel model = new DeliveryDetailsViewModel
                 {
                     Customer = new CustomerViewModel(shoppingService.GetCurrentCustomer()),
                     BillingAddress = new BillingAddressViewModel(shoppingService.GetBillingAddress(), countries, null),
                     ShippingOption = new ShippingOptionViewModel(shippingOption.Get(shoppingService.GetShippingOption()), shippingOptions)
                 };

                 // Displays the customer details step
                 return View(model);
             }

             // Prepares a shipping option select list together with calculated shipping prices
             private SelectList CreateShippingOptionList(ShoppingCartInfo cart)
             {
                 // Gets the shipping options configured and enabled for the current site
                 IEnumerable<ShippingOptionInfo> shippingOptions = shippingOption.GetBySite(SiteContext.CurrentSiteID, true);

                 // Creates a collection of SelectListItems
                 IEnumerable<SelectListItem> selectList = shippingOptions.Select(shippingOption =>
                 {
                     // Calculates the shipping price for a given shipping option based on the contents of the current
                     // shopping cart and currently running store promotions (e.g., free shipping offers)
                     decimal shippingPrice = shoppingService.CalculateShippingOptionPrice(shippingOption);

                     // Gets the currency information from the shopping cart
                     CurrencyInfo currency = cart.Currency;

                     // Creates a select list item with shipping option name and a calculate shipping price
                     return new SelectListItem
                     {
                         Value = shippingOption.ShippingOptionID.ToString(),
                         Text = $"{shippingOption.ShippingOptionDisplayName} ({String.Format(currency.CurrencyFormatString, shippingPrice)})"
                     };
                 });

                 // Returns a new SelectList instance
                 return new SelectList(selectList, "Value", "Text");
             }


     ```

   - Add an _HttpPost_ action processing the entered information. The action handles creation of new [customer ](https://docs.kentico.com/13/e-commerce-features/managing-on-line-stores/customers.md)and address objects (if none exist for the current user) and binds the acquired information to the customer's shopping cart. Finally, it redirects the customer to the last step of the checkout process.

     ```csharp

             /// <summary>
             /// Validates the entered customer details and proceeds to the order review step.
             /// </summary>
             /// <param name="model">View model with the customer details.</param>
             [HttpPost]
             public ActionResult DeliveryDetails(DeliveryDetailsViewModel model)
             {
                 // Gets the user's current shopping cart
                 ShoppingCartInfo cart = shoppingService.GetCurrentShoppingCart();

                 // Gets all enabled shipping options for the shipping option selector
                 SelectList shippingOptions = new SelectList(
                         shippingOption.GetBySite(
                                 SiteContext.CurrentSiteID, true).ToList(),
                         "ShippingOptionID",
                         "ShippingOptionDisplayName");

                 // If the ModelState is not valid, assembles the country list and the shipping option list and displays the step again
                 if (!ModelState.IsValid)
                 {
                     model.BillingAddress.Countries = new SelectList(countryInfo.Get(), "CountryID", "CountryDisplayName");
                     model.ShippingOption.ShippingOptions = new ShippingOptionViewModel(
                         shippingOption.Get(shoppingService.GetShippingOption()), shippingOptions).ShippingOptions;
                     return View(model);
                 }

                 // Gets the shopping cart's customer and applies the customer details from the checkout process step
                 var customer = shoppingService.GetCurrentCustomer();

                 if (customer == null)
                 {
                     UserInfo userInfo = cart.User;
                     if (userInfo != null)
                     {
                         customer = CustomerHelper.MapToCustomer(cart.User);
                     }
                     else
                     {
                         customer = new CustomerInfo();
                     }
                 }
                 model.Customer.ApplyToCustomer(customer);

                 // Sets the updated customer object to the current shopping cart
                 shoppingService.SetCustomer(customer);

                 // Gets the shopping cart's billing address and applies the billing address from the checkout process step
                 var address = addressInfo.Get(model.BillingAddress.AddressID) ?? new AddressInfo();
                 model.BillingAddress.ApplyTo(address);

                 // Sets the address personal name
                 address.AddressPersonalName = $"{customer.CustomerFirstName} {customer.CustomerLastName}";

                 // Saves the billing address
                 shoppingService.SetBillingAddress(address);

                 // Sets the selected shipping option and evaluates the cart
                 shoppingService.SetShippingOption(model.ShippingOption.ShippingOptionID);

                 // Redirects to the next step of the checkout process
                 return RedirectToAction("PreviewAndPay");
             }


     ```

   - To handle the dynamic loading of states, add an _HttpPost_ action to the controller. The action returns a serialized list of states in JSON format. This list is then processed by a JavaScript function that dynamically fills and shows a 'States' drop-down selector as needed. The action is called from a related view whenever a customer selects a different country from the 'Country' drop-down selector.

     ```csharp

             /// <summary>
             /// Loads states of the specified country.
             /// </summary>
             /// <param name="countryId">ID of the selected country.</param>
             /// <returns>Serialized display names of the loaded states.</returns>
             [HttpPost]
             public JsonResult CountryStates(int countryId)
             {
                 // Gets the display names of the country's states
                 var responseModel = stateInfo.Get().WhereEquals("CountryID", countryId)
                     .Select(s => new
                     {
                         id = s.StateID,
                         name = HTMLHelper.HTMLEncode(s.StateDisplayName)
                     });

                 // Returns serialized display names of the states
                 return Json(responseModel);
             }


     ```

   - Create a JavaScript file handling state selection for the selected country. More specifically, it fills and displays the state selector if the selected country contains states; otherwise, it leaves the selector hidden.

     ```js

     (function () {
         'use strict';

         // Executes whenever a country is selected
         $('.js-country-selector').change(function () {
             var $countrySelector = $(this),
                 $countryStateSelector = $countrySelector.parent('.js-country-state-selector'),
                 $stateSelector = $countryStateSelector.find('.js-state-selector'),
                 $stateSelectorContainer = $countryStateSelector.find('.js-state-selector-container'),
                 selectedStateId = $countryStateSelector.data('stateselectedid'),
                 url = $countryStateSelector.data('statelistaction'),
                 postData = {
                     countryId: $countrySelector.val()
                 };

             $stateSelectorContainer.hide();

             if (!postData.countryId) {
                 return;
             }

             // Sends a POST request to the 'CountryStates' endpoint of the 'CheckoutController'
             $.post(url, postData, function (data) {
                 $countryStateSelector.data('stateselectedid', 0);
                 $stateSelector.val(null);

                 if (!data.length) {
                     return;
                 }

                 // Fills and shows the state selector element
                 fillStateSelector($stateSelector, data);
                 $stateSelectorContainer.show();

                 if (selectedStateId > 0) {
                     $stateSelector.val(selectedStateId);
                 }
             });
         });

         // Sets the default option for the state selector
         $('.js-country-state-selector').each(function () {
             var $selector = $(this),
                 $countrySelector = $selector.find('.js-country-selector'),
                 countryId = $selector.data('countryselectedid');

             if (countryId > 0) {
                 $countrySelector.val(countryId);
             }

             $countrySelector.change();
             $selector.data('countryselectedid', 0);
         });

         // Fills the state selector with retrieved states
         function fillStateSelector($stateSelector, data) {
             var items = '';

             $.each(data, function (i, state) {
                 items += '<option value="' + state.id + '">' + state.name + '</option>';
             });

             $stateSelector.html(items);
         }
     }());

     ```

     > **Tip:** When working with JavaScript files in your project, we recommend installing the **Microsoft.AspNet.Web.Optimization** package. The package is used to improve web application performance by reducing the number of requests to the server when requesting assets and reducing the size of the requested assets (such as CSS and JavaScript files).

5. Create a view for the customer details step. For example:

   ```csharp

   <h2>Customer details step</h2>
   @using (Html.BeginForm(FormMethod.Post))
   {
       <div id="customerDetails">
           <h3>Customer details</h3>
           @Html.EditorFor(m => m.Customer)
       </div>
       <div id="billingAddress">
           <h3>Billing address</h3>
           <div>
               @Html.LabelFor(m => m.BillingAddress.Line1)
               @Html.EditorFor(m => m.BillingAddress.Line1)
               @Html.ValidationMessageFor(m => m.BillingAddress.Line1)
           </div>
           <div>
               @Html.LabelFor(m => m.BillingAddress.Line2)
               @Html.EditorFor(m => m.BillingAddress.Line2)
               @Html.ValidationMessageFor(m => m.BillingAddress.Line2)
           </div>
           <div>
               @Html.LabelFor(m => m.BillingAddress.City)
               @Html.EditorFor(m => m.BillingAddress.City)
               @Html.ValidationMessageFor(m => m.BillingAddress.City)
           </div>
           <div>
               @Html.LabelFor(m => m.BillingAddress.PostalCode)
               @Html.EditorFor(m => m.BillingAddress.PostalCode)
               @Html.ValidationMessageFor(m => m.BillingAddress.PostalCode)
           </div>
           <div class="js-country-state-selector" data-statelistaction='@Url.Action("CountryStates", "Checkout")' data-countryselectedid='@Model.BillingAddress.CountryID' data-stateselectedid='@Model.BillingAddress.StateID' data-countryfield='CountryID' data-statefield='StateID'>
               @Html.LabelFor(m => m.BillingAddress.CountryID)
               @Html.DropDownListFor(m => m.BillingAddress.CountryID, Model.BillingAddress.Countries, new { @class = "js-country-selector" })
               <div class="js-state-selector-container">
                   @Html.LabelFor(m => m.BillingAddress.StateID)
                   @Html.DropDownListFor(m => m.BillingAddress.StateID, Enumerable.Empty<SelectListItem>(), new { @class = "js-state-selector" })
               </div>
           </div>
       </div>

       <div id="shippingOption">
           <h3>Shipping option</h3>
           @Html.LabelFor(m => m.ShippingOption.ShippingOptionID)
           @Html.DropDownListFor(m => m.ShippingOption.ShippingOptionID, Model.ShippingOption.ShippingOptions)
       </div>

       <input type="submit" value="Continue" />
   }
   @Scripts.Render("~/Scripts/jquery-2.1.4.min.js")
   @Scripts.Render("~/Scripts/countryStateSelector.js")


   ```

   > **Tip:** Optionally, you can offer your registered users the ability to auto-fill previously entered personal details (such as billing and shipping addresses). See [Auto-filling addresses of existing customers](https://docs.kentico.com/13/e-commerce-features/developing-on-line-stores/implementing-a-checkout-process/gathering-customer-details-during-checkout/auto-filling-addresses-of-existing-customers.md) for more information.

The checkout process now allows your customers to provide information required for a successful checkout. In the [last part](https://docs.kentico.com/13/e-commerce-features/developing-on-line-stores/implementing-a-checkout-process/building-the-order-review-step.md) of this series, we will continue with implementing the order review step.
