public virtual async Task <string> GetBasePriceInfoAsync(Product product, Customer customer = null, Currency currency = null, Money?priceAdjustment = null)
        {
            Guard.NotNull(product, nameof(product));

            customer ??= _workContext.CurrentCustomer;
            currency ??= _workContext.WorkingCurrency;

            if (product.BasePriceHasValue && product.BasePriceAmount != decimal.Zero)
            {
                var currentPrice = await GetFinalPriceAsync(product, null, customer, includeDiscounts : true);

                if (priceAdjustment.HasValue)
                {
                    currentPrice += priceAdjustment.Value;
                }

                var(price, _) = await _taxService.GetProductPriceAsync(product, currentPrice, customer : customer);

                var convertedPrice = _currencyService.ConvertFromPrimaryStoreCurrency(price);

                return(GetBasePriceInfo(product, convertedPrice, currency));
            }

            return(string.Empty);
        }
Example #2
0
        private async Task <OrderItem> PrepareDefaultOrderItemFromProductAsync(Order order, Product product)
        {
            var customer = await CustomerService.GetCustomerByIdAsync(order.CustomerId);

            var presetQty = 1;
            var price     = await _priceCalculationService.GetFinalPriceAsync(product, customer, decimal.Zero, true, presetQty);

            var(priceInclTax, _) = await _taxService.GetProductPriceAsync(product, price.finalPrice, includingTax : true, customer);

            var(priceExclTax, _) = await _taxService.GetProductPriceAsync(product, price.finalPrice, includingTax : false, customer);

            var orderItem = new OrderItem
            {
                OrderItemGuid       = new Guid(),
                UnitPriceExclTax    = priceExclTax,
                UnitPriceInclTax    = priceInclTax,
                PriceInclTax        = priceInclTax,
                PriceExclTax        = priceExclTax,
                OriginalProductCost = await _priceCalculationService.GetProductCostAsync(product, null),
                Quantity            = presetQty,
                ProductId           = product.Id,
                OrderId             = order.Id
            };

            return(orderItem);
        }
        public virtual async Task <string> GetBasePriceInfoAsync(Product product, Customer customer = null, Currency currency = null, Money?priceAdjustment = null)
        {
            Guard.NotNull(product, nameof(product));

            // Currency is only used for formatting, not for calculation.
            currency ??= _workContext.WorkingCurrency;
            customer ??= _workContext.CurrentCustomer;

            if (product.BasePriceHasValue && product.BasePriceAmount != decimal.Zero)
            {
                var currentPrice = await GetFinalPriceAmountAsync(product, decimal.Zero, customer, includeDiscounts : true);

                if (priceAdjustment.HasValue)
                {
                    currentPrice += priceAdjustment.Value.Amount;
                }

                var(price, _) = await _taxService.GetProductPriceAsync(product, new(currentPrice, _primaryCurrency), customer : customer);

                var convertedPrice = _currencyService.ConvertCurrency(price, currency);

                return(GetBasePriceInfo(product, convertedPrice, currency));
            }

            return(string.Empty);
        }
Example #4
0
        public async Task <(decimal taxRate, decimal sciSubTotalInclTax, decimal sciUnitPriceInclTax, decimal warrantyUnitPriceExclTax, decimal warrantyUnitPriceInclTax)> CalculateWarrantyTaxAsync(
            ShoppingCartItem sci,
            Customer customer,
            decimal sciSubTotalExclTax,
            decimal sciUnitPriceExclTax
            )
        {
            var product = await _productService.GetProductByIdAsync(sci.ProductId);

            var sciSubTotalInclTax = await _taxService.GetProductPriceAsync(product, sciSubTotalExclTax, true, customer);

            var sciUnitPriceInclTax = await _taxService.GetProductPriceAsync(product, sciUnitPriceExclTax, true, customer);

            var warrantyUnitPriceExclTax = decimal.Zero;

            (decimal price, decimal taxRate)warrantyUnitPriceInclTax;
            warrantyUnitPriceInclTax.price   = decimal.Zero;
            warrantyUnitPriceInclTax.taxRate = decimal.Zero;

            // warranty item handling
            ProductAttributeMapping warrantyPam = await _attributeUtilities.GetWarrantyAttributeMappingAsync(sci.AttributesXml);

            if (warrantyPam != null)
            {
                warrantyUnitPriceExclTax =
                    (await _productAttributeParser.ParseProductAttributeValuesAsync(sci.AttributesXml))
                    .Where(pav => pav.ProductAttributeMappingId == warrantyPam.Id)
                    .Select(pav => pav.PriceAdjustment)
                    .FirstOrDefault();

                // get warranty "product" - this is so the warranties have a tax category
                Product warrProduct = _importUtilities.GetExistingProductBySku("WARRPLACE_SKU");

                var isCustomerInTaxableState = await IsCustomerInTaxableStateAsync(customer);

                if (warrProduct == null)
                {
                    // taxed warranty price
                    warrantyUnitPriceInclTax = await _taxService.GetProductPriceAsync(product, warrantyUnitPriceExclTax, false, customer);
                }
                else
                {
                    warrantyUnitPriceInclTax = await _taxService.GetProductPriceAsync(warrProduct, warrantyUnitPriceExclTax, isCustomerInTaxableState, customer);
                }

                var productUnitPriceInclTax
                    = await _taxService.GetProductPriceAsync(product, sciUnitPriceExclTax - warrantyUnitPriceExclTax, true, customer);

                sciUnitPriceInclTax.price = productUnitPriceInclTax.price + warrantyUnitPriceInclTax.price;
                sciSubTotalInclTax.price  = sciUnitPriceInclTax.price * sci.Quantity;
            }

            return(warrantyUnitPriceInclTax.taxRate, sciSubTotalInclTax.price, sciUnitPriceInclTax.price, warrantyUnitPriceExclTax, warrantyUnitPriceInclTax.price);
        }
        /// <summary>
        /// Prepare paged shopping cart item list model
        /// </summary>
        /// <param name="searchModel">Shopping cart item search model</param>
        /// <param name="customer">Customer</param>
        /// <returns>
        /// A task that represents the asynchronous operation
        /// The task result contains the shopping cart item list model
        /// </returns>
        public virtual async Task <ShoppingCartItemListModel> PrepareShoppingCartItemListModelAsync(ShoppingCartItemSearchModel searchModel, Customer customer)
        {
            if (searchModel == null)
            {
                throw new ArgumentNullException(nameof(searchModel));
            }

            if (customer == null)
            {
                throw new ArgumentNullException(nameof(customer));
            }

            //get shopping cart items
            var items = (await _shoppingCartService.GetShoppingCartAsync(customer, searchModel.ShoppingCartType,
                                                                         searchModel.StoreId, searchModel.ProductId, searchModel.StartDate, searchModel.EndDate)).ToPagedList(searchModel);

            var isSearchProduct = searchModel.ProductId > 0;

            Product product = null;

            if (isSearchProduct)
            {
                product = await _productService.GetProductByIdAsync(searchModel.ProductId) ?? throw new Exception("Product is not found");
            }

            //prepare list model
            var model = await new ShoppingCartItemListModel().PrepareToGridAsync(searchModel, items, () =>
            {
                return(items
                       .OrderByDescending(item => item.CreatedOnUtc)
                       .SelectAwait(async item =>
                {
                    //fill in model values from the entity
                    var itemModel = item.ToModel <ShoppingCartItemModel>();

                    if (!isSearchProduct)
                    {
                        product = await _productService.GetProductByIdAsync(item.ProductId);
                    }

                    //convert dates to the user time
                    itemModel.UpdatedOn = await _dateTimeHelper.ConvertToUserTimeAsync(item.UpdatedOnUtc, DateTimeKind.Utc);

                    //fill in additional values (not existing in the entity)
                    itemModel.Store = (await _storeService.GetStoreByIdAsync(item.StoreId))?.Name ?? "Deleted";
                    itemModel.AttributeInfo = await _productAttributeFormatter.FormatAttributesAsync(product, item.AttributesXml, customer);
                    var(unitPrice, _, _) = await _shoppingCartService.GetUnitPriceAsync(item, true);
                    itemModel.UnitPrice = await _priceFormatter.FormatPriceAsync((await _taxService.GetProductPriceAsync(product, unitPrice)).price);
                    var(subTotal, _, _, _) = await _shoppingCartService.GetSubTotalAsync(item, true);
                    itemModel.Total = await _priceFormatter.FormatPriceAsync((await _taxService.GetProductPriceAsync(product, subTotal)).price);

                    //set product name since it does not survive mapping
                    itemModel.ProductName = product.Name;

                    return itemModel;
                }));
            });

            return(model);
        }
