src/Relecloud.Web.CallCenter/Controllers/CartController.cs (199 lines of code) (raw):

using Microsoft.ApplicationInsights; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Relecloud.Web.CallCenter.Infrastructure; using Relecloud.Web.CallCenter.Services; using Relecloud.Web.CallCenter.ViewModels; using Relecloud.Web.Models.ConcertContext; using Relecloud.Web.Models.Services; using Relecloud.Web.Models.TicketManagement; using Relecloud.Web.Models.TicketManagement.Payment; namespace Relecloud.Web.CallCenter.Controllers { public class CartController : Controller { #region Fields private readonly ITicketPurchaseService ticketPurchaseService; private readonly IConcertContextService concertService; private readonly TelemetryClient telemetryClient; private readonly ILogger<CartController> logger; #endregion #region Constructors public CartController(IConcertContextService concertService, TelemetryClient telemetry, ILogger<CartController> logger, ITicketPurchaseService ticketPurchaseService) { this.concertService = concertService; this.telemetryClient = telemetry; this.logger = logger; this.ticketPurchaseService = ticketPurchaseService; } #endregion #region Index public async Task<IActionResult> Index() { try { var model = await GetCartAsync(); return View(model); } catch (Exception ex) { logger.LogError(ex, "Unable to show cart items"); return View(); } } #endregion #region Add public async Task<IActionResult> Add(int concertId) { var model = await this.concertService.GetConcertByIdAsync(concertId); if (model == null) { return NotFound(); } return View(model); } [HttpPost] public IActionResult Add(int concertId, int count) { if (ModelState.IsValid) { try { var cartData = GetCartData(); if (!cartData.ContainsKey(concertId)) { cartData.Add(concertId, 0); } cartData[concertId] = cartData[concertId] + count; SetCartData(cartData); this.telemetryClient.TrackEvent("AddToCart", new Dictionary<string, string> { { "ConcertId", concertId.ToString() }, { "Count", count.ToString() } }); } catch (Exception ex) { this.logger.LogError(ex, $"Unable to add {concertId} to cart"); } } return RedirectToAction(nameof(Index)); } #endregion #region Remove [HttpPost] public IActionResult Remove(int concertId) { if (ModelState.IsValid) { try { var cartData = GetCartData(); if (cartData.ContainsKey(concertId)) { cartData.Remove(concertId); } SetCartData(cartData); this.telemetryClient.TrackEvent("RemoveFromCart", new Dictionary<string, string> { { "ConcertId", concertId.ToString() } }); } catch (Exception ex) { this.logger.LogError(ex, $"Unable to remove {concertId} to cart"); } } return RedirectToAction(nameof(Index)); } #endregion #region Checkout [Authorize] public async Task<IActionResult> Checkout() { var model = await GetCartAsync(); return View(new CheckoutViewModel { PaymentDetails = new PaymentDetails(), Cart = model }); } [Authorize] [HttpPost] [ActionName(nameof(Checkout))] public async Task<IActionResult> CheckoutConfirmed(CheckoutViewModel model) { try { if (model == null || model.PaymentDetails is null) { ModelState.AddModelError(string.Empty, "Invalid form state data"); return View(nameof(Checkout), model); } if (ModelState.IsValid) { var cartData = await GetCartAsync(); var serializableDictionary = MapToSerializableDictionary(cartData.Concerts); var purchaseResult = await this.ticketPurchaseService.PurchaseTicketAsync(new PurchaseTicketsRequest { ConcertIdsAndTicketCounts = serializableDictionary, PaymentDetails = model.PaymentDetails, UserId = User.GetUniqueId(), }); if (purchaseResult.Status == PurchaseTicketsResultStatus.Success) { // Remove all items from the cart. SetCartData(new Dictionary<int, int>()); this.telemetryClient.TrackEvent("CheckoutCart"); return RedirectToAction(nameof(Index), "Ticket"); } if (purchaseResult.ErrorMessages is null) { ModelState.AddModelError(string.Empty, "We're sorry but the purchasing service is unavailable at this time."); } else { ModelState.AddErrorMessages(purchaseResult.ErrorMessages); } } } catch (Exception ex) { this.logger.LogError(ex, $"Unable to perform checkout for ${User.Identity!.Name}"); ModelState.AddModelError(string.Empty, "We're sorry for the iconvenience but there was an error while trying to process your order. Please try again later."); } model.Cart = await GetCartAsync(); return View(nameof(Checkout), model); } private IDictionary<int, int> MapToSerializableDictionary(IDictionary<Concert, int> cartData) { var result = new Dictionary<int, int>(); foreach (var key in cartData.Keys) { result[key.Id] = cartData[key]; } return result; } #endregion #region Helper Methods // The key is the concert ID, the value is the number of items in the cart. private IDictionary<int, int> GetCartData() { return this.HttpContext.Session.Get<IDictionary<int, int>>(nameof(CartViewModel)) ?? new Dictionary<int, int>(); } private void SetCartData(IDictionary<int, int> data) { // Remove keys that have don't have items in the cart anymore. foreach (var emptyItemKey in data.Where(item => item.Value <= 0).Select(item => item.Key).ToArray()) { data.Remove(emptyItemKey); } this.HttpContext.Session.Set(nameof(CartViewModel), data); } private async Task<CartViewModel> GetCartAsync() { var cartData = GetCartData(); ICollection<Concert> concertsInCart = new List<Concert>(); if (cartData.Count > 0) { concertsInCart = await this.concertService.GetConcertsByIdAsync(cartData.Keys); } var concertCartData = concertsInCart.ToDictionary(concert => concert, concert => cartData[concert.Id]); return new CartViewModel(concertCartData); } #endregion } }