using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Primitives; using Nop.Core; using Nop.Core.Caching; using Nop.Core.Domain.Catalog; using Nop.Core.Domain.Common; using Nop.Core.Domain.Customers; using Nop.Core.Domain.Media; using Nop.Core.Domain.Orders; using Nop.Core.Domain.Security; using Nop.Core.Domain.Shipping; using Nop.Core.Http.Extensions; using Nop.Core.Infrastructure; using Nop.Services.Attributes; using Nop.Services.Catalog; using Nop.Services.Common; using Nop.Services.Customers; using Nop.Services.Directory; using Nop.Services.Discounts; using Nop.Services.Html; using Nop.Services.Localization; using Nop.Services.Logging; using Nop.Services.Media; using Nop.Services.Messages; using Nop.Services.Orders; using Nop.Services.Security; using Nop.Services.Seo; using Nop.Services.Shipping; using Nop.Services.Stores; using Nop.Services.Tax; using Nop.Web.Components; using Nop.Web.Factories; using Nop.Web.Framework.Controllers; using Nop.Web.Framework.Mvc; using Nop.Web.Framework.Mvc.Filters; using Nop.Web.Framework.Mvc.Routing; using Nop.Web.Infrastructure.Cache; using Nop.Web.Models.Media; using Nop.Web.Models.ShoppingCart; namespace Nop.Web.Controllers; [AutoValidateAntiforgeryToken] public partial class ShoppingCartController : BasePublicController { #region Fields protected readonly CaptchaSettings _captchaSettings; protected readonly CustomerSettings _customerSettings; protected readonly IAttributeParser _checkoutAttributeParser; protected readonly IAttributeService _checkoutAttributeService; protected readonly ICurrencyService _currencyService; protected readonly ICustomerActivityService _customerActivityService; protected readonly ICustomerService _customerService; protected readonly IDiscountService _discountService; protected readonly IDownloadService _downloadService; protected readonly IGenericAttributeService _genericAttributeService; protected readonly IGiftCardService _giftCardService; protected readonly IHtmlFormatter _htmlFormatter; protected readonly ILocalizationService _localizationService; protected readonly INopFileProvider _fileProvider; protected readonly INopUrlHelper _nopUrlHelper; protected readonly INotificationService _notificationService; protected readonly IPermissionService _permissionService; protected readonly IPictureService _pictureService; protected readonly IPriceFormatter _priceFormatter; protected readonly IProductAttributeParser _productAttributeParser; protected readonly IProductAttributeService _productAttributeService; protected readonly IProductService _productService; protected readonly IShippingService _shippingService; protected readonly IShoppingCartModelFactory _shoppingCartModelFactory; protected readonly IShoppingCartService _shoppingCartService; protected readonly IStaticCacheManager _staticCacheManager; protected readonly IStoreContext _storeContext; protected readonly IStoreMappingService _storeMappingService; protected readonly ITaxService _taxService; protected readonly IUrlRecordService _urlRecordService; protected readonly IWebHelper _webHelper; protected readonly IWorkContext _workContext; protected readonly IWorkflowMessageService _workflowMessageService; protected readonly MediaSettings _mediaSettings; protected readonly OrderSettings _orderSettings; protected readonly ShoppingCartSettings _shoppingCartSettings; protected readonly ShippingSettings _shippingSettings; private static readonly char[] _separator = [',']; #endregion #region Ctor public ShoppingCartController(CaptchaSettings captchaSettings, CustomerSettings customerSettings, IAttributeParser checkoutAttributeParser, IAttributeService checkoutAttributeService, ICurrencyService currencyService, ICustomerActivityService customerActivityService, ICustomerService customerService, IDiscountService discountService, IDownloadService downloadService, IGenericAttributeService genericAttributeService, IGiftCardService giftCardService, IHtmlFormatter htmlFormatter, ILocalizationService localizationService, INopFileProvider fileProvider, INopUrlHelper nopUrlHelper, INotificationService notificationService, IPermissionService permissionService, IPictureService pictureService, IPriceFormatter priceFormatter, IProductAttributeParser productAttributeParser, IProductAttributeService productAttributeService, IProductService productService, IShippingService shippingService, IShoppingCartModelFactory shoppingCartModelFactory, IShoppingCartService shoppingCartService, IStaticCacheManager staticCacheManager, IStoreContext storeContext, IStoreMappingService storeMappingService, ITaxService taxService, IUrlRecordService urlRecordService, IWebHelper webHelper, IWorkContext workContext, IWorkflowMessageService workflowMessageService, MediaSettings mediaSettings, OrderSettings orderSettings, ShoppingCartSettings shoppingCartSettings, ShippingSettings shippingSettings) { _captchaSettings = captchaSettings; _customerSettings = customerSettings; _checkoutAttributeParser = checkoutAttributeParser; _checkoutAttributeService = checkoutAttributeService; _currencyService = currencyService; _customerActivityService = customerActivityService; _customerService = customerService; _discountService = discountService; _downloadService = downloadService; _genericAttributeService = genericAttributeService; _giftCardService = giftCardService; _htmlFormatter = htmlFormatter; _localizationService = localizationService; _fileProvider = fileProvider; _nopUrlHelper = nopUrlHelper; _notificationService = notificationService; _permissionService = permissionService; _pictureService = pictureService; _priceFormatter = priceFormatter; _productAttributeParser = productAttributeParser; _productAttributeService = productAttributeService; _productService = productService; _shippingService = shippingService; _shoppingCartModelFactory = shoppingCartModelFactory; _shoppingCartService = shoppingCartService; _staticCacheManager = staticCacheManager; _storeContext = storeContext; _storeMappingService = storeMappingService; _taxService = taxService; _urlRecordService = urlRecordService; _webHelper = webHelper; _workContext = workContext; _workflowMessageService = workflowMessageService; _mediaSettings = mediaSettings; _orderSettings = orderSettings; _shoppingCartSettings = shoppingCartSettings; _shippingSettings = shippingSettings; } #endregion #region Utilities protected virtual async Task ParseAndSaveCheckoutAttributesAsync(IList cart, IFormCollection form) { ArgumentNullException.ThrowIfNull(cart); ArgumentNullException.ThrowIfNull(form); var attributesXml = string.Empty; var excludeShippableAttributes = !await _shoppingCartService.ShoppingCartRequiresShippingAsync(cart); var store = await _storeContext.GetCurrentStoreAsync(); var checkoutAttributes = await _checkoutAttributeService.GetAllAttributesAsync(_staticCacheManager, _storeMappingService, store.Id, excludeShippableAttributes); foreach (var attribute in checkoutAttributes) { var controlId = $"checkout_attribute_{attribute.Id}"; switch (attribute.AttributeControlType) { case AttributeControlType.DropdownList: case AttributeControlType.RadioList: case AttributeControlType.ColorSquares: case AttributeControlType.ImageSquares: { var ctrlAttributes = form[controlId]; if (!StringValues.IsNullOrEmpty(ctrlAttributes)) { var selectedAttributeId = int.Parse(ctrlAttributes); if (selectedAttributeId > 0) attributesXml = _checkoutAttributeParser.AddAttribute(attributesXml, attribute, selectedAttributeId.ToString()); } } break; case AttributeControlType.Checkboxes: { var cblAttributes = form[controlId]; if (!StringValues.IsNullOrEmpty(cblAttributes)) { foreach (var item in cblAttributes.ToString().Split(_separator, StringSplitOptions.RemoveEmptyEntries)) { var selectedAttributeId = int.Parse(item); if (selectedAttributeId > 0) attributesXml = _checkoutAttributeParser.AddAttribute(attributesXml, attribute, selectedAttributeId.ToString()); } } } break; case AttributeControlType.ReadonlyCheckboxes: { //load read-only (already server-side selected) values var attributeValues = await _checkoutAttributeService.GetAttributeValuesAsync(attribute.Id); foreach (var selectedAttributeId in attributeValues .Where(v => v.IsPreSelected) .Select(v => v.Id) .ToList()) { attributesXml = _checkoutAttributeParser.AddAttribute(attributesXml, attribute, selectedAttributeId.ToString()); } } break; case AttributeControlType.TextBox: case AttributeControlType.MultilineTextbox: { var ctrlAttributes = form[controlId]; if (!StringValues.IsNullOrEmpty(ctrlAttributes)) { var enteredText = ctrlAttributes.ToString().Trim(); attributesXml = _checkoutAttributeParser.AddAttribute(attributesXml, attribute, enteredText); } } break; case AttributeControlType.Datepicker: { var date = form[controlId + "_day"]; var month = form[controlId + "_month"]; var year = form[controlId + "_year"]; DateTime? selectedDate = null; try { selectedDate = new DateTime(int.Parse(year), int.Parse(month), int.Parse(date)); } catch { // ignored } if (selectedDate.HasValue) attributesXml = _checkoutAttributeParser.AddAttribute(attributesXml, attribute, selectedDate.Value.ToString("D")); } break; case AttributeControlType.FileUpload: { _ = Guid.TryParse(form[controlId], out var downloadGuid); var download = await _downloadService.GetDownloadByGuidAsync(downloadGuid); if (download != null) { attributesXml = _checkoutAttributeParser.AddAttribute(attributesXml, attribute, download.DownloadGuid.ToString()); } } break; default: break; } } //validate conditional attributes (if specified) foreach (var attribute in checkoutAttributes) { var conditionMet = await _checkoutAttributeParser.IsConditionMetAsync(attribute.ConditionAttributeXml, attributesXml); if (conditionMet.HasValue && !conditionMet.Value) attributesXml = _checkoutAttributeParser.RemoveAttribute(attributesXml, attribute.Id); } //save checkout attributes await _genericAttributeService.SaveAttributeAsync(await _workContext.GetCurrentCustomerAsync(), NopCustomerDefaults.CheckoutAttributes, attributesXml, store.Id); } protected virtual async Task SaveItemAsync(ShoppingCartItem updatecartitem, List addToCartWarnings, Product product, ShoppingCartType cartType, string attributes, decimal customerEnteredPriceConverted, DateTime? rentalStartDate, DateTime? rentalEndDate, int quantity) { var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); if (updatecartitem == null) { //add to the cart addToCartWarnings.AddRange(await _shoppingCartService.AddToCartAsync(customer, product, cartType, store.Id, attributes, customerEnteredPriceConverted, rentalStartDate, rentalEndDate, quantity, true)); } else { var cart = await _shoppingCartService.GetShoppingCartAsync(customer, updatecartitem.ShoppingCartType, store.Id); var otherCartItemWithSameParameters = await _shoppingCartService.FindShoppingCartItemInTheCartAsync( cart, updatecartitem.ShoppingCartType, product, attributes, customerEnteredPriceConverted, rentalStartDate, rentalEndDate); if (otherCartItemWithSameParameters != null && otherCartItemWithSameParameters.Id == updatecartitem.Id) { //ensure it's some other shopping cart item otherCartItemWithSameParameters = null; } //update existing item addToCartWarnings.AddRange(await _shoppingCartService.UpdateShoppingCartItemAsync(customer, updatecartitem.Id, attributes, customerEnteredPriceConverted, rentalStartDate, rentalEndDate, quantity + (otherCartItemWithSameParameters?.Quantity ?? 0), true)); if (otherCartItemWithSameParameters != null && !addToCartWarnings.Any()) { //delete the same shopping cart item (the other one) await _shoppingCartService.DeleteShoppingCartItemAsync(otherCartItemWithSameParameters); } } } protected virtual async Task GetProductToCartDetailsAsync(List addToCartWarnings, ShoppingCartType cartType, Product product) { if (addToCartWarnings.Any()) { //cannot be added to the cart/wishlist //let's display warnings return Json(new { success = false, message = addToCartWarnings.ToArray() }); } //added to the cart/wishlist var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); switch (cartType) { case ShoppingCartType.Wishlist: { //activity log await _customerActivityService.InsertActivityAsync("PublicStore.AddToWishlist", string.Format(await _localizationService.GetResourceAsync("ActivityLog.PublicStore.AddToWishlist"), product.Name), product); if (_shoppingCartSettings.DisplayWishlistAfterAddingProduct) { //redirect to the wishlist page return Json(new { redirect = Url.RouteUrl("Wishlist") }); } //display notification message and update appropriate blocks var shoppingCarts = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.Wishlist, store.Id); var updateTopWishlistSectionHtml = string.Format( await _localizationService.GetResourceAsync("Wishlist.HeaderQuantity"), shoppingCarts.Sum(item => item.Quantity)); return Json(new { success = true, message = string.Format( await _localizationService.GetResourceAsync("Products.ProductHasBeenAddedToTheWishlist.Link"), Url.RouteUrl("Wishlist")), updatetopwishlistsectionhtml = updateTopWishlistSectionHtml }); } case ShoppingCartType.ShoppingCart: default: { //activity log await _customerActivityService.InsertActivityAsync("PublicStore.AddToShoppingCart", string.Format(await _localizationService.GetResourceAsync("ActivityLog.PublicStore.AddToShoppingCart"), product.Name), product); if (_shoppingCartSettings.DisplayCartAfterAddingProduct) { //redirect to the shopping cart page return Json(new { redirect = Url.RouteUrl("ShoppingCart") }); } //display notification message and update appropriate blocks var shoppingCarts = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); var updateTopCartSectionHtml = string.Format( await _localizationService.GetResourceAsync("ShoppingCart.HeaderQuantity"), shoppingCarts.Sum(item => item.Quantity)); var updateFlyoutCartSectionHtml = _shoppingCartSettings.MiniShoppingCartEnabled ? await RenderViewComponentToStringAsync(typeof(FlyoutShoppingCartViewComponent)) : string.Empty; return Json(new { success = true, message = string.Format(await _localizationService.GetResourceAsync("Products.ProductHasBeenAddedToTheCart.Link"), Url.RouteUrl("ShoppingCart")), updatetopcartsectionhtml = updateTopCartSectionHtml, updateflyoutcartsectionhtml = updateFlyoutCartSectionHtml }); } } } protected virtual async Task GetGiftCardValidationErrorAsync(IList cart, string giftcardcouponcode) { if (string.IsNullOrWhiteSpace(giftcardcouponcode)) return await _localizationService.GetResourceAsync("ShoppingCart.GiftCardCouponCode.WrongGiftCard"); if (await _shoppingCartService.ShoppingCartIsRecurringAsync(cart)) return await _localizationService.GetResourceAsync("ShoppingCart.GiftCardCouponCode.DontWorkWithAutoshipProducts"); var giftCard = (await _giftCardService.GetAllGiftCardsAsync(giftCardCouponCode: giftcardcouponcode)).FirstOrDefault(); if (giftCard == null || !await _giftCardService.IsGiftCardValidAsync(giftCard)) return await _localizationService.GetResourceAsync("ShoppingCart.GiftCardCouponCode.WrongGiftCard"); if (await _productService.HasAnyGiftCardProductAsync(cart.Select(c => c.ProductId).ToArray())) return await _localizationService.GetResourceAsync("ShoppingCart.GiftCardCouponCode.DontWorkWithGiftCards"); return string.Empty; } #endregion #region Shopping cart [HttpPost] public virtual async Task SelectShippingOption([FromQuery] string name, [FromQuery] EstimateShippingModel model, IFormCollection form) { if (model == null) model = new EstimateShippingModel(); var errors = new List(); if (string.IsNullOrEmpty(model.ZipPostalCode) && !_shippingSettings.EstimateShippingCityNameEnabled) errors.Add(await _localizationService.GetResourceAsync("Shipping.EstimateShipping.ZipPostalCode.Required")); if (model.CountryId == null || model.CountryId == 0) errors.Add(await _localizationService.GetResourceAsync("Shipping.EstimateShipping.Country.Required")); if (errors.Count > 0) return Json(new { success = false, errors = errors }); var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); //parse and save checkout attributes await ParseAndSaveCheckoutAttributesAsync(cart, form); var shippingOptions = new List(); ShippingOption selectedShippingOption = null; if (!string.IsNullOrWhiteSpace(name)) { //find shipping options //performance optimization. try cache first shippingOptions = await _genericAttributeService.GetAttributeAsync>(customer, NopCustomerDefaults.OfferedShippingOptionsAttribute, store.Id); if (shippingOptions == null || !shippingOptions.Any()) { var address = new Address { CountryId = model.CountryId, StateProvinceId = model.StateProvinceId, ZipPostalCode = model.ZipPostalCode, }; //not found? let's load them using shipping service var getShippingOptionResponse = await _shippingService.GetShippingOptionsAsync(cart, address, customer, storeId: store.Id); if (getShippingOptionResponse.Success) shippingOptions = getShippingOptionResponse.ShippingOptions.ToList(); else foreach (var error in getShippingOptionResponse.Errors) errors.Add(error); } } selectedShippingOption = shippingOptions.Find(so => !string.IsNullOrEmpty(so.Name) && so.Name.Equals(name, StringComparison.InvariantCultureIgnoreCase)); if (selectedShippingOption == null) errors.Add(await _localizationService.GetResourceAsync("Shipping.EstimateShippingPopUp.ShippingOption.IsNotFound")); if (errors.Count > 0) return Json(new { success = false, errors = errors }); //reset pickup point await _genericAttributeService.SaveAttributeAsync(customer, NopCustomerDefaults.SelectedPickupPointAttribute, null, store.Id); //cache shipping option await _genericAttributeService.SaveAttributeAsync(customer, NopCustomerDefaults.SelectedShippingOptionAttribute, selectedShippingOption, store.Id); var orderTotalsSectionHtml = await RenderViewComponentToStringAsync(typeof(OrderTotalsViewComponent), new { isEditable = true }); return Json(new { success = true, ordertotalssectionhtml = orderTotalsSectionHtml }); } //add product to cart using AJAX //currently we use this method on catalog pages (category/manufacturer/etc) [HttpPost] public virtual async Task AddProductToCart_Catalog(int productId, int shoppingCartTypeId, int quantity, bool forceredirection = false) { var cartType = (ShoppingCartType)shoppingCartTypeId; var product = await _productService.GetProductByIdAsync(productId); if (product == null) //no product found return Json(new { success = false, message = "No product found with the specified ID" }); var redirectUrl = await _nopUrlHelper.RouteGenericUrlAsync(new { SeName = await _urlRecordService.GetSeNameAsync(product) }); //we can add only simple products if (product.ProductType != ProductType.SimpleProduct) return Json(new { redirect = redirectUrl }); //products with "minimum order quantity" more than a specified qty if (product.OrderMinimumQuantity > quantity) { //we cannot add to the cart such products from category pages //it can confuse customers. That's why we redirect customers to the product details page return Json(new { redirect = redirectUrl }); } if (product.CustomerEntersPrice) { //cannot be added to the cart (requires a customer to enter price) return Json(new { redirect = redirectUrl }); } if (product.IsRental) { //rental products require start/end dates to be entered return Json(new { redirect = redirectUrl }); } var allowedQuantities = _productService.ParseAllowedQuantities(product); if (allowedQuantities.Length > 0) { //cannot be added to the cart (requires a customer to select a quantity from dropdownlist) return Json(new { redirect = redirectUrl }); } //allow a product to be added to the cart when all attributes are with "read-only checkboxes" type var productAttributes = await _productAttributeService.GetProductAttributeMappingsByProductIdAsync(product.Id); if (productAttributes.Any(pam => pam.AttributeControlType != AttributeControlType.ReadonlyCheckboxes)) { //product has some attributes. let a customer see them return Json(new { redirect = redirectUrl }); } //creating XML for "read-only checkboxes" attributes var attXml = await productAttributes.AggregateAwaitAsync(string.Empty, async (attributesXml, attribute) => { var attributeValues = await _productAttributeService.GetProductAttributeValuesAsync(attribute.Id); foreach (var selectedAttributeId in attributeValues .Where(v => v.IsPreSelected) .Select(v => v.Id) .ToList()) { attributesXml = _productAttributeParser.AddProductAttribute(attributesXml, attribute, selectedAttributeId.ToString()); } return attributesXml; }); //get standard warnings without attribute validations //first, try to find existing shopping cart item var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, cartType, store.Id); var shoppingCartItem = await _shoppingCartService.FindShoppingCartItemInTheCartAsync(cart, cartType, product); //if we already have the same product in the cart, then use the total quantity to validate var quantityToValidate = shoppingCartItem != null ? shoppingCartItem.Quantity + quantity : quantity; var addToCartWarnings = await _shoppingCartService .GetShoppingCartItemWarningsAsync(customer, cartType, product, store.Id, string.Empty, decimal.Zero, null, null, quantityToValidate, false, shoppingCartItem?.Id ?? 0, true, false, false, false); if (addToCartWarnings.Any()) { //cannot be added to the cart //let's display standard warnings return Json(new { success = false, message = addToCartWarnings.ToArray() }); } //now let's try adding product to the cart (now including product attribute validation, etc) addToCartWarnings = await _shoppingCartService.AddToCartAsync(customer: customer, product: product, shoppingCartType: cartType, storeId: store.Id, attributesXml: attXml, quantity: quantity); if (addToCartWarnings.Any()) { //cannot be added to the cart //but we do not display attribute and gift card warnings here. let's do it on the product details page return Json(new { redirect = redirectUrl }); } //added to the cart/wishlist switch (cartType) { case ShoppingCartType.Wishlist: { //activity log await _customerActivityService.InsertActivityAsync("PublicStore.AddToWishlist", string.Format(await _localizationService.GetResourceAsync("ActivityLog.PublicStore.AddToWishlist"), product.Name), product); if (_shoppingCartSettings.DisplayWishlistAfterAddingProduct || forceredirection) { //redirect to the wishlist page return Json(new { redirect = Url.RouteUrl("Wishlist") }); } //display notification message and update appropriate blocks var shoppingCarts = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.Wishlist, store.Id); var updatetopwishlistsectionhtml = string.Format(await _localizationService.GetResourceAsync("Wishlist.HeaderQuantity"), shoppingCarts.Sum(item => item.Quantity)); return Json(new { success = true, message = string.Format(await _localizationService.GetResourceAsync("Products.ProductHasBeenAddedToTheWishlist.Link"), Url.RouteUrl("Wishlist")), updatetopwishlistsectionhtml }); } case ShoppingCartType.ShoppingCart: default: { //activity log await _customerActivityService.InsertActivityAsync("PublicStore.AddToShoppingCart", string.Format(await _localizationService.GetResourceAsync("ActivityLog.PublicStore.AddToShoppingCart"), product.Name), product); if (_shoppingCartSettings.DisplayCartAfterAddingProduct || forceredirection) { //redirect to the shopping cart page return Json(new { redirect = Url.RouteUrl("ShoppingCart") }); } //display notification message and update appropriate blocks var shoppingCarts = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); var updatetopcartsectionhtml = string.Format(await _localizationService.GetResourceAsync("ShoppingCart.HeaderQuantity"), shoppingCarts.Sum(item => item.Quantity)); var updateflyoutcartsectionhtml = _shoppingCartSettings.MiniShoppingCartEnabled ? await RenderViewComponentToStringAsync(typeof(FlyoutShoppingCartViewComponent)) : string.Empty; return Json(new { success = true, message = string.Format(await _localizationService.GetResourceAsync("Products.ProductHasBeenAddedToTheCart.Link"), Url.RouteUrl("ShoppingCart")), updatetopcartsectionhtml, updateflyoutcartsectionhtml }); } } } //add product to cart using AJAX //currently we use this method on the product details pages [HttpPost] public virtual async Task AddProductToCart_Details(int productId, int shoppingCartTypeId, IFormCollection form) { var product = await _productService.GetProductByIdAsync(productId); if (product == null) { return Json(new { redirect = Url.RouteUrl("Homepage") }); } //we can add only simple products if (product.ProductType != ProductType.SimpleProduct) { return Json(new { success = false, message = "Only simple products could be added to the cart" }); } //update existing shopping cart item var updatecartitemid = 0; foreach (var formKey in form.Keys) if (formKey.Equals($"addtocart_{productId}.UpdatedShoppingCartItemId", StringComparison.InvariantCultureIgnoreCase)) { _ = int.TryParse(form[formKey], out updatecartitemid); break; } ShoppingCartItem updatecartitem = null; if (_shoppingCartSettings.AllowCartItemEditing && updatecartitemid > 0) { var store = await _storeContext.GetCurrentStoreAsync(); //search with the same cart type as specified var cart = await _shoppingCartService.GetShoppingCartAsync(await _workContext.GetCurrentCustomerAsync(), (ShoppingCartType)shoppingCartTypeId, store.Id); updatecartitem = cart.FirstOrDefault(x => x.Id == updatecartitemid); //not found? let's ignore it. in this case we'll add a new item //if (updatecartitem == null) //{ // return Json(new // { // success = false, // message = "No shopping cart item found to update" // }); //} //is it this product? if (updatecartitem != null && product.Id != updatecartitem.ProductId) { return Json(new { success = false, message = "This product does not match a passed shopping cart item identifier" }); } } var addToCartWarnings = new List(); //customer entered price var customerEnteredPriceConverted = await _productAttributeParser.ParseCustomerEnteredPriceAsync(product, form); //entered quantity var quantity = _productAttributeParser.ParseEnteredQuantity(product, form); //product and gift card attributes var attributes = await _productAttributeParser.ParseProductAttributesAsync(product, form, addToCartWarnings); //rental attributes _productAttributeParser.ParseRentalDates(product, form, out var rentalStartDate, out var rentalEndDate); var cartType = updatecartitem == null ? (ShoppingCartType)shoppingCartTypeId : //if the item to update is found, then we ignore the specified "shoppingCartTypeId" parameter updatecartitem.ShoppingCartType; await SaveItemAsync(updatecartitem, addToCartWarnings, product, cartType, attributes, customerEnteredPriceConverted, rentalStartDate, rentalEndDate, quantity); //return result return await GetProductToCartDetailsAsync(addToCartWarnings, cartType, product); } //handle product attribute selection event. this way we return new price, overridden gtin/sku/mpn //currently we use this method on the product details pages [HttpPost] public virtual async Task ProductDetails_AttributeChange(int productId, bool validateAttributeConditions, bool loadPicture, IFormCollection form) { var product = await _productService.GetProductByIdAsync(productId); if (product == null) return new NullJsonResult(); var errors = new List(); var attributeXml = await _productAttributeParser.ParseProductAttributesAsync(product, form, errors); //rental attributes DateTime? rentalStartDate = null; DateTime? rentalEndDate = null; if (product.IsRental) { _productAttributeParser.ParseRentalDates(product, form, out rentalStartDate, out rentalEndDate); } //sku, mpn, gtin var sku = await _productService.FormatSkuAsync(product, attributeXml); var mpn = await _productService.FormatMpnAsync(product, attributeXml); var gtin = await _productService.FormatGtinAsync(product, attributeXml); // calculating weight adjustment var attributeValues = await _productAttributeParser.ParseProductAttributeValuesAsync(attributeXml); var totalWeight = product.BasepriceAmount; foreach (var attributeValue in attributeValues) { switch (attributeValue.AttributeValueType) { case AttributeValueType.Simple: //simple attribute totalWeight += attributeValue.WeightAdjustment; break; case AttributeValueType.AssociatedToProduct: //bundled product var associatedProduct = await _productService.GetProductByIdAsync(attributeValue.AssociatedProductId); if (associatedProduct != null) totalWeight += associatedProduct.BasepriceAmount * attributeValue.Quantity; break; } } //price var price = string.Empty; //base price var basepricepangv = string.Empty; if (!product.CustomerEntersPrice && await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.DISPLAY_PRICES)) { var currentStore = await _storeContext.GetCurrentStoreAsync(); var currentCustomer = await _workContext.GetCurrentCustomerAsync(); //we do not calculate price of "customer enters price" option is enabled var (finalPrice, _, _) = await _shoppingCartService.GetUnitPriceAsync(product, currentCustomer, currentStore, ShoppingCartType.ShoppingCart, 1, attributeXml, 0, rentalStartDate, rentalEndDate, true); var (finalPriceWithDiscountBase, _) = await _taxService.GetProductPriceAsync(product, finalPrice); var finalPriceWithDiscount = await _currencyService.ConvertFromPrimaryStoreCurrencyAsync(finalPriceWithDiscountBase, await _workContext.GetWorkingCurrencyAsync()); price = await _priceFormatter.FormatPriceAsync(finalPriceWithDiscount); basepricepangv = await _priceFormatter.FormatBasePriceAsync(product, finalPriceWithDiscountBase, totalWeight); } //stock var stockAvailability = await _productService.FormatStockMessageAsync(product, attributeXml); //conditional attributes var enabledAttributeMappingIds = new List(); var disabledAttributeMappingIds = new List(); if (validateAttributeConditions) { var attributes = await _productAttributeService.GetProductAttributeMappingsByProductIdAsync(product.Id); foreach (var attribute in attributes) { var conditionMet = await _productAttributeParser.IsConditionMetAsync(attribute, attributeXml); if (conditionMet.HasValue) { if (conditionMet.Value) enabledAttributeMappingIds.Add(attribute.Id); else disabledAttributeMappingIds.Add(attribute.Id); } } } //picture. used when we want to override a default product picture when some attribute is selected var pictureFullSizeUrl = string.Empty; var pictureDefaultSizeUrl = string.Empty; var pictureIds = new List(); if (loadPicture) { //first, try to get product attribute combination picture var pictureId = 0; var combination = await _productAttributeParser.FindProductAttributeCombinationAsync(product, attributeXml); if (combination != null) { var combinationPictures = await _productAttributeService.GetProductAttributeCombinationPicturesAsync(combination.Id); pictureIds = combinationPictures.Select(cp => cp.PictureId).ToList(); pictureId = combinationPictures.FirstOrDefault()?.PictureId ?? 0; } //then, let's see whether we have attribute values with pictures if (pictureId == 0) { var valuePictures = await (await _productAttributeParser.ParseProductAttributeValuesAsync(attributeXml)) .SelectManyAwait(async attributeValue => await _productAttributeService.GetProductAttributeValuePicturesAsync(attributeValue.Id)) .ToListAsync(); pictureIds = valuePictures.Select(vp => vp.PictureId).ToList(); pictureId = valuePictures.FirstOrDefault()?.PictureId ?? 0; } if (pictureId > 0) { var productAttributePictureCacheKey = _staticCacheManager.PrepareKeyForDefaultCache(NopModelCacheDefaults.ProductAttributePictureModelKey, pictureId, _webHelper.IsCurrentConnectionSecured(), await _storeContext.GetCurrentStoreAsync()); var pictureModel = await _staticCacheManager.GetAsync(productAttributePictureCacheKey, async () => { var picture = await _pictureService.GetPictureByIdAsync(pictureId); string fullSizeImageUrl, imageUrl; (fullSizeImageUrl, picture) = await _pictureService.GetPictureUrlAsync(picture); (imageUrl, picture) = await _pictureService.GetPictureUrlAsync(picture, _mediaSettings.ProductDetailsPictureSize); return picture == null ? new PictureModel() : new PictureModel { FullSizeImageUrl = fullSizeImageUrl, ImageUrl = imageUrl }; }); pictureFullSizeUrl = pictureModel.FullSizeImageUrl; pictureDefaultSizeUrl = pictureModel.ImageUrl; } } var isFreeShipping = product.IsFreeShipping; if (isFreeShipping && !string.IsNullOrEmpty(attributeXml)) { isFreeShipping = await (await _productAttributeParser.ParseProductAttributeValuesAsync(attributeXml)) .Where(attributeValue => attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct) .SelectAwait(async attributeValue => await _productService.GetProductByIdAsync(attributeValue.AssociatedProductId)) .AllAsync(associatedProduct => associatedProduct == null || !associatedProduct.IsShipEnabled || associatedProduct.IsFreeShipping); } return Json(new { productId, gtin, mpn, sku, price, basepricepangv, stockAvailability, enabledattributemappingids = enabledAttributeMappingIds.ToArray(), disabledattributemappingids = disabledAttributeMappingIds.ToArray(), pictureFullSizeUrl, pictureDefaultSizeUrl, pictureIds, isFreeShipping, message = errors.Any() ? errors.ToArray() : null }); } [HttpPost] public virtual async Task CheckoutAttributeChange(IFormCollection form, bool isEditable) { var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); //save selected attributes await ParseAndSaveCheckoutAttributesAsync(cart, form); var attributeXml = await _genericAttributeService.GetAttributeAsync(customer, NopCustomerDefaults.CheckoutAttributes, store.Id); //conditions var enabledAttributeIds = new List(); var disabledAttributeIds = new List(); var excludeShippableAttributes = !await _shoppingCartService.ShoppingCartRequiresShippingAsync(cart); var attributes = await _checkoutAttributeService.GetAllAttributesAsync(_staticCacheManager, _storeMappingService, store.Id, excludeShippableAttributes); foreach (var attribute in attributes) { var conditionMet = await _checkoutAttributeParser.IsConditionMetAsync(attribute.ConditionAttributeXml, attributeXml); if (conditionMet.HasValue) { if (conditionMet.Value) enabledAttributeIds.Add(attribute.Id); else disabledAttributeIds.Add(attribute.Id); } } //update blocks var ordetotalssectionhtml = await RenderViewComponentToStringAsync(typeof(OrderTotalsViewComponent), new { isEditable }); var selectedcheckoutattributesssectionhtml = await RenderViewComponentToStringAsync(typeof(SelectedCheckoutAttributesViewComponent)); return Json(new { ordetotalssectionhtml, selectedcheckoutattributesssectionhtml, enabledattributeids = enabledAttributeIds.ToArray(), disabledattributeids = disabledAttributeIds.ToArray() }); } [HttpPost] [IgnoreAntiforgeryToken] public virtual async Task UploadFileProductAttribute(int attributeId) { var attribute = await _productAttributeService.GetProductAttributeMappingByIdAsync(attributeId); if (attribute == null || attribute.AttributeControlType != AttributeControlType.FileUpload) { return Json(new { success = false, downloadGuid = Guid.Empty }); } var httpPostedFile = await Request.GetFirstOrDefaultFileAsync(); if (httpPostedFile == null) { return Json(new { success = false, message = "No file uploaded", downloadGuid = Guid.Empty }); } var fileBinary = await _downloadService.GetDownloadBitsAsync(httpPostedFile); var fileName = httpPostedFile.FileName; //remove path (passed in IE) fileName = _fileProvider.GetFileName(fileName); var contentType = httpPostedFile.ContentType; var fileExtension = _fileProvider.GetFileExtension(fileName); if (!string.IsNullOrEmpty(fileExtension)) fileExtension = fileExtension.ToLowerInvariant(); if (attribute.ValidationFileMaximumSize.HasValue) { //compare in bytes var maxFileSizeBytes = attribute.ValidationFileMaximumSize.Value * 1024; if (fileBinary.Length > maxFileSizeBytes) { //when returning JSON the mime-type must be set to text/plain //otherwise some browsers will pop-up a "Save As" dialog. return Json(new { success = false, message = string.Format(await _localizationService.GetResourceAsync("ShoppingCart.MaximumUploadedFileSize"), attribute.ValidationFileMaximumSize.Value), downloadGuid = Guid.Empty }); } } var download = new Download { DownloadGuid = Guid.NewGuid(), UseDownloadUrl = false, DownloadUrl = string.Empty, DownloadBinary = fileBinary, ContentType = contentType, //we store filename without extension for downloads Filename = _fileProvider.GetFileNameWithoutExtension(fileName), Extension = fileExtension, IsNew = true }; await _downloadService.InsertDownloadAsync(download); //when returning JSON the mime-type must be set to text/plain //otherwise some browsers will pop-up a "Save As" dialog. return Json(new { success = true, message = await _localizationService.GetResourceAsync("ShoppingCart.FileUploaded"), downloadUrl = Url.RouteUrl("DownloadGetFileUpload", new { downloadId = download.DownloadGuid }), downloadGuid = download.DownloadGuid }); } [HttpPost] [IgnoreAntiforgeryToken] public virtual async Task UploadFileCheckoutAttribute(int attributeId) { var attribute = await _checkoutAttributeService.GetAttributeByIdAsync(attributeId); if (attribute == null || attribute.AttributeControlType != AttributeControlType.FileUpload) { return Json(new { success = false, downloadGuid = Guid.Empty }); } var httpPostedFile = await Request.GetFirstOrDefaultFileAsync(); if (httpPostedFile == null) { return Json(new { success = false, message = "No file uploaded", downloadGuid = Guid.Empty }); } var fileBinary = await _downloadService.GetDownloadBitsAsync(httpPostedFile); var fileName = httpPostedFile.FileName; //remove path (passed in IE) fileName = _fileProvider.GetFileName(fileName); var contentType = httpPostedFile.ContentType; var fileExtension = _fileProvider.GetFileExtension(fileName); if (!string.IsNullOrEmpty(fileExtension)) fileExtension = fileExtension.ToLowerInvariant(); if (attribute.ValidationFileMaximumSize.HasValue) { //compare in bytes var maxFileSizeBytes = attribute.ValidationFileMaximumSize.Value * 1024; if (fileBinary.Length > maxFileSizeBytes) { //when returning JSON the mime-type must be set to text/plain //otherwise some browsers will pop-up a "Save As" dialog. return Json(new { success = false, message = string.Format(await _localizationService.GetResourceAsync("ShoppingCart.MaximumUploadedFileSize"), attribute.ValidationFileMaximumSize.Value), downloadGuid = Guid.Empty }); } } var download = new Download { DownloadGuid = Guid.NewGuid(), UseDownloadUrl = false, DownloadUrl = string.Empty, DownloadBinary = fileBinary, ContentType = contentType, //we store filename without extension for downloads Filename = _fileProvider.GetFileNameWithoutExtension(fileName), Extension = fileExtension, IsNew = true }; await _downloadService.InsertDownloadAsync(download); //when returning JSON the mime-type must be set to text/plain //otherwise some browsers will pop-up a "Save As" dialog. return Json(new { success = true, message = await _localizationService.GetResourceAsync("ShoppingCart.FileUploaded"), downloadUrl = Url.RouteUrl("DownloadGetFileUpload", new { downloadId = download.DownloadGuid }), downloadGuid = download.DownloadGuid }); } public virtual async Task Cart() { if (!await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.ENABLE_SHOPPING_CART)) return RedirectToRoute("Homepage"); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(await _workContext.GetCurrentCustomerAsync(), ShoppingCartType.ShoppingCart, store.Id); var model = new ShoppingCartModel(); model = await _shoppingCartModelFactory.PrepareShoppingCartModelAsync(model, cart); return View(model); } [HttpPost, ActionName("Cart")] [FormValueRequired("updatecart")] public virtual async Task UpdateCart(IFormCollection form) { if (!await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.ENABLE_SHOPPING_CART)) return RedirectToRoute("Homepage"); var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); //get identifiers of items to remove var itemIdsToRemove = form["removefromcart"] .SelectMany(value => value.Split(_separator, StringSplitOptions.RemoveEmptyEntries)) .Select(idString => int.TryParse(idString, out var id) ? id : 0) .Distinct().ToList(); var products = (await _productService.GetProductsByIdsAsync(cart.Select(item => item.ProductId).Distinct().ToArray())) .ToDictionary(item => item.Id, item => item); //get order items with changed quantity var itemsWithNewQuantity = cart.Select(item => new { //try to get a new quantity for the item, set 0 for items to remove NewQuantity = itemIdsToRemove.Contains(item.Id) ? 0 : int.TryParse(form[$"itemquantity{item.Id}"], out var quantity) ? quantity : item.Quantity, Item = item, Product = products.TryGetValue(item.ProductId, out var value) ? value : null }).Where(item => item.NewQuantity != item.Item.Quantity); //order cart items //first should be items with a reduced quantity and that require other products; or items with an increased quantity and are required for other products var orderedCart = await itemsWithNewQuantity .OrderByDescendingAwait(async cartItem => (cartItem.NewQuantity < cartItem.Item.Quantity && (cartItem.Product?.RequireOtherProducts ?? false)) || (cartItem.NewQuantity > cartItem.Item.Quantity && cartItem.Product != null && (await _shoppingCartService .GetProductsRequiringProductAsync(cart, cartItem.Product)).Any())) .ToListAsync(); //try to update cart items with new quantities and get warnings var warnings = await orderedCart.SelectAwait(async cartItem => new { ItemId = cartItem.Item.Id, Warnings = await _shoppingCartService.UpdateShoppingCartItemAsync(customer, cartItem.Item.Id, cartItem.Item.AttributesXml, cartItem.Item.CustomerEnteredPrice, cartItem.Item.RentalStartDateUtc, cartItem.Item.RentalEndDateUtc, cartItem.NewQuantity, true) }).ToListAsync(); //updated cart cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); //parse and save checkout attributes await ParseAndSaveCheckoutAttributesAsync(cart, form); //prepare model var model = new ShoppingCartModel(); model = await _shoppingCartModelFactory.PrepareShoppingCartModelAsync(model, cart); //update current warnings foreach (var warningItem in warnings.Where(warningItem => warningItem.Warnings.Any())) { //find shopping cart item model to display appropriate warnings var itemModel = model.Items.FirstOrDefault(item => item.Id == warningItem.ItemId); if (itemModel != null) itemModel.Warnings = warningItem.Warnings.Concat(itemModel.Warnings).Distinct().ToList(); } return View(model); } [HttpPost, ActionName("Cart")] [FormValueRequired("continueshopping")] public virtual async Task ContinueShopping() { var store = await _storeContext.GetCurrentStoreAsync(); var returnUrl = await _genericAttributeService.GetAttributeAsync(await _workContext.GetCurrentCustomerAsync(), NopCustomerDefaults.LastContinueShoppingPageAttribute, store.Id); if (!string.IsNullOrEmpty(returnUrl)) return Redirect(returnUrl); return RedirectToRoute("Homepage"); } [HttpPost, ActionName("Cart")] [FormValueRequired("checkout")] public virtual async Task StartCheckout(IFormCollection form) { var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); //parse and save checkout attributes await ParseAndSaveCheckoutAttributesAsync(cart, form); //validate attributes var checkoutAttributes = await _genericAttributeService.GetAttributeAsync(customer, NopCustomerDefaults.CheckoutAttributes, store.Id); var checkoutAttributeWarnings = await _shoppingCartService.GetShoppingCartWarningsAsync(cart, checkoutAttributes, true); if (checkoutAttributeWarnings.Any()) { //something wrong, redisplay the page with warnings var model = new ShoppingCartModel(); model = await _shoppingCartModelFactory.PrepareShoppingCartModelAsync(model, cart, validateCheckoutAttributes: true); return View(model); } var anonymousPermissed = _orderSettings.AnonymousCheckoutAllowed && _customerSettings.UserRegistrationType == UserRegistrationType.Disabled; if (anonymousPermissed || !await _customerService.IsGuestAsync(customer)) return RedirectToRoute("Checkout"); var cartProductIds = cart.Select(ci => ci.ProductId).ToArray(); var downloadableProductsRequireRegistration = _customerSettings.RequireRegistrationForDownloadableProducts && await _productService.HasAnyDownloadableProductAsync(cartProductIds); if (!_orderSettings.AnonymousCheckoutAllowed || downloadableProductsRequireRegistration) { //verify user identity (it may be facebook login page, or google, or local) return Challenge(); } return RedirectToRoute("LoginCheckoutAsGuest", new { returnUrl = Url.RouteUrl("ShoppingCart") }); } [HttpPost, ActionName("Cart")] [FormValueRequired("applydiscountcouponcode")] public virtual async Task ApplyDiscountCoupon(string discountcouponcode, IFormCollection form) { //trim if (discountcouponcode != null) discountcouponcode = discountcouponcode.Trim(); //cart var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); //parse and save checkout attributes await ParseAndSaveCheckoutAttributesAsync(cart, form); var model = new ShoppingCartModel(); if (!string.IsNullOrWhiteSpace(discountcouponcode)) { //we find even hidden records here. this way we can display a user-friendly message if it's expired var discounts = (await _discountService.GetAllDiscountsAsync(couponCode: discountcouponcode, showHidden: true)) .Where(d => d.RequiresCouponCode) .ToList(); if (discounts.Any()) { var userErrors = new List(); var anyValidDiscount = await discounts.AnyAwaitAsync(async discount => { var validationResult = await _discountService.ValidateDiscountAsync(discount, customer, [discountcouponcode]); userErrors.AddRange(validationResult.Errors); return validationResult.IsValid; }); if (anyValidDiscount) { //valid await _customerService.ApplyDiscountCouponCodeAsync(customer, discountcouponcode); model.DiscountBox.Messages.Add(await _localizationService.GetResourceAsync("ShoppingCart.DiscountCouponCode.Applied")); model.DiscountBox.IsApplied = true; } else { if (userErrors.Any()) //some user errors model.DiscountBox.Messages = userErrors; else //general error text model.DiscountBox.Messages.Add(await _localizationService.GetResourceAsync("ShoppingCart.DiscountCouponCode.WrongDiscount")); } } else //discount cannot be found model.DiscountBox.Messages.Add(await _localizationService.GetResourceAsync("ShoppingCart.DiscountCouponCode.CannotBeFound")); } else //empty coupon code model.DiscountBox.Messages.Add(await _localizationService.GetResourceAsync("ShoppingCart.DiscountCouponCode.Empty")); model = await _shoppingCartModelFactory.PrepareShoppingCartModelAsync(model, cart); return View(model); } [HttpPost, ActionName("Cart")] [FormValueRequired("applygiftcardcouponcode")] public virtual async Task ApplyGiftCard(string giftcardcouponcode, IFormCollection form) { //trim if (giftcardcouponcode != null) giftcardcouponcode = giftcardcouponcode.Trim(); //cart var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); //parse and save checkout attributes await ParseAndSaveCheckoutAttributesAsync(cart, form); var model = new ShoppingCartModel(); var validationError = await GetGiftCardValidationErrorAsync(cart, giftcardcouponcode); if (string.IsNullOrEmpty(validationError)) { await _customerService.ApplyGiftCardCouponCodeAsync(customer, giftcardcouponcode); model.GiftCardBox.Message = await _localizationService.GetResourceAsync("ShoppingCart.GiftCardCouponCode.Applied"); model.GiftCardBox.IsApplied = true; } else { model.GiftCardBox.Message = validationError; model.GiftCardBox.IsApplied = false; } model = await _shoppingCartModelFactory.PrepareShoppingCartModelAsync(model, cart); return View(model); } [HttpPost] public virtual async Task GetEstimateShipping(EstimateShippingModel model, IFormCollection form) { if (model == null) model = new EstimateShippingModel(); var errors = new List(); if (!_shippingSettings.EstimateShippingCityNameEnabled && string.IsNullOrEmpty(model.ZipPostalCode)) errors.Add(await _localizationService.GetResourceAsync("Shipping.EstimateShipping.ZipPostalCode.Required")); if (_shippingSettings.EstimateShippingCityNameEnabled && string.IsNullOrEmpty(model.City)) errors.Add(await _localizationService.GetResourceAsync("Shipping.EstimateShipping.City.Required")); if (model.CountryId == null || model.CountryId == 0) errors.Add(await _localizationService.GetResourceAsync("Shipping.EstimateShipping.Country.Required")); if (errors.Count > 0) return Json(new { Success = false, Errors = errors }); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(await _workContext.GetCurrentCustomerAsync(), ShoppingCartType.ShoppingCart, store.Id); //parse and save checkout attributes await ParseAndSaveCheckoutAttributesAsync(cart, form); var result = await _shoppingCartModelFactory.PrepareEstimateShippingResultModelAsync(cart, model, true); return Json(result); } [HttpPost, ActionName("Cart")] [FormValueRequired(FormValueRequirement.StartsWith, "removediscount-")] public virtual async Task RemoveDiscountCoupon(IFormCollection form) { var model = new ShoppingCartModel(); //get discount identifier var discountId = 0; foreach (var formValue in form.Keys) if (formValue.StartsWith("removediscount-", StringComparison.InvariantCultureIgnoreCase)) discountId = Convert.ToInt32(formValue["removediscount-".Length..]); var discount = await _discountService.GetDiscountByIdAsync(discountId); var customer = await _workContext.GetCurrentCustomerAsync(); if (discount != null) await _customerService.RemoveDiscountCouponCodeAsync(customer, discount.CouponCode); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); model = await _shoppingCartModelFactory.PrepareShoppingCartModelAsync(model, cart); return View(model); } [HttpPost, ActionName("Cart")] [FormValueRequired(FormValueRequirement.StartsWith, "removegiftcard-")] public virtual async Task RemoveGiftCardCode(IFormCollection form) { var model = new ShoppingCartModel(); //get gift card identifier var giftCardId = 0; foreach (var formValue in form.Keys) if (formValue.StartsWith("removegiftcard-", StringComparison.InvariantCultureIgnoreCase)) giftCardId = Convert.ToInt32(formValue["removegiftcard-".Length..]); var gc = await _giftCardService.GetGiftCardByIdAsync(giftCardId); var customer = await _workContext.GetCurrentCustomerAsync(); if (gc != null) await _customerService.RemoveGiftCardCouponCodeAsync(customer, gc.GiftCardCouponCode); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); model = await _shoppingCartModelFactory.PrepareShoppingCartModelAsync(model, cart); return View(model); } #endregion #region Wishlist public virtual async Task Wishlist(Guid? customerGuid) { if (!await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.ENABLE_WISHLIST)) return RedirectToRoute("Homepage"); var customer = customerGuid.HasValue ? await _customerService.GetCustomerByGuidAsync(customerGuid.Value) : await _workContext.GetCurrentCustomerAsync(); if (customer == null) return RedirectToRoute("Homepage"); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.Wishlist, store.Id); var model = new WishlistModel(); model = await _shoppingCartModelFactory.PrepareWishlistModelAsync(model, cart, !customerGuid.HasValue); return View(model); } [HttpPost, ActionName("Wishlist")] [FormValueRequired("updatecart")] public virtual async Task UpdateWishlist(IFormCollection form) { if (!await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.ENABLE_WISHLIST)) return RedirectToRoute("Homepage"); var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.Wishlist, store.Id); var allIdsToRemove = form.ContainsKey("removefromcart") ? form["removefromcart"].ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(int.Parse) .ToList() : new List(); //current warnings var innerWarnings = new Dictionary>(); foreach (var sci in cart) { var remove = allIdsToRemove.Contains(sci.Id); if (remove) await _shoppingCartService.DeleteShoppingCartItemAsync(sci); else { foreach (var formKey in form.Keys) if (formKey.Equals($"itemquantity{sci.Id}", StringComparison.InvariantCultureIgnoreCase)) { if (int.TryParse(form[formKey], out var newQuantity)) { var currSciWarnings = await _shoppingCartService.UpdateShoppingCartItemAsync(customer, sci.Id, sci.AttributesXml, sci.CustomerEnteredPrice, sci.RentalStartDateUtc, sci.RentalEndDateUtc, newQuantity, true); innerWarnings.Add(sci.Id, currSciWarnings); } break; } } } //updated wishlist cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.Wishlist, store.Id); var model = new WishlistModel(); model = await _shoppingCartModelFactory.PrepareWishlistModelAsync(model, cart); //update current warnings foreach (var kvp in innerWarnings) { //kvp = var sciId = kvp.Key; var warnings = kvp.Value; //find model var sciModel = model.Items.FirstOrDefault(x => x.Id == sciId); if (sciModel != null) foreach (var w in warnings) if (!sciModel.Warnings.Contains(w)) sciModel.Warnings.Add(w); } return View(model); } [HttpPost, ActionName("Wishlist")] [FormValueRequired("addtocartbutton")] public virtual async Task AddItemsToCartFromWishlist(Guid? customerGuid, IFormCollection form) { if (!await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.ENABLE_SHOPPING_CART)) return RedirectToRoute("Homepage"); if (!await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.ENABLE_WISHLIST)) return RedirectToRoute("Homepage"); var customer = await _workContext.GetCurrentCustomerAsync(); var pageCustomer = customerGuid.HasValue ? await _customerService.GetCustomerByGuidAsync(customerGuid.Value) : customer; if (pageCustomer == null) return RedirectToRoute("Homepage"); var store = await _storeContext.GetCurrentStoreAsync(); var pageCart = await _shoppingCartService.GetShoppingCartAsync(pageCustomer, ShoppingCartType.Wishlist, store.Id); var allWarnings = new List(); var countOfAddedItems = 0; var allIdsToAdd = form.ContainsKey("addtocart") ? form["addtocart"].ToString().Split(_separator, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToList() : []; foreach (var sci in pageCart) { if (allIdsToAdd.Contains(sci.Id)) { var product = await _productService.GetProductByIdAsync(sci.ProductId); var warnings = await _shoppingCartService.AddToCartAsync(customer, product, ShoppingCartType.ShoppingCart, store.Id, sci.AttributesXml, sci.CustomerEnteredPrice, sci.RentalStartDateUtc, sci.RentalEndDateUtc, sci.Quantity, true); if (!warnings.Any()) countOfAddedItems++; if (_shoppingCartSettings.MoveItemsFromWishlistToCart && //settings enabled !customerGuid.HasValue && //own wishlist !warnings.Any()) //no warnings ( already in the cart) { //let's remove the item from wishlist await _shoppingCartService.DeleteShoppingCartItemAsync(sci); } allWarnings.AddRange(warnings); } } if (countOfAddedItems > 0) { //redirect to the shopping cart page if (allWarnings.Any()) { _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Wishlist.AddToCart.Error")); } return RedirectToRoute("ShoppingCart"); } else { _notificationService.WarningNotification(await _localizationService.GetResourceAsync("Wishlist.AddToCart.NoAddedItems")); } //no items added. redisplay the wishlist page if (allWarnings.Any()) { _notificationService.ErrorNotification(await _localizationService.GetResourceAsync("Wishlist.AddToCart.Error")); } var cart = await _shoppingCartService.GetShoppingCartAsync(pageCustomer, ShoppingCartType.Wishlist, store.Id); var model = new WishlistModel(); model = await _shoppingCartModelFactory.PrepareWishlistModelAsync(model, cart, !customerGuid.HasValue); return View(model); } public virtual async Task EmailWishlist() { if (!await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.ENABLE_WISHLIST) || !_shoppingCartSettings.EmailWishlistEnabled) return RedirectToRoute("Homepage"); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(await _workContext.GetCurrentCustomerAsync(), ShoppingCartType.Wishlist, store.Id); if (!cart.Any()) return RedirectToRoute("Homepage"); var model = new WishlistEmailAFriendModel(); model = await _shoppingCartModelFactory.PrepareWishlistEmailAFriendModelAsync(model, false); return View(model); } [HttpPost, ActionName("EmailWishlist")] [FormValueRequired("send-email")] [ValidateCaptcha] public virtual async Task EmailWishlistSend(WishlistEmailAFriendModel model, bool captchaValid) { if (!await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.ENABLE_WISHLIST) || !_shoppingCartSettings.EmailWishlistEnabled) return RedirectToRoute("Homepage"); var customer = await _workContext.GetCurrentCustomerAsync(); var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.Wishlist, store.Id); if (!cart.Any()) return RedirectToRoute("Homepage"); //validate CAPTCHA if (_captchaSettings.Enabled && _captchaSettings.ShowOnEmailWishlistToFriendPage && !captchaValid) { ModelState.AddModelError(string.Empty, await _localizationService.GetResourceAsync("Common.WrongCaptchaMessage")); } //check whether the current customer is guest and ia allowed to email wishlist if (await _customerService.IsGuestAsync(customer) && !_shoppingCartSettings.AllowAnonymousUsersToEmailWishlist) { ModelState.AddModelError(string.Empty, await _localizationService.GetResourceAsync("Wishlist.EmailAFriend.OnlyRegisteredUsers")); } if (ModelState.IsValid) { //email await _workflowMessageService.SendWishlistEmailAFriendMessageAsync(customer, (await _workContext.GetWorkingLanguageAsync()).Id, model.YourEmailAddress, model.FriendEmail, _htmlFormatter.FormatText(model.PersonalMessage, false, true, false, false, false, false)); model.SuccessfullySent = true; model.Result = await _localizationService.GetResourceAsync("Wishlist.EmailAFriend.SuccessfullySent"); return View(model); } //If we got this far, something failed, redisplay form model = await _shoppingCartModelFactory.PrepareWishlistEmailAFriendModelAsync(model, true); return View(model); } #endregion }