Example #6
0
        public async Task CanGetProductPricePriceIncludesTaxIncludingTaxTaxable()
        {
            var customer = new Customer();
            var product  = new Product();

            var(price, _) = await _taxService.GetProductPriceAsync(product, 0, 1000M, true, customer, true);

            price.Should().Be(1000);
            (price, _) = await _taxService.GetProductPriceAsync(product, 0, 1000M, true, customer, false);

            price.Should().Be(1100);
            (price, _) = await _taxService.GetProductPriceAsync(product, 0, 1000M, false, customer, true);

            price.Should().Be(909.0909090909090909090909091M);
            (price, _) = await _taxService.GetProductPriceAsync(product, 0, 1000M, false, customer, false);

            price.Should().Be(1000);
        }
Example #7
0
        public virtual async Task <string> GetBasePriceInfoAsync(Product product, Customer customer = null, Currency currency = null, decimal priceAdjustment = decimal.Zero)
        {
            Guard.NotNull(product, nameof(product));

            customer ??= _workContext.CurrentCustomer;
            currency ??= _workContext.WorkingCurrency;

            if (product.BasePriceHasValue && product.BasePriceAmount != decimal.Zero)
            {
                var currentPrice = await GetFinalPriceAsync(product, customer, includeDiscounts : true);

                var price = await _taxService.GetProductPriceAsync(product, decimal.Add(currentPrice, priceAdjustment), currency : currency, customer : customer);

                price = _currencyService.ConvertFromPrimaryStoreCurrency(price, currency);

                return(_priceFormatter.GetBasePriceInfo(product, price, currency));
            }

            return(string.Empty);
        }
Example #8
0
        public async Task <List <ProductDetailsModel.TierPriceModel> > CreateTierPriceModelAsync(Product product, decimal adjustment = decimal.Zero)
        {
            var model = await product.TierPrices
                        .OrderBy(x => x.Quantity)
                        .FilterByStore(_services.StoreContext.CurrentStore.Id)
                        .FilterForCustomer(_services.WorkContext.CurrentCustomer)
                        .ToList()
                        .RemoveDuplicatedQuantities()
                        .SelectAsync(async(tierPrice) =>
            {
                var m = new ProductDetailsModel.TierPriceModel
                {
                    Quantity = tierPrice.Quantity,
                };

                if (adjustment != 0 && tierPrice.CalculationMethod == TierPriceCalculationMethod.Percental && _catalogSettings.ApplyTierPricePercentageToAttributePriceAdjustments)
                {
                    adjustment -= (adjustment / 100 * tierPrice.Price);
                }
                else
                {
                    adjustment = decimal.Zero;
                }

                var adjustmentAmount = adjustment == 0 ? (Money?)null : new Money(adjustment, _currencyService.PrimaryCurrency);
                var priceBase        = default(Money);
                var taxRate          = decimal.Zero;
                var finalPriceBase   = await _priceCalculationService.GetFinalPriceAsync(product,
                                                                                         adjustmentAmount,
                                                                                         _services.WorkContext.CurrentCustomer,
                                                                                         _catalogSettings.DisplayTierPricesWithDiscounts,
                                                                                         tierPrice.Quantity, null, null, true);

                (priceBase, taxRate) = await _taxService.GetProductPriceAsync(product, finalPriceBase);
                m.Price = _currencyService.ConvertToWorkingCurrency(priceBase);

                return(m);
            })
                        .AsyncToList();

            return(model);
        }
