In the final part of this series we will add the possibility to checkout the items from your cart.
As you probably would have noticed in Part 7, we already added the checkout button in the Shopping Cart Index page.
In this series I will not cover the possibility of registering and login in. I will cover this in another tutorial/walkthrough. So the users can now just checkout without needing to sign in to an account.
First create the CheckoutController which will have the following methods:
AddressAndPayment (GET method) will display a form to allow the user to enter their information.
AddressAndPayment (POST method) will validate the input and process the order. If the input is valid, an order item will be created in Sitecore and the order will be confirmed by redirecting to the completion page.
Complete will be shown after a user has successfully finished the checkout process. This view will include the user’s order number, as confirmation.
public class CheckoutController : Controller { private readonly IOrderService _orderService; const string PromoCode = "FREE"; public CheckoutController(IOrderService orderService) { _orderService = orderService; } public ActionResult AddressAndPayment() { return View(); } [HttpPost] public ActionResult AddressAndPayment(FormCollection values) { var order = new Order(); var viewModel = new OrderViewModel(); try { if (string.Equals(values["PromoCode"], PromoCode, StringComparison.OrdinalIgnoreCase) == false) { return View(viewModel); } order.FirstName = values["FirstName"]; order.LastName = values["LastName"]; order.Address = values["Address"]; order.City = values["City"]; order.State = values["State"]; order.PostalCode = values["PostalCode"]; order.Country = values["Country"]; order.Phone = values["Phone"]; order.Email = values["Email"]; //Save Order _orderService.AddOrder(order); //Process the order var cart = ShoppingCart.GetCart(this.HttpContext); cart.CreateOrder(order); Response.Redirect("/Checkout/Complete?id=" + order.Id); } catch { //Invalid - redisplay with errors return View(viewModel); } return View(viewModel); } public ActionResult Complete(string id) { // Validate customer owns this order bool isValid = _orderService.GetOrder(id) != null; if (isValid) { return View(model: id); } else { return View("Error"); } } }
As you can see, this controller uses an OrderService to create and update orders in Sitecore.
Note that I use a 'IBaseEntityService' in this class. This is a service I created just like the Album and Genre services which only has a get method, which gets the Sitecore item based on the ID.
public class OrderService : BaseModelService<Order>, IOrderService { private readonly IBaseEntityService _baseEntityService; public OrderService(IModelValidator validator, IEventAggregator eventAggregator, IRepository<Order> repository, IBaseEntityService baseEntityService) : base(validator, eventAggregator, repository) { _baseEntityService = baseEntityService; } public Order AddOrder(Order order) { order.Name = HttpContext.Current.Session["CartId"].ToString().Replace("\\", "-") + " - " + DateTime.Now.ToDashedDateFormat(); order.Parent = _baseEntityService.Get("{D0C819DA-BA63-4766-8E5B-603D83DD7D30}"); using (new global::Sitecore.SecurityModel.SecurityDisabler()) { using (UnitOfWork.BeginUnitOfWork()) { return Insert(order); } } } public Order UpdateOrder(Order order) { using (new global::Sitecore.SecurityModel.SecurityDisabler()) { using (UnitOfWork.BeginUnitOfWork()) { return SaveOrUpdate(order); } } } public Order GetOrder(object id) { using (UnitOfWork.BeginUnitOfWork()) { return Get(id); } } }
Don't forget to also create the interface for this service and to add it to the SetDependencies class.
As you can see the AddOrder method creates a new Order item in Sitecore. But to do this it needs a parent item, in this case I use an Sitecore ID to get a folder I created in Sitecore.
Using an Sitecore ID like this is not recommended though! Normally I would create a setting in Sitecore in which the web admin can specify a folder where the orders will be stored. But because this is only a simple tutorial, I used the direct ID to this folder.
Now we need to create the views for the methods we added in the Checkout Controller.
@model MusicStore.Web.ViewModels.OrderViewModel @{ ViewBag.Title = "Address And Payment"; } <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> @using (Html.BeginForm()) { <h2>Address And Payment</h2> <fieldset> <legend>Shipping Information</legend> @Html.EditorForModel() </fieldset> <fieldset> <legend>Payment</legend> <p>We're running a promotion: all music is free with the promo code: "FREE"</p> <div class="editor-label"> @Html.Label("Promo Code") </div> <div class="editor-field"> @Html.TextBox("PromoCode") </div> </fieldset> <input type="submit" value="Submit Order" /> }
@model string <h2>Checkout Complete</h2> <p>Thanks for your order! Your order number is: @Model</p> <p> How about shopping for some more music in our <a href="/">store</a> </p>
The complete method also refers to a "Error" view. This view is not placed in the Checkout folder in the Views folder of the project as conventions would want the others, but because it's a direct call to the view, we need to add it to the Shared folder.
<h2>Error</h2> <p> We're sorry, we've hit an unexpected error. <a href="javascript:history.go(-1)">Click here</a> if you'd like to go back and try that again. </p>
Finally we ofcourse need to create the proper renderings in Sitecore again.
The AddressAndPayment rendering should be added to the /Home/Checkout page and the Complete rendering to the /Home/Checkout/Complete page.
That concludes this series. We should now have created a website where we can browse through genres and albums, and finally order those albums.