/// <summary> /// Gets the final price /// </summary> /// <param name="product">Product</param> /// <param name="customer">The customer</param> /// <param name="overriddenProductPrice">Overridden product price. If specified, then it'll be used instead of a product price. For example, used with product attribute combinations</param> /// <param name="additionalCharge">Additional charge</param> /// <param name="includeDiscounts">A value indicating whether include discounts or not for final price computation</param> /// <param name="quantity">Shopping cart item quantity</param> /// <param name="rentalStartDate">Rental period start date (for rental products)</param> /// <param name="rentalEndDate">Rental period end date (for rental products)</param> /// <param name="discountAmount">Applied discount amount</param> /// <param name="appliedDiscounts">Applied discounts</param> /// <returns>Final price</returns> public virtual decimal GetFinalPrice(Product product, Customer customer, decimal? overriddenProductPrice, decimal additionalCharge, bool includeDiscounts, int quantity, DateTime? rentalStartDate, DateTime? rentalEndDate, out decimal discountAmount, out List<Discount> appliedDiscounts) { if (product == null) throw new ArgumentNullException("product"); discountAmount = decimal.Zero; appliedDiscounts = new List<Discount>(); var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_PRICE_MODEL_KEY, product.Id, overriddenProductPrice.HasValue ? overriddenProductPrice.Value.ToString(CultureInfo.InvariantCulture) : null, additionalCharge.ToString(CultureInfo.InvariantCulture), includeDiscounts, quantity, string.Join(",", customer.GetCustomerRoleIds()), _storeContext.CurrentStore.Id); var cacheTime = _catalogSettings.CacheProductPrices ? 60 : 0; //we do not cache price for rental products //otherwise, it can cause memory leaks (to store all possible date period combinations) if (product.IsRental) cacheTime = 0; var cachedPrice = _cacheManager.Get(cacheKey, cacheTime, () => { var result = new ProductPriceForCaching(); //initial price decimal price = overriddenProductPrice.HasValue ? overriddenProductPrice.Value : product.Price; //special price var specialPrice = product.GetSpecialPrice(); if (specialPrice.HasValue) price = specialPrice.Value; //tier prices if (product.HasTierPrices) { decimal? tierPrice = GetMinimumTierPrice(product, customer, quantity); if (tierPrice.HasValue) price = Math.Min(price, tierPrice.Value); } //additional charge price = price + additionalCharge; //rental products if (product.IsRental) if (rentalStartDate.HasValue && rentalEndDate.HasValue) price = price * product.GetRentalPeriods(rentalStartDate.Value, rentalEndDate.Value); if (includeDiscounts) { //discount List<Discount> tmpAppliedDiscounts; decimal tmpDiscountAmount = GetDiscountAmount(product, customer, price, out tmpAppliedDiscounts); price = price - tmpDiscountAmount; if (tmpAppliedDiscounts != null) { result.AppliedDiscountIds = tmpAppliedDiscounts.Select(x=>x.Id).ToList(); result.AppliedDiscountAmount = tmpDiscountAmount; } } if (price < decimal.Zero) price = decimal.Zero; result.Price = price; return result; }); if (includeDiscounts) { //Discount instance cannnot be cached between requests (when "catalogSettings.CacheProductPrices" is "true) //This is limitation of Entity Framework //That's why we load it here after working with cache foreach (var appliedDiscountId in cachedPrice.AppliedDiscountIds) { var appliedDiscount = _discountService.GetDiscountById(appliedDiscountId); if (appliedDiscount != null) appliedDiscounts.Add(appliedDiscount); } if (appliedDiscounts.Any()) { discountAmount = cachedPrice.AppliedDiscountAmount; } } return cachedPrice.Price; }