Example #9
0
        public virtual async Task <ShoppingCartSubTotal> GetShoppingCartSubTotalAsync(IList <OrganizedShoppingCartItem> cart, bool?includeTax = null)
        {
            Guard.NotNull(cart, nameof(cart));

            var includingTax = includeTax ?? (_workContext.TaxDisplayType == TaxDisplayType.IncludingTax);
            var result       = new ShoppingCartSubTotal();

            if (!(cart?.Any() ?? false))
            {
                return(result);
            }

            var customer = cart.GetCustomer();
            var subTotalExclTaxWithoutDiscount = new Money(_primaryCurrency);
            var subTotalInclTaxWithoutDiscount = new Money(_primaryCurrency);

            foreach (var cartItem in cart)
            {
                if (cartItem.Item.Product == null)
                {
                    continue;
                }

                var     item = cartItem.Item;
                decimal taxRate = decimal.Zero;
                Money   itemExclTax, itemInclTax;

                await _productAttributeMaterializer.MergeWithCombinationAsync(item.Product, item.AttributeSelection);

                if (_primaryCurrency.RoundOrderItemsEnabled)
                {
                    // Gross > Net RoundFix.
                    var unitPrice = await _priceCalculationService.GetUnitPriceAsync(cartItem, true);

                    // Adaption to eliminate rounding issues.
                    (itemExclTax, taxRate) = await _taxService.GetProductPriceAsync(item.Product, unitPrice, false, customer : customer);

                    itemExclTax            = _primaryCurrency.AsMoney(itemExclTax.Amount * item.Quantity);
                    (itemInclTax, taxRate) = await _taxService.GetProductPriceAsync(item.Product, unitPrice, true, customer : customer);

                    itemInclTax = _primaryCurrency.AsMoney(itemInclTax.Amount * item.Quantity);
                }
                else
                {
                    var itemSubTotal = await _priceCalculationService.GetSubTotalAsync(cartItem, true);

                    (itemExclTax, taxRate) = await _taxService.GetProductPriceAsync(item.Product, itemSubTotal, false, customer : customer);

                    (itemInclTax, taxRate) = await _taxService.GetProductPriceAsync(item.Product, itemSubTotal, true, customer : customer);
                }

                subTotalExclTaxWithoutDiscount += itemExclTax;
                subTotalInclTaxWithoutDiscount += itemInclTax;

                result.TaxRates.Add(taxRate, itemInclTax.Amount - itemExclTax.Amount);
            }

            // Checkout attributes.
            if (customer != null)
            {
                var values = await _checkoutAttributeMaterializer.MaterializeCheckoutAttributeValuesAsync(customer.GenericAttributes.CheckoutAttributes);

                foreach (var value in values)
                {
                    var(attributePriceExclTax, _) = await _taxService.GetCheckoutAttributePriceAsync(value, false, customer);

                    var(attributePriceInclTax, taxRate) = await _taxService.GetCheckoutAttributePriceAsync(value, true, customer);

                    subTotalExclTaxWithoutDiscount += attributePriceExclTax.Amount;
                    subTotalInclTaxWithoutDiscount += attributePriceInclTax.Amount;

                    result.TaxRates.Add(taxRate, attributePriceInclTax.Amount - attributePriceExclTax.Amount);
                }
            }


            // Subtotal without discount.
            result.SubTotalWithoutDiscount = _primaryCurrency.AsMoney(includingTax ? subTotalInclTaxWithoutDiscount.Amount : subTotalExclTaxWithoutDiscount.Amount, true, true);

            // We calculate discount amount on order subtotal excl tax (discount first).
            var(discountAmountExclTax, appliedDiscount) = await this.GetOrderSubtotalDiscountAsync(subTotalExclTaxWithoutDiscount, customer);

            result.AppliedDiscount = appliedDiscount;

            if (subTotalExclTaxWithoutDiscount < discountAmountExclTax)
            {
                discountAmountExclTax = subTotalExclTaxWithoutDiscount;
            }

            var discountAmountInclTax = discountAmountExclTax;

            // Subtotal with discount (excl tax).
            var subTotalExclTaxWithDiscount = subTotalExclTaxWithoutDiscount - discountAmountExclTax;
            var subTotalInclTaxWithDiscount = subTotalExclTaxWithDiscount;

            // Add tax for shopping items & checkout attributes.
            var tempTaxRates = new Dictionary <decimal, decimal>(result.TaxRates);

            foreach (var kvp in tempTaxRates)
            {
                var taxRate   = kvp.Key;
                var taxAmount = kvp.Value;

                if (taxAmount != decimal.Zero)
                {
                    // Discount the tax amount that applies to subtotal items.
                    if (subTotalExclTaxWithoutDiscount > decimal.Zero)
                    {
                        var discountTax = result.TaxRates[taxRate] * (discountAmountExclTax.Amount / subTotalExclTaxWithoutDiscount.Amount);
                        discountAmountInclTax   += discountTax;
                        taxAmount                = _primaryCurrency.RoundIfEnabledFor(result.TaxRates[taxRate] - discountTax);
                        result.TaxRates[taxRate] = taxAmount;
                    }

                    // Subtotal with discount (incl tax).
                    subTotalInclTaxWithDiscount += taxAmount;
                }
            }

            discountAmountInclTax = _primaryCurrency.AsMoney(discountAmountInclTax.Amount);

            result.DiscountAmount       = _primaryCurrency.AsMoney(includingTax ? discountAmountInclTax.Amount : discountAmountExclTax.Amount, false);
            result.SubTotalWithDiscount = _primaryCurrency.AsMoney(includingTax ? subTotalInclTaxWithDiscount.Amount : subTotalExclTaxWithDiscount.Amount, true, true);

            return(result);
        }
Example #10
0
        public virtual async Task <string> FormatAttributesAsync(
            ProductVariantAttributeSelection selection,
            Product product,
            Customer customer              = null,
            string separator               = "<br />",
            bool htmlEncode                = true,
            bool includePrices             = true,
            bool includeProductAttributes  = true,
            bool includeGiftCardAttributes = true,
            bool includeHyperlinks         = true)
        {
            Guard.NotNull(selection, nameof(selection));
            Guard.NotNull(product, nameof(product));

            customer ??= _workContext.CurrentCustomer;

            using var pool = StringBuilderPool.Instance.Get(out var result);

            if (includeProductAttributes)
            {
                var languageId = _workContext.WorkingLanguage.Id;
                var attributes = await _productAttributeMaterializer.MaterializeProductVariantAttributesAsync(selection);

                var attributesDic = attributes.ToDictionary(x => x.Id);

                foreach (var kvp in selection.AttributesMap)
                {
                    if (!attributesDic.TryGetValue(kvp.Key, out var pva))
                    {
                        continue;
                    }

                    foreach (var value in kvp.Value)
                    {
                        var valueStr     = value.ToString().EmptyNull();
                        var pvaAttribute = string.Empty;

                        if (pva.IsListTypeAttribute())
                        {
                            var pvaValue = pva.ProductVariantAttributeValues.FirstOrDefault(x => x.Id == valueStr.ToInt());
                            if (pvaValue != null)
                            {
                                pvaAttribute = "{0}: {1}".FormatInvariant(
                                    pva.ProductAttribute.GetLocalized(x => x.Name, languageId),
                                    pvaValue.GetLocalized(x => x.Name, languageId));

                                if (includePrices)
                                {
                                    var attributeValuePriceAdjustment = await _priceCalculationService.GetProductVariantAttributeValuePriceAdjustmentAsync(pvaValue, product, customer, null, 1);

                                    var(priceAdjustmentBase, _) = await _taxService.GetProductPriceAsync(product, attributeValuePriceAdjustment, customer : customer);

                                    var priceAdjustment = _currencyService.ConvertToWorkingCurrency(priceAdjustmentBase);

                                    if (_shoppingCartSettings.ShowLinkedAttributeValueQuantity &&
                                        pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage &&
                                        pvaValue.Quantity > 1)
                                    {
                                        pvaAttribute = pvaAttribute + " × " + pvaValue.Quantity;
                                    }

                                    if (_catalogSettings.ShowVariantCombinationPriceAdjustment)
                                    {
                                        if (priceAdjustmentBase > decimal.Zero)
                                        {
                                            pvaAttribute += $" (+{priceAdjustment})";
                                        }
                                        else if (priceAdjustmentBase < decimal.Zero)
                                        {
                                            pvaAttribute += $" (+{priceAdjustment * -1})";
                                        }
                                    }
                                }

                                if (htmlEncode)
                                {
                                    pvaAttribute = pvaAttribute.HtmlEncode();
                                }
                            }
                        }
                        else if (pva.AttributeControlType == AttributeControlType.MultilineTextbox)
                        {
                            string attributeName = pva.ProductAttribute.GetLocalized(x => x.Name, languageId);

                            pvaAttribute = "{0}: {1}".FormatInvariant(
                                htmlEncode ? attributeName.HtmlEncode() : attributeName,
                                HtmlUtils.ConvertPlainTextToHtml(valueStr.HtmlEncode()));
                        }
                        else if (pva.AttributeControlType == AttributeControlType.FileUpload)
                        {
                            if (Guid.TryParse(valueStr, out var downloadGuid) && downloadGuid != Guid.Empty)
                            {
                                var download = await _db.Downloads
                                               .AsNoTracking()
                                               .Include(x => x.MediaFile)
                                               .Where(x => x.DownloadGuid == downloadGuid)
                                               .FirstOrDefaultAsync();

                                if (download?.MediaFile != null)
                                {
                                    var attributeText = string.Empty;
                                    var fileName      = htmlEncode
                                        ? download.MediaFile.Name.HtmlEncode()
                                        : download.MediaFile.Name;

                                    if (includeHyperlinks)
                                    {
                                        // TODO: (core) add a method for getting URL (use routing because it handles all SEO friendly URLs).
                                        var downloadLink = _webHelper.GetStoreLocation(false) + "download/getfileupload/?downloadId=" + download.DownloadGuid;
                                        attributeText = $"<a href=\"{downloadLink}\" class=\"fileuploadattribute\">{fileName}</a>";
                                    }
                                    else
                                    {
                                        attributeText = fileName;
                                    }

                                    string attributeName = pva.ProductAttribute.GetLocalized(a => a.Name, languageId);

                                    pvaAttribute = "{0}: {1}".FormatInvariant(
                                        htmlEncode ? attributeName.HtmlEncode() : attributeName,
                                        attributeText);
                                }
                            }
                        }
                        else
                        {
                            // TextBox, Datepicker
                            pvaAttribute = "{0}: {1}".FormatInvariant(pva.ProductAttribute.GetLocalized(x => x.Name, languageId), valueStr);

                            if (htmlEncode)
                            {
                                pvaAttribute = pvaAttribute.HtmlEncode();
                            }
                        }

                        result.Grow(pvaAttribute, separator);
                    }
                }
            }

            if (includeGiftCardAttributes && product.IsGiftCard)
            {
                var gci = selection.GiftCardInfo;
                if (gci != null)
                {
                    // Sender.
                    var giftCardFrom = product.GiftCardType == GiftCardType.Virtual
                        ? (await _localizationService.GetResourceAsync("GiftCardAttribute.From.Virtual")).FormatInvariant(gci.SenderName, gci.SenderEmail)
                        : (await _localizationService.GetResourceAsync("GiftCardAttribute.From.Physical")).FormatInvariant(gci.SenderName);

                    // Recipient.
                    var giftCardFor = product.GiftCardType == GiftCardType.Virtual
                        ? (await _localizationService.GetResourceAsync("GiftCardAttribute.For.Virtual")).FormatInvariant(gci.RecipientName, gci.RecipientEmail)
                        : (await _localizationService.GetResourceAsync("GiftCardAttribute.For.Physical")).FormatInvariant(gci.RecipientName);

                    if (htmlEncode)
                    {
                        giftCardFrom = giftCardFrom.HtmlEncode();
                        giftCardFor  = giftCardFor.HtmlEncode();
                    }

                    result.Grow(giftCardFrom, separator);
                    result.Grow(giftCardFor, separator);
                }
            }

            return(result.ToString());
        }
Example #11
0
        protected async override Task MoveShoppingCartItemsToOrderItemsAsync(
            PlaceOrderContainer details,
            Order order
            )
        {
            foreach (var sc in details.Cart)
            {
                var product = await _productService.GetProductByIdAsync(sc.ProductId);

                //prices
                var scUnitPrice = (await _shoppingCartService.GetUnitPriceAsync(sc, true)).unitPrice;
                var(scSubTotal, discountAmount, scDiscounts, _) = await _shoppingCartService.GetSubTotalAsync(sc, true);

                // var scUnitPriceInclTax =
                //     await _taxService.GetProductPriceAsync(product, scUnitPrice, true, details.Customer);
                var scUnitPriceExclTax =
                    await _taxService.GetProductPriceAsync(product, scUnitPrice, false, details.Customer);

                // var scSubTotalInclTax =
                //     await _taxService.GetProductPriceAsync(product, scSubTotal, true, details.Customer);
                var scSubTotalExclTax =
                    await _taxService.GetProductPriceAsync(product, scSubTotal, false, details.Customer);

                // custom - getting warranty tax
                var(_, scSubTotalInclTax, scUnitPriceInclTax, _, _) = await _warrantyTaxService.CalculateWarrantyTaxAsync(sc, details.Customer, scSubTotalExclTax.price, scUnitPriceExclTax.price);

                var discountAmountInclTax =
                    await _taxService.GetProductPriceAsync(product, discountAmount, true, details.Customer);

                var discountAmountExclTax =
                    await _taxService.GetProductPriceAsync(product, discountAmount, false, details.Customer);

                foreach (var disc in scDiscounts)
                {
                    if (!_discountService.ContainsDiscount(details.AppliedDiscounts, disc))
                    {
                        details.AppliedDiscounts.Add(disc);
                    }
                }



                //attributes
                var attributeDescription =
                    await _productAttributeFormatter.FormatAttributesAsync(product, sc.AttributesXml, details.Customer);

                var itemWeight = await _shippingService.GetShoppingCartItemWeightAsync(sc);

                //save order item
                var orderItem = new OrderItem
                {
                    OrderItemGuid         = Guid.NewGuid(),
                    OrderId               = order.Id,
                    ProductId             = product.Id,
                    UnitPriceInclTax      = scUnitPriceInclTax,
                    UnitPriceExclTax      = scUnitPriceExclTax.price,
                    PriceInclTax          = scSubTotalInclTax,
                    PriceExclTax          = scSubTotalExclTax.price,
                    OriginalProductCost   = await _priceCalculationService.GetProductCostAsync(product, sc.AttributesXml),
                    AttributeDescription  = attributeDescription,
                    AttributesXml         = sc.AttributesXml,
                    Quantity              = sc.Quantity,
                    DiscountAmountInclTax = discountAmountInclTax.price,
                    DiscountAmountExclTax = discountAmountExclTax.price,
                    DownloadCount         = 0,
                    IsDownloadActivated   = false,
                    LicenseDownloadId     = 0,
                    ItemWeight            = itemWeight,
                    RentalStartDateUtc    = sc.RentalStartDateUtc,
                    RentalEndDateUtc      = sc.RentalEndDateUtc
                };

                await _orderService.InsertOrderItemAsync(orderItem);

                //gift cards
                await AddGiftCardsAsync(product, sc.AttributesXml, sc.Quantity, orderItem, scUnitPriceExclTax.price);

                //inventory
                await _productService.AdjustInventoryAsync(product, -sc.Quantity, sc.AttributesXml,
                                                           string.Format(await _localizationService.GetResourceAsync("Admin.StockQuantityHistory.Messages.PlaceOrder"), order.Id));
            }

            //clear shopping cart
            details.Cart.ToList().ForEach(async sci => await _shoppingCartService.DeleteShoppingCartItemAsync(sci, false));
        }
Example #12
0
        private async Task <WishlistModel.ShoppingCartItemModel> PrepareWishlistCartItemModelAsync(OrganizedShoppingCartItem cartItem)
        {
            Guard.NotNull(cartItem, nameof(cartItem));

            var item     = cartItem.Item;
            var product  = item.Product;
            var customer = item.Customer;
            var currency = Services.WorkContext.WorkingCurrency;

            await _productAttributeMaterializer.MergeWithCombinationAsync(product, item.AttributeSelection);

            var productSeName = await SeoExtensions.GetActiveSlugAsync(product);

            var model = new WishlistModel.ShoppingCartItemModel
            {
                Id                  = item.Id,
                Sku                 = product.Sku,
                ProductId           = product.Id,
                ProductName         = product.GetLocalized(x => x.Name),
                ProductSeName       = productSeName,
                ProductUrl          = await _productUrlHelper.GetProductUrlAsync(productSeName, cartItem),
                EnteredQuantity     = item.Quantity,
                MinOrderAmount      = product.OrderMinimumQuantity,
                MaxOrderAmount      = product.OrderMaximumQuantity,
                QuantityStep        = product.QuantityStep > 0 ? product.QuantityStep : 1,
                ShortDesc           = product.GetLocalized(x => x.ShortDescription),
                ProductType         = product.ProductType,
                VisibleIndividually = product.Visibility != ProductVisibility.Hidden,
                CreatedOnUtc        = item.UpdatedOnUtc,
                DisableBuyButton    = product.DisableBuyButton,
            };

            if (item.BundleItem != null)
            {
                model.BundleItem.Id             = item.BundleItem.Id;
                model.BundleItem.DisplayOrder   = item.BundleItem.DisplayOrder;
                model.BundleItem.HideThumbnail  = item.BundleItem.HideThumbnail;
                model.BundlePerItemPricing      = item.BundleItem.BundleProduct.BundlePerItemPricing;
                model.BundlePerItemShoppingCart = item.BundleItem.BundleProduct.BundlePerItemShoppingCart;
                model.AttributeInfo             = await _productAttributeFormatter.FormatAttributesAsync(item.AttributeSelection, product, customer,
                                                                                                         includePrices : false, includeGiftCardAttributes : false, includeHyperlinks : false);

                var bundleItemName = item.BundleItem.GetLocalized(x => x.Name);
                if (bundleItemName.HasValue())
                {
                    model.ProductName = bundleItemName;
                }

                var bundleItemShortDescription = item.BundleItem.GetLocalized(x => x.ShortDescription);
                if (bundleItemShortDescription.HasValue())
                {
                    model.ShortDesc = bundleItemShortDescription;
                }

                if (model.BundlePerItemPricing && model.BundlePerItemShoppingCart)
                {
                    (var bundleItemPriceBase, var bundleItemTaxRate) = await _taxService.GetProductPriceAsync(product, await _priceCalculationService.GetSubTotalAsync(cartItem, true));

                    var bundleItemPrice = _currencyService.ConvertFromPrimaryCurrency(bundleItemPriceBase.Amount, currency);
                    model.BundleItem.PriceWithDiscount = bundleItemPrice.ToString();
                }
            }
            else
            {
                model.AttributeInfo = await _productAttributeFormatter.FormatAttributesAsync(item.AttributeSelection, product, customer);
            }

            var allowedQuantities = product.ParseAllowedQuantities();

            foreach (var qty in allowedQuantities)
            {
                model.AllowedQuantities.Add(new SelectListItem
                {
                    Text     = qty.ToString(),
                    Value    = qty.ToString(),
                    Selected = item.Quantity == qty
                });
            }

            var quantityUnit = await _db.QuantityUnits.FindByIdAsync(product.QuantityUnitId ?? 0);

            if (quantityUnit != null)
            {
                model.QuantityUnitName = quantityUnit.GetLocalized(x => x.Name);
            }

            if (product.IsRecurring)
            {
                model.RecurringInfo = string.Format(T("ShoppingCart.RecurringPeriod"),
                                                    product.RecurringCycleLength, product.RecurringCyclePeriod.GetLocalizedEnum());
            }

            if (product.CallForPrice)
            {
                model.UnitPrice = T("Products.CallForPrice");
            }
            else
            {
                var unitPriceWithDiscount = await _priceCalculationService.GetUnitPriceAsync(cartItem, true);

                var unitPriceBaseWithDiscount = await _taxService.GetProductPriceAsync(product, unitPriceWithDiscount);

                unitPriceWithDiscount = _currencyService.ConvertFromPrimaryCurrency(unitPriceBaseWithDiscount.Price.Amount, currency);

                model.UnitPrice = unitPriceWithDiscount.ToString();
            }

            // Subtotal and discount.
            if (product.CallForPrice)
            {
                model.SubTotal = T("Products.CallForPrice");
            }
            else
            {
                var cartItemSubTotalWithDiscount = await _priceCalculationService.GetSubTotalAsync(cartItem, true);

                var cartItemSubTotalWithDiscountBase = await _taxService.GetProductPriceAsync(product, cartItemSubTotalWithDiscount);

                cartItemSubTotalWithDiscount = _currencyService.ConvertFromPrimaryCurrency(cartItemSubTotalWithDiscountBase.Price.Amount, currency);

                model.SubTotal = cartItemSubTotalWithDiscount.ToString();

                // Display an applied discount amount.
                var cartItemSubTotalWithoutDiscount = await _priceCalculationService.GetSubTotalAsync(cartItem, false);

                var cartItemSubTotalWithoutDiscountBase = await _taxService.GetProductPriceAsync(product, cartItemSubTotalWithoutDiscount);

                var cartItemSubTotalDiscountBase = cartItemSubTotalWithoutDiscountBase.Price - cartItemSubTotalWithDiscountBase.Price;

                if (cartItemSubTotalDiscountBase > decimal.Zero)
                {
                    var shoppingCartItemDiscount = _currencyService.ConvertFromPrimaryCurrency(cartItemSubTotalDiscountBase.Amount, currency);
                    model.Discount = shoppingCartItemDiscount.ToString();
                }
            }

            if (item.BundleItem != null)
            {
                if (_shoppingCartSettings.ShowProductBundleImagesOnShoppingCart)
                {
                    model.Image = await PrepareCartItemPictureModelAsync(product, _mediaSettings.CartThumbBundleItemPictureSize, model.ProductName, item.AttributeSelection);
                }
            }
            else
            {
                if (_shoppingCartSettings.ShowProductImagesOnShoppingCart)
                {
                    model.Image = await PrepareCartItemPictureModelAsync(product, _mediaSettings.CartThumbPictureSize, model.ProductName, item.AttributeSelection);
                }
            }

            var itemWarnings = new List <string>();
            var itemIsValid  = await _shoppingCartValidator.ValidateCartAsync(new List <OrganizedShoppingCartItem> {
                cartItem
            }, itemWarnings);

            if (!itemIsValid)
            {
                model.Warnings.AddRange(itemWarnings);
            }

            if (cartItem.ChildItems != null)
            {
                foreach (var childItem in cartItem.ChildItems.Where(x => x.Item.Id != item.Id))
                {
                    var childModel = await PrepareWishlistCartItemModelAsync(childItem);

                    model.ChildItems.Add(childModel);
                }
            }

            return(model);
        }
Example #13
0
        public override async Task MapAsync(OrganizedShoppingCartItem from, TModel to, dynamic parameters = null)
        {
            Guard.NotNull(from, nameof(from));

            // TODO: (ms) (core) Be certain that product is null if it is removed from cart.
            if (from.Item.Product == null)
            {
                return;
            }

            var item             = from.Item;
            var product          = from.Item.Product;
            var customer         = item.Customer;
            var currency         = _services.WorkContext.WorkingCurrency;
            var shoppingCartType = item.ShoppingCartType;

            await _productAttributeMaterializer.MergeWithCombinationAsync(product, item.AttributeSelection);

            var productSeName = await product.GetActiveSlugAsync();

            // General model data
            to.Id            = item.Id;
            to.Sku           = product.Sku;
            to.ProductId     = product.Id;
            to.ProductName   = product.GetLocalized(x => x.Name);
            to.ProductSeName = productSeName;
            to.ProductUrl    = await _productUrlHelper.GetProductUrlAsync(productSeName, from);

            to.EnteredQuantity     = item.Quantity;
            to.MinOrderAmount      = product.OrderMinimumQuantity;
            to.MaxOrderAmount      = product.OrderMaximumQuantity;
            to.QuantityStep        = product.QuantityStep > 0 ? product.QuantityStep : 1;
            to.ShortDesc           = product.GetLocalized(x => x.ShortDescription);
            to.ProductType         = product.ProductType;
            to.VisibleIndividually = product.Visibility != ProductVisibility.Hidden;
            to.CreatedOnUtc        = item.UpdatedOnUtc;

            if (item.BundleItem != null)
            {
                to.BundleItem.Id             = item.BundleItem.Id;
                to.BundleItem.DisplayOrder   = item.BundleItem.DisplayOrder;
                to.BundleItem.HideThumbnail  = item.BundleItem.HideThumbnail;
                to.BundlePerItemPricing      = item.BundleItem.BundleProduct.BundlePerItemPricing;
                to.BundlePerItemShoppingCart = item.BundleItem.BundleProduct.BundlePerItemShoppingCart;
                to.AttributeInfo             = await _productAttributeFormatter.FormatAttributesAsync(
                    item.AttributeSelection,
                    product,
                    customer,
                    includePrices : false,
                    includeGiftCardAttributes : true,
                    includeHyperlinks : true);

                var bundleItemName = item.BundleItem.GetLocalized(x => x.Name);
                if (bundleItemName.Value.HasValue())
                {
                    to.ProductName = bundleItemName;
                }

                var bundleItemShortDescription = item.BundleItem.GetLocalized(x => x.ShortDescription);
                if (bundleItemShortDescription.Value.HasValue())
                {
                    to.ShortDesc = bundleItemShortDescription;
                }

                if (to.BundlePerItemPricing && to.BundlePerItemShoppingCart)
                {
                    var bundleItemSubTotalWithDiscountBase = await _taxService.GetProductPriceAsync(product, await _priceCalculationService.GetSubTotalAsync(from, true));

                    var bundleItemSubTotalWithDiscount = _currencyService.ConvertFromPrimaryCurrency(bundleItemSubTotalWithDiscountBase.Price.Amount, currency);
                    to.BundleItem.PriceWithDiscount = bundleItemSubTotalWithDiscount.ToString();
                }
            }
            else
            {
                to.AttributeInfo = await _productAttributeFormatter.FormatAttributesAsync(item.AttributeSelection, product, customer);
            }

            var allowedQuantities = product.ParseAllowedQuantities();

            foreach (var quantity in allowedQuantities)
            {
                to.AllowedQuantities.Add(new SelectListItem
                {
                    Text     = quantity.ToString(),
                    Value    = quantity.ToString(),
                    Selected = item.Quantity == quantity
                });
            }

            var quantityUnit = await _db.QuantityUnits.GetQuantityUnitByIdAsync(product.QuantityUnitId ?? 0, _catalogSettings.ShowDefaultQuantityUnit);

            if (quantityUnit != null)
            {
                to.QuantityUnitName = quantityUnit.GetLocalized(x => x.Name);
            }

            if (product.IsRecurring)
            {
                to.RecurringInfo = T("ShoppingCart.RecurringPeriod", product.RecurringCycleLength, product.RecurringCyclePeriod.GetLocalizedEnum());
            }

            if (product.CallForPrice)
            {
                to.UnitPrice = T("Products.CallForPrice");
            }
            else
            {
                var unitPriceWithDiscountBase = await _taxService.GetProductPriceAsync(product, await _priceCalculationService.GetUnitPriceAsync(from, true));

                var unitPriceWithDiscount = _currencyService.ConvertFromPrimaryCurrency(unitPriceWithDiscountBase.Price.Amount, currency);
                to.UnitPrice = unitPriceWithDiscount.ToString();
            }

            // Subtotal and discount.
            if (product.CallForPrice)
            {
                to.SubTotal = T("Products.CallForPrice");
            }
            else
            {
                var cartItemSubTotalWithDiscount = await _priceCalculationService.GetSubTotalAsync(from, true);

                var cartItemSubTotalWithDiscountBase = await _taxService.GetProductPriceAsync(product, cartItemSubTotalWithDiscount);

                cartItemSubTotalWithDiscount = _currencyService.ConvertFromPrimaryCurrency(cartItemSubTotalWithDiscountBase.Price.Amount, currency);

                to.SubTotal = cartItemSubTotalWithDiscount.ToString();

                // Display an applied discount amount.
                var cartItemSubTotalWithoutDiscount = await _priceCalculationService.GetSubTotalAsync(from, false);

                var cartItemSubTotalWithoutDiscountBase = await _taxService.GetProductPriceAsync(product, cartItemSubTotalWithoutDiscount);

                var cartItemSubTotalDiscountBase = cartItemSubTotalWithoutDiscountBase.Price - cartItemSubTotalWithDiscountBase.Price;

                if (cartItemSubTotalDiscountBase > decimal.Zero)
                {
                    var itemDiscount = _currencyService.ConvertFromPrimaryCurrency(cartItemSubTotalDiscountBase.Amount, currency);
                    to.Discount = itemDiscount.ToString();
                }
            }

            if (item.BundleItem != null)
            {
                if (_shoppingCartSettings.ShowProductBundleImagesOnShoppingCart)
                {
                    await from.MapAsync(to.Image, _mediaSettings.CartThumbBundleItemPictureSize, to.ProductName);
                }
            }
            else
            {
                if (_shoppingCartSettings.ShowProductImagesOnShoppingCart)
                {
                    await from.MapAsync(to.Image, _mediaSettings.CartThumbPictureSize, to.ProductName);
                }
            }

            var itemWarnings = new List <string>();
            var isItemValid  = await _shoppingCartValidator.ValidateCartItemsAsync(new[] { from }, itemWarnings);

            if (!isItemValid)
            {
                itemWarnings.Each(x => to.Warnings.Add(x));
            }
        }
        /// <summary>
        /// Formats attributes
        /// </summary>
        /// <param name="product">Product</param>
        /// <param name="attributesXml">Attributes in XML format</param>
        /// <param name="customer">Customer</param>
        /// <param name="separator">Separator</param>
        /// <param name="htmlEncode">A value indicating whether to encode (HTML) values</param>
        /// <param name="renderPrices">A value indicating whether to render prices</param>
        /// <param name="renderProductAttributes">A value indicating whether to render product attributes</param>
        /// <param name="renderGiftCardAttributes">A value indicating whether to render gift card attributes</param>
        /// <param name="allowHyperlinks">A value indicating whether to HTML hyperink tags could be rendered (if required)</param>
        /// <returns>
        /// A task that represents the asynchronous operation
        /// The task result contains the attributes
        /// </returns>
        public virtual async Task <string> FormatAttributesAsync(Product product, string attributesXml,
                                                                 Customer customer, string separator = "<br />", bool htmlEncode           = true, bool renderPrices = true,
                                                                 bool renderProductAttributes        = true, bool renderGiftCardAttributes = true,
                                                                 bool allowHyperlinks = true)
        {
            var result = new StringBuilder();

            //attributes
            if (renderProductAttributes)
            {
                foreach (var attribute in await _productAttributeParser.ParseProductAttributeMappingsAsync(attributesXml))
                {
                    var productAttribute = await _productAttributeService.GetProductAttributeByIdAsync(attribute.ProductAttributeId);

                    var attributeName = await _localizationService.GetLocalizedAsync(productAttribute, a => a.Name, (await _workContext.GetWorkingLanguageAsync()).Id);

                    //attributes without values
                    if (!attribute.ShouldHaveValues())
                    {
                        foreach (var value in _productAttributeParser.ParseValues(attributesXml, attribute.Id))
                        {
                            var formattedAttribute = string.Empty;
                            if (attribute.AttributeControlType == AttributeControlType.MultilineTextbox)
                            {
                                //encode (if required)
                                if (htmlEncode)
                                {
                                    attributeName = WebUtility.HtmlEncode(attributeName);
                                }

                                //we never encode multiline textbox input
                                formattedAttribute = $"{attributeName}: {HtmlHelper.FormatText(value, false, true, false, false, false, false)}";
                            }
                            else if (attribute.AttributeControlType == AttributeControlType.FileUpload)
                            {
                                //file upload
                                Guid.TryParse(value, out var downloadGuid);
                                var download = await _downloadService.GetDownloadByGuidAsync(downloadGuid);

                                if (download != null)
                                {
                                    var fileName = $"{download.Filename ?? download.DownloadGuid.ToString()}{download.Extension}";

                                    //encode (if required)
                                    if (htmlEncode)
                                    {
                                        fileName = WebUtility.HtmlEncode(fileName);
                                    }

                                    var attributeText = allowHyperlinks ? $"<a href=\"{_webHelper.GetStoreLocation(false)}download/getfileupload/?downloadId={download.DownloadGuid}\" class=\"fileuploadattribute\">{fileName}</a>"
                                        : fileName;

                                    //encode (if required)
                                    if (htmlEncode)
                                    {
                                        attributeName = WebUtility.HtmlEncode(attributeName);
                                    }

                                    formattedAttribute = $"{attributeName}: {attributeText}";
                                }
                            }
                            else
                            {
                                //other attributes (textbox, datepicker)
                                formattedAttribute = $"{attributeName}: {value}";

                                //encode (if required)
                                if (htmlEncode)
                                {
                                    formattedAttribute = WebUtility.HtmlEncode(formattedAttribute);
                                }
                            }

                            if (string.IsNullOrEmpty(formattedAttribute))
                            {
                                continue;
                            }

                            if (result.Length > 0)
                            {
                                result.Append(separator);
                            }
                            result.Append(formattedAttribute);
                        }
                    }
                    //product attribute values
                    else
                    {
                        foreach (var attributeValue in await _productAttributeParser.ParseProductAttributeValuesAsync(attributesXml, attribute.Id))
                        {
                            var formattedAttribute = $"{attributeName}: {await _localizationService.GetLocalizedAsync(attributeValue, a => a.Name, (await _workContext.GetWorkingLanguageAsync()).Id)}";

                            if (renderPrices)
                            {
                                if (attributeValue.PriceAdjustmentUsePercentage)
                                {
                                    if (attributeValue.PriceAdjustment > decimal.Zero)
                                    {
                                        formattedAttribute += string.Format(
                                            await _localizationService.GetResourceAsync("FormattedAttributes.PriceAdjustment"),
                                            "+", attributeValue.PriceAdjustment.ToString("G29"), "%");
                                    }
                                    else if (attributeValue.PriceAdjustment < decimal.Zero)
                                    {
                                        formattedAttribute += string.Format(
                                            await _localizationService.GetResourceAsync("FormattedAttributes.PriceAdjustment"),
                                            string.Empty, attributeValue.PriceAdjustment.ToString("G29"), "%");
                                    }
                                }
                                else
                                {
                                    var attributeValuePriceAdjustment = await _priceCalculationService.GetProductAttributeValuePriceAdjustmentAsync(product, attributeValue, customer);

                                    var(priceAdjustmentBase, _) = await _taxService.GetProductPriceAsync(product, attributeValuePriceAdjustment, customer);

                                    var priceAdjustment = await _currencyService.ConvertFromPrimaryStoreCurrencyAsync(priceAdjustmentBase, await _workContext.GetWorkingCurrencyAsync());

                                    if (priceAdjustmentBase > decimal.Zero)
                                    {
                                        formattedAttribute += string.Format(
                                            await _localizationService.GetResourceAsync("FormattedAttributes.PriceAdjustment"),
                                            "+", await _priceFormatter.FormatPriceAsync(priceAdjustment, false, false), string.Empty);
                                    }
                                    else if (priceAdjustmentBase < decimal.Zero)
                                    {
                                        formattedAttribute += string.Format(
                                            await _localizationService.GetResourceAsync("FormattedAttributes.PriceAdjustment"),
                                            "-", await _priceFormatter.FormatPriceAsync(-priceAdjustment, false, false), string.Empty);
                                    }
                                }
                            }

                            //display quantity
                            if (_shoppingCartSettings.RenderAssociatedAttributeValueQuantity && attributeValue.AttributeValueType == AttributeValueType.AssociatedToProduct)
                            {
                                //render only when more than 1
                                if (attributeValue.Quantity > 1)
                                {
                                    formattedAttribute += string.Format(await _localizationService.GetResourceAsync("ProductAttributes.Quantity"), attributeValue.Quantity);
                                }
                            }

                            //encode (if required)
                            if (htmlEncode)
                            {
                                formattedAttribute = WebUtility.HtmlEncode(formattedAttribute);
                            }

                            if (string.IsNullOrEmpty(formattedAttribute))
                            {
                                continue;
                            }

                            if (result.Length > 0)
                            {
                                result.Append(separator);
                            }
                            result.Append(formattedAttribute);
                        }
                    }
                }
            }

            //gift cards
            if (!renderGiftCardAttributes)
            {
                return(result.ToString());
            }

            if (!product.IsGiftCard)
            {
                return(result.ToString());
            }

            _productAttributeParser.GetGiftCardAttribute(attributesXml, out var giftCardRecipientName, out var giftCardRecipientEmail, out var giftCardSenderName, out var giftCardSenderEmail, out var _);

            //sender
            var giftCardFrom = product.GiftCardType == GiftCardType.Virtual ?
                               string.Format(await _localizationService.GetResourceAsync("GiftCardAttribute.From.Virtual"), giftCardSenderName, giftCardSenderEmail) :
                               string.Format(await _localizationService.GetResourceAsync("GiftCardAttribute.From.Physical"), giftCardSenderName);
            //recipient
            var giftCardFor = product.GiftCardType == GiftCardType.Virtual ?
                              string.Format(await _localizationService.GetResourceAsync("GiftCardAttribute.For.Virtual"), giftCardRecipientName, giftCardRecipientEmail) :
                              string.Format(await _localizationService.GetResourceAsync("GiftCardAttribute.For.Physical"), giftCardRecipientName);

            //encode (if required)
            if (htmlEncode)
            {
                giftCardFrom = WebUtility.HtmlEncode(giftCardFrom);
                giftCardFor  = WebUtility.HtmlEncode(giftCardFor);
            }

            if (!string.IsNullOrEmpty(result.ToString()))
            {
                result.Append(separator);
            }

            result.Append(giftCardFrom);
            result.Append(separator);
            result.Append(giftCardFor);

            return(result.ToString());
        }
        public override async Task <(decimal discountAmount, List <Discount> appliedDiscounts, decimal subTotalWithoutDiscount, decimal subTotalWithDiscount, SortedDictionary <decimal, decimal> taxRates)> GetShoppingCartSubTotalAsync(
            IList <ShoppingCartItem> cart,
            bool includingTax)
        {
            var discountAmount          = decimal.Zero;
            var appliedDiscounts        = new List <Discount>();
            var subTotalWithoutDiscount = decimal.Zero;
            var subTotalWithDiscount    = decimal.Zero;
            var taxRates = new SortedDictionary <decimal, decimal>();

            if (!cart.Any())
            {
                return(discountAmount, appliedDiscounts, subTotalWithoutDiscount, subTotalWithDiscount, taxRates);
            }

            //get the customer
            var customer = await _customerService.GetShoppingCartCustomerAsync(cart);

            //sub totals
            var subTotalExclTaxWithoutDiscount = decimal.Zero;
            var subTotalInclTaxWithoutDiscount = decimal.Zero;

            foreach (var shoppingCartItem in cart)
            {
                var sciSubTotal = (await _shoppingCartService.GetSubTotalAsync(shoppingCartItem, true)).subTotal;
                var product     = await _productService.GetProductByIdAsync(shoppingCartItem.ProductId);

                var(sciExclTax, taxRate) = await _taxService.GetProductPriceAsync(product, sciSubTotal, false, customer);

                // custom
                var(_, sciInclTax) = await _warrantyTaxService.CalculateWarrantyTaxAsync(shoppingCartItem, customer, sciExclTax);

                subTotalExclTaxWithoutDiscount += sciExclTax;
                subTotalInclTaxWithoutDiscount += sciInclTax;

                //tax rates
                var sciTax = sciInclTax - sciExclTax;
                if (taxRate <= decimal.Zero || sciTax <= decimal.Zero)
                {
                    continue;
                }

                if (!taxRates.ContainsKey(taxRate))
                {
                    taxRates.Add(taxRate, sciTax);
                }
                else
                {
                    taxRates[taxRate] = taxRates[taxRate] + sciTax;
                }
            }

            //checkout attributes
            if (customer != null)
            {
                var checkoutAttributesXml = await _genericAttributeService.GetAttributeAsync <string>(customer, NopCustomerDefaults.CheckoutAttributes, (await _storeContext.GetCurrentStoreAsync()).Id);

                var attributeValues = _checkoutAttributeParser.ParseCheckoutAttributeValues(checkoutAttributesXml);
                if (attributeValues != null)
                {
                    await foreach (var(attribute, values) in attributeValues)
                    {
                        await foreach (var attributeValue in values)
                        {
                            var(caExclTax, taxRate) = await _taxService.GetCheckoutAttributePriceAsync(attribute, attributeValue, false, customer);

                            var(caInclTax, _) = await _taxService.GetCheckoutAttributePriceAsync(attribute, attributeValue, true, customer);

                            subTotalExclTaxWithoutDiscount += caExclTax;
                            subTotalInclTaxWithoutDiscount += caInclTax;

                            //tax rates
                            var caTax = caInclTax - caExclTax;
                            if (taxRate <= decimal.Zero || caTax <= decimal.Zero)
                            {
                                continue;
                            }

                            if (!taxRates.ContainsKey(taxRate))
                            {
                                taxRates.Add(taxRate, caTax);
                            }
                            else
                            {
                                taxRates[taxRate] = taxRates[taxRate] + caTax;
                            }
                        }
                    }
                }
            }

            //subtotal without discount
            subTotalWithoutDiscount = includingTax ? subTotalInclTaxWithoutDiscount : subTotalExclTaxWithoutDiscount;
            if (subTotalWithoutDiscount < decimal.Zero)
            {
                subTotalWithoutDiscount = decimal.Zero;
            }

            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                subTotalWithoutDiscount = await _priceCalculationService.RoundPriceAsync(subTotalWithoutDiscount);
            }

            //We calculate discount amount on order subtotal excl tax (discount first)
            //calculate discount amount ('Applied to order subtotal' discount)
            decimal discountAmountExclTax;

            (discountAmountExclTax, appliedDiscounts) = await GetOrderSubtotalDiscountAsync(customer, subTotalExclTaxWithoutDiscount);

            if (subTotalExclTaxWithoutDiscount < discountAmountExclTax)
            {
                discountAmountExclTax = subTotalExclTaxWithoutDiscount;
            }
            var discountAmountInclTax = discountAmountExclTax;
            //subtotal with discount (excl tax)
            var subTotalExclTaxWithDiscount = subTotalExclTaxWithoutDiscount - discountAmountExclTax;
            var subTotalInclTaxWithDiscount = subTotalExclTaxWithDiscount;

            //add tax for shopping items & checkout attributes
            var tempTaxRates = new Dictionary <decimal, decimal>(taxRates);

            foreach (var kvp in tempTaxRates)
            {
                var taxRate  = kvp.Key;
                var taxValue = kvp.Value;

                if (taxValue == decimal.Zero)
                {
                    continue;
                }

                //discount the tax amount that applies to subtotal items
                if (subTotalExclTaxWithoutDiscount > decimal.Zero)
                {
                    var discountTax = taxRates[taxRate] * (discountAmountExclTax / subTotalExclTaxWithoutDiscount);
                    discountAmountInclTax += discountTax;
                    taxValue = taxRates[taxRate] - discountTax;
                    if (_shoppingCartSettings.RoundPricesDuringCalculation)
                    {
                        taxValue = await _priceCalculationService.RoundPriceAsync(taxValue);
                    }
                    taxRates[taxRate] = taxValue;
                }

                //subtotal with discount (incl tax)
                subTotalInclTaxWithDiscount += taxValue;
            }

            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                discountAmountInclTax = await _priceCalculationService.RoundPriceAsync(discountAmountInclTax);

                discountAmountExclTax = await _priceCalculationService.RoundPriceAsync(discountAmountExclTax);
            }

            if (includingTax)
            {
                subTotalWithDiscount = subTotalInclTaxWithDiscount;
                discountAmount       = discountAmountInclTax;
            }
            else
            {
                subTotalWithDiscount = subTotalExclTaxWithDiscount;
                discountAmount       = discountAmountExclTax;
            }

            if (subTotalWithDiscount < decimal.Zero)
            {
                subTotalWithDiscount = decimal.Zero;
            }

            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                subTotalWithDiscount = await _priceCalculationService.RoundPriceAsync(subTotalWithDiscount);
            }

            return(discountAmount, appliedDiscounts, subTotalWithoutDiscount, subTotalWithDiscount, taxRates);
        }