/// <summary>
        /// Gets shopping cart shipping total
        /// </summary>
        /// <param name="cart">Cart</param>
        /// <param name="includingTax">A value indicating whether calculated price should include tax</param>
        /// <param name="taxRate">Applied tax rate</param>
        /// <param name="appliedDiscount">Applied discount</param>
        /// <returns>Shipping total</returns>
        public virtual decimal?GetShoppingCartShippingTotal(IList <ShoppingCartItem> cart, bool includingTax,
                                                            out decimal taxRate, out Discount appliedDiscount)
        {
            decimal?shippingTotal      = null;
            decimal?shippingTotalTaxed = null;

            appliedDiscount = null;
            taxRate         = decimal.Zero;

            var customer = cart.GetCustomer();

            bool isFreeShipping = IsFreeShipping(cart);

            if (isFreeShipping)
            {
                return(decimal.Zero);
            }

            ShippingOption shippingOption = null;

            if (customer != null)
            {
                shippingOption = customer.GetAttribute <ShippingOption>(SystemCustomerAttributeNames.SelectedShippingOption, _genericAttributeService, _storeContext.CurrentStore.Id);
            }

            if (shippingOption != null)
            {
                //use last shipping option (get from cache)

                var pickUpInStore = _shippingSettings.AllowPickUpInStore &&
                                    customer.GetAttribute <bool>(SystemCustomerAttributeNames.SelectedPickUpInStore, _storeContext.CurrentStore.Id);
                if (pickUpInStore)
                {
                    //"pick up in store" fee
                    //we do not adjust shipping rate ("AdjustShippingRate" method) for pickup in store
                    shippingTotal = _shippingSettings.PickUpInStoreFee;
                }
                else
                {
                    //adjust shipping rate
                    shippingTotal = AdjustShippingRate(shippingOption.Rate, cart, out appliedDiscount);
                }
            }
            else
            {
                //use fixed rate (if possible)
                Address shippingAddress = null;
                if (customer != null)
                {
                    shippingAddress = customer.ShippingAddress;
                }

                var shippingRateComputationMethods = _shippingService.LoadActiveShippingRateComputationMethods(_storeContext.CurrentStore.Id);
                if (shippingRateComputationMethods == null || shippingRateComputationMethods.Count == 0)
                {
                    throw new NopException("Shipping rate computation method could not be loaded");
                }

                if (shippingRateComputationMethods.Count == 1)
                {
                    var shippingRateComputationMethod = shippingRateComputationMethods[0];

                    var     shippingOptionRequests = _shippingService.CreateShippingOptionRequests(cart, shippingAddress);
                    decimal?fixedRate = null;
                    foreach (var shippingOptionRequest in shippingOptionRequests)
                    {
                        //calculate fixed rates for each request-package
                        var fixedRateTmp = shippingRateComputationMethod.GetFixedRate(shippingOptionRequest);
                        if (fixedRateTmp.HasValue)
                        {
                            if (!fixedRate.HasValue)
                            {
                                fixedRate = decimal.Zero;
                            }

                            fixedRate += fixedRateTmp.Value;
                        }
                    }

                    if (fixedRate.HasValue)
                    {
                        //adjust shipping rate
                        shippingTotal = AdjustShippingRate(fixedRate.Value, cart, out appliedDiscount);
                    }
                }
            }

            if (shippingTotal.HasValue)
            {
                if (shippingTotal.Value < decimal.Zero)
                {
                    shippingTotal = decimal.Zero;
                }

                //round
                if (_shoppingCartSettings.RoundPricesDuringCalculation)
                {
                    shippingTotal = RoundingHelper.RoundPrice(shippingTotal.Value);
                }

                shippingTotalTaxed = _taxService.GetShippingPrice(shippingTotal.Value,
                                                                  includingTax,
                                                                  customer,
                                                                  out taxRate);

                //round
                if (_shoppingCartSettings.RoundPricesDuringCalculation)
                {
                    shippingTotalTaxed = RoundingHelper.RoundPrice(shippingTotalTaxed.Value);
                }
            }

            return(shippingTotalTaxed);
        }
        GetShoppingCartTotal(IList <ShoppingCartItem> cart, bool?useLoyaltyPoints = null, bool usePaymentMethodAdditionalFee = true)
        {
            var redeemedLoyaltyPoints       = 0;
            var redeemedLoyaltyPointsAmount = decimal.Zero;

            var customer = _workContext.CurrentCustomer;

            string paymentMethodSystemName = "";

            if (customer != null)
            {
                paymentMethodSystemName = customer.GetUserFieldFromEntity <string>(
                    SystemCustomerFieldNames.SelectedPaymentMethod,
                    _workContext.CurrentStore.Id);
            }

            //subtotal without tax
            var subTotal = await GetShoppingCartSubTotal(cart, false);

            decimal subTotalWithDiscountBase = subTotal.subTotalWithDiscount;

            //subtotal with discount
            decimal subtotalBase = subTotalWithDiscountBase;

            //shipping without tax
            var shippingTotal = await GetShoppingCartShippingTotal(cart, false);

            decimal?shoppingCartShipping = shippingTotal.shoppingCartShippingTotal;

            //payment method additional fee without tax
            decimal paymentMethodAdditionalFeeWithoutTax = decimal.Zero;

            if (usePaymentMethodAdditionalFee && !string.IsNullOrEmpty(paymentMethodSystemName))
            {
                var paymentMethodAdditionalFee = await _paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName);

                paymentMethodAdditionalFeeWithoutTax = (await _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer)).paymentPrice;
            }

            //tax
            decimal shoppingCartTax = (await GetTaxTotal(cart, usePaymentMethodAdditionalFee)).taxtotal;

            //order total
            decimal resultTemp = decimal.Zero;

            resultTemp += subtotalBase;
            if (shoppingCartShipping.HasValue)
            {
                resultTemp += shoppingCartShipping.Value;
            }
            resultTemp += paymentMethodAdditionalFeeWithoutTax;
            resultTemp += shoppingCartTax;
            if (_shoppingCartSettings.RoundPrices)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp, _workContext.WorkingCurrency);
            }
            #region Order total discount

            var totalDiscount = await GetOrderTotalDiscount(customer, _workContext.WorkingCurrency, resultTemp);

            var discountAmount   = totalDiscount.orderTotalDiscount;
            var appliedDiscounts = totalDiscount.appliedDiscounts;

            //sub totals with discount
            if (resultTemp < discountAmount)
            {
                discountAmount = resultTemp;
            }

            //reduce subtotal
            resultTemp -= discountAmount;

            if (resultTemp < decimal.Zero)
            {
                resultTemp = decimal.Zero;
            }
            if (_shoppingCartSettings.RoundPrices)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp, _workContext.WorkingCurrency);
            }

            #endregion

            #region Applied gift vouchers

            var appliedGiftVouchers = new List <AppliedGiftVoucher>();
            //we don't apply gift vouchers for recurring products
            var giftVouchers = await GetActiveGiftVouchers(customer, _workContext.WorkingCurrency);

            if (giftVouchers != null)
            {
                foreach (var gc in giftVouchers)
                {
                    if (resultTemp > decimal.Zero)
                    {
                        decimal remainingAmount = gc.GetGiftVoucherRemainingAmount();
                        decimal amountCanBeUsed = resultTemp > remainingAmount ?
                                                  remainingAmount :
                                                  resultTemp;

                        //reduce subtotal
                        resultTemp -= amountCanBeUsed;

                        var appliedGiftVoucher = new AppliedGiftVoucher
                        {
                            GiftVoucher     = gc,
                            AmountCanBeUsed = amountCanBeUsed
                        };
                        appliedGiftVouchers.Add(appliedGiftVoucher);
                    }
                }
            }

            #endregion

            if (resultTemp < decimal.Zero)
            {
                resultTemp = decimal.Zero;
            }
            if (_shoppingCartSettings.RoundPrices)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp, _workContext.WorkingCurrency);
            }

            if (!shoppingCartShipping.HasValue)
            {
                //we have errors
                return(null, discountAmount, appliedDiscounts, appliedGiftVouchers, redeemedLoyaltyPoints, redeemedLoyaltyPointsAmount);
            }

            decimal orderTotal = resultTemp;

            #region Loyalty points

            if (_loyaltyPointsSettings.Enabled)
            {
                if (!useLoyaltyPoints.HasValue)
                {
                    useLoyaltyPoints = customer.GetUserFieldFromEntity <bool>(SystemCustomerFieldNames.UseLoyaltyPointsDuringCheckout, _workContext.CurrentStore.Id);
                }

                if (useLoyaltyPoints.Value)
                {
                    int loyaltyPointsBalance = await _loyaltyPointsService.GetLoyaltyPointsBalance(customer.Id, _workContext.CurrentStore.Id);

                    if (CheckMinimumLoyaltyPointsToUseRequirement(loyaltyPointsBalance))
                    {
                        decimal loyaltyPointsBalanceAmount = await ConvertLoyaltyPointsToAmount(loyaltyPointsBalance);

                        if (orderTotal > decimal.Zero)
                        {
                            if (orderTotal > loyaltyPointsBalanceAmount)
                            {
                                redeemedLoyaltyPoints       = loyaltyPointsBalance;
                                redeemedLoyaltyPointsAmount = await _currencyService.ConvertFromPrimaryStoreCurrency(loyaltyPointsBalanceAmount, _workContext.WorkingCurrency);
                            }
                            else
                            {
                                redeemedLoyaltyPointsAmount = orderTotal;
                                redeemedLoyaltyPoints       = ConvertAmountToLoyaltyPoints(await _currencyService.ConvertToPrimaryStoreCurrency(redeemedLoyaltyPointsAmount, _workContext.WorkingCurrency));
                            }
                        }
                    }
                }
            }

            #endregion

            orderTotal -= redeemedLoyaltyPointsAmount;
            if (_shoppingCartSettings.RoundPrices)
            {
                orderTotal = RoundingHelper.RoundPrice(orderTotal, _workContext.WorkingCurrency);
            }

            return(orderTotal, discountAmount, appliedDiscounts, appliedGiftVouchers, redeemedLoyaltyPoints, redeemedLoyaltyPointsAmount);
        }
        /// <summary>
        /// Gets shopping cart subtotal
        /// </summary>
        /// <param name="cart">Cart</param>
        /// <param name="includingTax">A value indicating whether calculated price should include tax</param>
        /// <param name="discountAmount">Applied discount amount</param>
        /// <param name="appliedDiscount">Applied discount</param>
        /// <param name="subTotalWithoutDiscount">Sub total (without discount)</param>
        /// <param name="subTotalWithDiscount">Sub total (with discount)</param>
        /// <param name="taxRates">Tax rates (of order sub total)</param>
        public virtual void GetShoppingCartSubTotal(IList <ShoppingCartItem> cart,
                                                    bool includingTax,
                                                    out decimal discountAmount, out Discount appliedDiscount,
                                                    out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount,
                                                    out SortedDictionary <decimal, decimal> taxRates)
        {
            discountAmount          = decimal.Zero;
            appliedDiscount         = null;
            subTotalWithoutDiscount = decimal.Zero;
            subTotalWithDiscount    = decimal.Zero;
            taxRates = new SortedDictionary <decimal, decimal>();

            if (cart.Count == 0)
            {
                return;
            }

            //get the customer
            Customer customer = cart.GetCustomer();

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

            foreach (var shoppingCartItem in cart)
            {
                decimal sciSubTotal = _priceCalculationService.GetSubTotal(shoppingCartItem);

                decimal taxRate;
                decimal sciExclTax = _taxService.GetProductPrice(shoppingCartItem.Product, sciSubTotal, false, customer, out taxRate);
                decimal sciInclTax = _taxService.GetProductPrice(shoppingCartItem.Product, sciSubTotal, true, customer, out taxRate);
                subTotalExclTaxWithoutDiscount += sciExclTax;
                subTotalInclTaxWithoutDiscount += sciInclTax;

                //tax rates
                decimal sciTax = sciInclTax - sciExclTax;
                if (taxRate > decimal.Zero && sciTax > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, sciTax);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + sciTax;
                    }
                }
            }

            //checkout attributes
            if (customer != null)
            {
                var checkoutAttributesXml = customer.GetAttribute <string>(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService, _storeContext.CurrentStore.Id);
                var attributeValues       = _checkoutAttributeParser.ParseCheckoutAttributeValues(checkoutAttributesXml);
                if (attributeValues != null)
                {
                    foreach (var attributeValue in attributeValues)
                    {
                        decimal taxRate;

                        decimal caExclTax = _taxService.GetCheckoutAttributePrice(attributeValue, false, customer, out taxRate);
                        decimal caInclTax = _taxService.GetCheckoutAttributePrice(attributeValue, true, customer, out taxRate);
                        subTotalExclTaxWithoutDiscount += caExclTax;
                        subTotalInclTaxWithoutDiscount += caInclTax;

                        //tax rates
                        decimal caTax = caInclTax - caExclTax;
                        if (taxRate > decimal.Zero && caTax > decimal.Zero)
                        {
                            if (!taxRates.ContainsKey(taxRate))
                            {
                                taxRates.Add(taxRate, caTax);
                            }
                            else
                            {
                                taxRates[taxRate] = taxRates[taxRate] + caTax;
                            }
                        }
                    }
                }
            }

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

            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                subTotalWithoutDiscount = RoundingHelper.RoundPrice(subTotalWithoutDiscount);
            }

            //We calculate discount amount on order subtotal excl tax (discount first)
            //calculate discount amount ('Applied to order subtotal' discount)
            decimal discountAmountExclTax = GetOrderSubtotalDiscount(customer, subTotalExclTaxWithoutDiscount, out appliedDiscount);

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

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

            foreach (KeyValuePair <decimal, decimal> kvp in tempTaxRates)
            {
                decimal taxRate  = kvp.Key;
                decimal taxValue = kvp.Value;

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

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

            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                discountAmountInclTax = RoundingHelper.RoundPrice(discountAmountInclTax);
                discountAmountExclTax = RoundingHelper.RoundPrice(discountAmountExclTax);
            }

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

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

            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                subTotalWithDiscount = RoundingHelper.RoundPrice(subTotalWithDiscount);
            }
        }
        /// <summary>
        /// Gets shopping cart shipping total
        /// </summary>
        /// <param name="cart">Cart</param>
        /// <param name="includingTax">A value indicating whether calculated price should include tax</param>
        /// <param name="taxRate">Applied tax rate</param>
        /// <param name="appliedDiscount">Applied discount</param>
        /// <returns>Shipping total</returns>
        public virtual async Task <(decimal?shoppingCartShippingTotal, decimal taxRate, List <ApplyDiscount> appliedDiscounts)> GetShoppingCartShippingTotal(IList <ShoppingCartItem> cart, bool includingTax)
        {
            decimal?shippingTotal      = null;
            decimal?shippingTotalTaxed = null;
            var     appliedDiscounts   = new List <ApplyDiscount>();
            var     taxRate            = decimal.Zero;

            var customer = _workContext.CurrentCustomer;
            var currency = await _currencyService.GetPrimaryExchangeRateCurrency();

            bool isFreeShipping = await IsFreeShipping(cart);

            if (isFreeShipping)
            {
                return(decimal.Zero, taxRate, appliedDiscounts);
            }

            ShippingOption shippingOption = null;

            if (customer != null)
            {
                shippingOption = customer.GetUserFieldFromEntity <ShippingOption>(SystemCustomerFieldNames.SelectedShippingOption, _workContext.CurrentStore.Id);
            }

            if (shippingOption != null)
            {
                var rate = shippingOption.Rate;
                var adjustshipingRate = await AdjustShippingRate(rate, cart);

                shippingTotal    = adjustshipingRate.shippingRate;
                appliedDiscounts = adjustshipingRate.appliedDiscounts;
            }
            else
            {
                //use fixed rate (if possible)
                Address shippingAddress = null;
                if (customer != null)
                {
                    shippingAddress = customer.ShippingAddress;
                }

                var shippingRateMethods = await _shippingService.LoadActiveShippingRateCalculationProviders(_workContext.CurrentCustomer, _workContext.CurrentStore.Id, cart);

                if (!shippingRateMethods.Any() && !_shippingSettings.AllowPickUpInStore)
                {
                    throw new GrandException("Shipping rate  method could not be loaded");
                }

                if (shippingRateMethods.Count == 1)
                {
                    var shippingRateMethod = shippingRateMethods[0];

                    var shippingOptionRequests = await _shippingService.CreateShippingOptionRequests(customer, cart,
                                                                                                     shippingAddress,
                                                                                                     _workContext.CurrentStore);

                    decimal?fixedRate = null;
                    foreach (var shippingOptionRequest in shippingOptionRequests)
                    {
                        //calculate fixed rates for each request-package
                        var fixedRateTmp = await shippingRateMethod.GetFixedRate(shippingOptionRequest);

                        if (fixedRateTmp.HasValue)
                        {
                            if (!fixedRate.HasValue)
                            {
                                fixedRate = decimal.Zero;
                            }

                            fixedRate += fixedRateTmp.Value;
                        }
                    }

                    if (fixedRate.HasValue)
                    {
                        //adjust shipping rate
                        var adjustShippingRate = await AdjustShippingRate(fixedRate.Value, cart);

                        shippingTotal    = adjustShippingRate.shippingRate;
                        appliedDiscounts = adjustShippingRate.appliedDiscounts;
                    }
                }
            }

            if (shippingTotal.HasValue)
            {
                if (shippingTotal.Value < decimal.Zero)
                {
                    shippingTotal = decimal.Zero;
                }

                //round
                if (_shoppingCartSettings.RoundPrices)
                {
                    shippingTotal = RoundingHelper.RoundPrice(shippingTotal.Value, currency);
                }

                var shippingPrice = await _taxService.GetShippingPrice(shippingTotal.Value, includingTax, customer);

                shippingTotalTaxed = shippingPrice.shippingPrice;
                taxRate            = shippingPrice.taxRate;

                //round
                if (_shoppingCartSettings.RoundPrices)
                {
                    shippingTotalTaxed = RoundingHelper.RoundPrice(shippingTotalTaxed.Value, currency);
                }
            }

            return(shippingTotalTaxed, taxRate, appliedDiscounts);
        }
        /// <summary>
        /// Gets tax
        /// </summary>
        /// <param name="cart">Shopping cart</param>
        /// <param name="taxRates">Tax rates</param>
        /// <param name="usePaymentMethodAdditionalFee">A value indicating whether we should use payment method additional fee when calculating tax</param>
        /// <returns>Tax total</returns>
        public virtual async Task <(decimal taxtotal, SortedDictionary <decimal, decimal> taxRates)> GetTaxTotal(IList <ShoppingCartItem> cart, bool usePaymentMethodAdditionalFee = true)
        {
            if (cart == null)
            {
                throw new ArgumentNullException(nameof(cart));
            }

            var taxRates = new SortedDictionary <decimal, decimal>();

            var    customer = _workContext.CurrentCustomer;
            string paymentMethodSystemName = "";

            if (customer != null)
            {
                paymentMethodSystemName = customer.GetUserFieldFromEntity <string>(
                    SystemCustomerFieldNames.SelectedPaymentMethod,
                    _workContext.CurrentStore.Id);
            }

            //order sub total (items + checkout attributes)
            decimal subTotalTaxTotal = decimal.Zero;

            var shoppingCartSubTotal = await GetShoppingCartSubTotal(cart, false);

            SortedDictionary <decimal, decimal> orderSubTotalTaxRates = shoppingCartSubTotal.taxRates;

            foreach (KeyValuePair <decimal, decimal> kvp in orderSubTotalTaxRates)
            {
                decimal taxRate  = kvp.Key;
                decimal taxValue = kvp.Value;
                subTotalTaxTotal += taxValue;

                if (taxRate > decimal.Zero && taxValue > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, taxValue);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + taxValue;
                    }
                }
            }

            //shipping
            decimal shippingTax = decimal.Zero;

            if (_taxSettings.ShippingIsTaxable)
            {
                decimal taxRate;
                var     shippingTotalExcl = await GetShoppingCartShippingTotal(cart, false);

                decimal?shippingExclTax = shippingTotalExcl.shoppingCartShippingTotal;

                var shippingTotalIncl = await GetShoppingCartShippingTotal(cart, true);

                decimal?shippingInclTax = shippingTotalIncl.shoppingCartShippingTotal;

                taxRate = shippingTotalIncl.taxRate;

                if (shippingExclTax.HasValue && shippingInclTax.HasValue)
                {
                    shippingTax = shippingInclTax.Value - shippingExclTax.Value;
                    //ensure that tax is equal or greater than zero
                    if (shippingTax < decimal.Zero)
                    {
                        shippingTax = decimal.Zero;
                    }

                    //tax rates
                    if (taxRate > decimal.Zero && shippingTax > decimal.Zero)
                    {
                        if (!taxRates.ContainsKey(taxRate))
                        {
                            taxRates.Add(taxRate, shippingTax);
                        }
                        else
                        {
                            taxRates[taxRate] = taxRates[taxRate] + shippingTax;
                        }
                    }
                }
            }

            //payment method additional fee
            decimal paymentMethodAdditionalFeeTax = decimal.Zero;

            if (usePaymentMethodAdditionalFee && _taxSettings.PaymentMethodAdditionalFeeIsTaxable)
            {
                decimal taxRate;
                decimal paymentMethodAdditionalFee = await _paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName);

                var additionalFeeExclTax = await _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer);

                decimal paymentMethodAdditionalFeeExclTax = additionalFeeExclTax.paymentPrice;

                var additionalFeeInclTax = await _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, true, customer);

                decimal paymentMethodAdditionalFeeInclTax = additionalFeeInclTax.paymentPrice;

                taxRate = additionalFeeInclTax.taxRate;

                paymentMethodAdditionalFeeTax = paymentMethodAdditionalFeeInclTax - paymentMethodAdditionalFeeExclTax;
                //ensure that tax is equal or greater than zero
                if (paymentMethodAdditionalFeeTax < decimal.Zero)
                {
                    paymentMethodAdditionalFeeTax = decimal.Zero;
                }

                //tax rates
                if (taxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, paymentMethodAdditionalFeeTax);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + paymentMethodAdditionalFeeTax;
                    }
                }
            }

            //add at least one tax rate (0%)
            if (!taxRates.Any())
            {
                taxRates.Add(decimal.Zero, decimal.Zero);
            }

            //summarize taxes
            decimal taxTotal = subTotalTaxTotal + shippingTax + paymentMethodAdditionalFeeTax;

            //ensure that tax is equal or greater than zero
            if (taxTotal < decimal.Zero)
            {
                taxTotal = decimal.Zero;
            }
            //round tax
            if (_shoppingCartSettings.RoundPrices)
            {
                taxTotal = RoundingHelper.RoundPrice(taxTotal, _workContext.WorkingCurrency);
            }
            return(taxTotal, taxRates);
        }
        /// <summary>
        /// Generate a feed
        /// </summary>
        /// <param name="stream">Stream</param>
        /// <param name="store">Store</param>
        /// <returns>Generated feed</returns>
        public async Task GenerateFeed(Stream stream, Store store)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            if (store == null)
            {
                throw new ArgumentNullException("store");
            }

            const string googleBaseNamespace = "http://base.google.com/ns/1.0";

            var settings = new XmlWriterSettings {
                Encoding = Encoding.UTF8,
                Async    = true,
            };

            //language
            var languageId = "";
            var languages  = await _languageService.GetAllLanguages(storeId : store.Id);

            //if we have only one language, let's use it
            if (languages.Count == 1)
            {
                //let's use the first one
                var language = languages.FirstOrDefault();
                languageId = language != null ? language.Id : "";
            }
            //otherwise, use the current one
            if (String.IsNullOrEmpty(languageId))
            {
                languageId = _workContext.WorkingLanguage.Id;
            }

            //we load all google products here using one SQL request (performance optimization)
            var allGoogleProducts = await _googleService.GetAll();

            using (var writer = XmlWriter.Create(stream, settings))
            {
                //Generate feed according to the following specs: http://www.google.com/support/merchants/bin/answer.py?answer=188494&expand=GB
                writer.WriteStartDocument();
                writer.WriteStartElement("rss");
                writer.WriteAttributeString("version", "2.0");
                writer.WriteAttributeString("xmlns", "g", null, googleBaseNamespace);
                writer.WriteStartElement("channel");
                writer.WriteElementString("title", "Google Base feed");
                writer.WriteElementString("link", "http://base.google.com/base/");
                writer.WriteElementString("description", "Information about products");


                var products1 = (await _productService.SearchProducts(storeId: store.Id, visibleIndividuallyOnly: true)).products;
                foreach (var product1 in products1)
                {
                    var productsToProcess = new List <Product>();
                    switch (product1.ProductType)
                    {
                    case ProductType.SimpleProduct:
                    {
                        //simple product doesn't have child products
                        productsToProcess.Add(product1);
                    }
                    break;

                    case ProductType.GroupedProduct:
                    {
                        //grouped products could have several child products
                        var associatedProducts = await _productService.GetAssociatedProducts(product1.Id, store.Id);

                        productsToProcess.AddRange(associatedProducts);
                    }
                    break;

                    default:
                        continue;
                    }
                    foreach (var product in productsToProcess)
                    {
                        writer.WriteStartElement("item");

                        #region Basic Product Information

                        //id [id]- An identifier of the item
                        writer.WriteElementString("g", "id", googleBaseNamespace, product.Id.ToString());

                        //title [title] - Title of the item
                        writer.WriteStartElement("title");
                        var title = product.GetLocalized(x => x.Name, languageId);
                        //title should be not longer than 70 characters
                        if (title.Length > 70)
                        {
                            title = title.Substring(0, 70);
                        }
                        writer.WriteCData(title);
                        writer.WriteEndElement(); // title

                        //description [description] - Description of the item
                        writer.WriteStartElement("description");
                        string description = product.GetLocalized(x => x.FullDescription, languageId);
                        if (String.IsNullOrEmpty(description))
                        {
                            description = product.GetLocalized(x => x.ShortDescription, languageId);
                        }
                        if (String.IsNullOrEmpty(description))
                        {
                            description = product.GetLocalized(x => x.Name, languageId); //description is required
                        }
                        //resolving character encoding issues in your data feed
                        description = StripInvalidChars(description, true);
                        writer.WriteCData(description);
                        writer.WriteEndElement(); // description



                        //google product category [google_product_category] - Google's category of the item
                        //the category of the product according to Google’s product taxonomy. http://www.google.com/support/merchants/bin/answer.py?answer=160081
                        string googleProductCategory = "";
                        var    googleProduct         = allGoogleProducts.FirstOrDefault(x => x.ProductId == product.Id);
                        if (googleProduct != null)
                        {
                            googleProductCategory = googleProduct.Taxonomy;
                        }
                        if (String.IsNullOrEmpty(googleProductCategory))
                        {
                            googleProductCategory = _googleShoppingSettings.DefaultGoogleCategory;
                        }
                        if (String.IsNullOrEmpty(googleProductCategory))
                        {
                            throw new GrandException("Default Google category is not set");
                        }
                        writer.WriteStartElement("g", "google_product_category", googleBaseNamespace);
                        writer.WriteCData(googleProductCategory);
                        writer.WriteFullEndElement(); // g:google_product_category

                        //product type [product_type] - Your category of the item
                        if (product.ProductCategories.Count > 0)
                        {
                            var defaultProductCategory = await _categoryService.GetCategoryById(product.ProductCategories.OrderBy(x => x.DisplayOrder).FirstOrDefault().CategoryId);

                            if (defaultProductCategory != null)
                            {
                                //TODO localize categories
                                var category = await defaultProductCategory
                                               .GetFormattedBreadCrumb(_categoryService, separator : ">", languageId : languageId);

                                if (!String.IsNullOrEmpty((category)))
                                {
                                    writer.WriteStartElement("g", "product_type", googleBaseNamespace);
                                    writer.WriteCData(category);
                                    writer.WriteFullEndElement(); // g:product_type
                                }
                            }
                        }
                        //link [link] - URL directly linking to your item's page on your website
                        var productUrl = GetUrlHelper().RouteUrl("Product", new { SeName = product.GetSeName(languageId) }, GetHttpProtocol());
                        writer.WriteElementString("link", productUrl);

                        //image link [image_link] - URL of an image of the item
                        //additional images [additional_image_link]
                        //up to 10 pictures
                        const int maximumPictures = 10;
                        var       storeLocation   = _webHelper.IsCurrentConnectionSecured() ?
                                                    (!string.IsNullOrWhiteSpace(store.SecureUrl) ? store.SecureUrl : store.Url.Replace("http://", "https://")) :
                                                    store.Url;

                        var pictures = product.ProductPictures.Take(maximumPictures).ToList();
                        for (int i = 0; i < pictures.Count; i++)
                        {
                            var picture  = pictures[i];
                            var imageUrl = await _pictureService.GetPictureUrl(picture.PictureId,
                                                                               _googleShoppingSettings.ProductPictureSize,
                                                                               storeLocation : storeLocation);

                            if (i == 0)
                            {
                                //default image
                                writer.WriteElementString("g", "image_link", googleBaseNamespace, imageUrl);
                            }
                            else
                            {
                                //additional image
                                writer.WriteElementString("g", "additional_image_link", googleBaseNamespace, imageUrl);
                            }
                        }
                        if (pictures.Count == 0)
                        {
                            //no picture? submit a default one
                            var imageUrl = await _pictureService.GetDefaultPictureUrl(_googleShoppingSettings.ProductPictureSize, storeLocation : storeLocation);

                            writer.WriteElementString("g", "image_link", googleBaseNamespace, imageUrl);
                        }

                        //condition [condition] - Condition or state of the item
                        writer.WriteElementString("g", "condition", googleBaseNamespace, "new");

                        writer.WriteElementString("g", "expiration_date", googleBaseNamespace, DateTime.Now.AddDays(_googleShoppingSettings.ExpirationNumberOfDays).ToString("yyyy-MM-dd"));

                        #endregion

                        #region Availability & Price

                        //availability [availability] - Availability status of the item
                        string availability = "in stock"; //in stock by default
                        if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
                            product.BackorderMode == BackorderMode.NoBackorders &&
                            product.GetTotalStockQuantity() <= 0)
                        {
                            availability = "out of stock";
                        }
                        //uncomment th code below in order to support "preorder" value for "availability"
                        //if (product.AvailableForPreOrder &&
                        //    (!product.PreOrderAvailabilityStartDateTimeUtc.HasValue ||
                        //    product.PreOrderAvailabilityStartDateTimeUtc.Value >= DateTime.UtcNow))
                        //{
                        //    availability = "preorder";
                        //}
                        writer.WriteElementString("g", "availability", googleBaseNamespace, availability);

                        //price [price] - Price of the item
                        var currency = await GetUsedCurrency();

                        decimal finalPriceBase;
                        if (_googleShoppingSettings.PricesConsiderPromotions)
                        {
                            //calculate for the maximum quantity (in case if we have tier prices)
                            decimal minPossiblePrice = (await _priceCalculationService.GetFinalPrice(product,
                                                                                                     _workContext.CurrentCustomer, decimal.Zero, true, int.MaxValue)).finalPrice;
                            finalPriceBase = (await _taxService.GetProductPrice(product, minPossiblePrice)).productprice;
                        }
                        else
                        {
                            finalPriceBase = product.Price;
                        }
                        decimal price = await _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, currency);

                        price = RoundingHelper.RoundPrice(price, currency);
                        writer.WriteElementString("g", "price", googleBaseNamespace,
                                                  price.ToString(new CultureInfo("en-US", false).NumberFormat) + " " +
                                                  currency.CurrencyCode);

                        #endregion

                        #region Unique Product Identifiers

                        /* Unique product identifiers such as UPC, EAN, JAN or ISBN allow us to show your listing on the appropriate product page. If you don't provide the required unique product identifiers, your store may not appear on product pages, and all your items may be removed from Product Search.
                         * We require unique product identifiers for all products - except for custom made goods. For apparel, you must submit the 'brand' attribute. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute. In all cases, we recommend you submit all three attributes.
                         * You need to submit at least two attributes of 'brand', 'gtin' and 'mpn', but we recommend that you submit all three if available. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute, but we recommend that you include 'brand' and 'mpn' if available.
                         */

                        //GTIN [gtin] - GTIN
                        var gtin = product.Gtin;
                        if (!String.IsNullOrEmpty(gtin))
                        {
                            writer.WriteStartElement("g", "gtin", googleBaseNamespace);
                            writer.WriteCData(gtin);
                            writer.WriteFullEndElement(); // g:gtin
                        }

                        //brand [brand] - Brand of the item
                        if (product.ProductManufacturers.Count > 0)
                        {
                            var defaultManufacturer = await _manufacturerService.GetManufacturerById((product.ProductManufacturers.FirstOrDefault().ManufacturerId));

                            if (defaultManufacturer != null)
                            {
                                writer.WriteStartElement("g", "brand", googleBaseNamespace);
                                writer.WriteCData(defaultManufacturer.Name);
                                writer.WriteFullEndElement(); // g:brand
                            }
                        }


                        //mpn [mpn] - Manufacturer Part Number (MPN) of the item
                        var mpn = product.ManufacturerPartNumber;
                        if (!String.IsNullOrEmpty(mpn))
                        {
                            writer.WriteStartElement("g", "mpn", googleBaseNamespace);
                            writer.WriteCData(mpn);
                            writer.WriteFullEndElement(); // g:mpn
                        }

                        //identifier exists [identifier_exists] - Submit custom goods
                        if (googleProduct != null && googleProduct.CustomGoods)
                        {
                            writer.WriteElementString("g", "identifier_exists", googleBaseNamespace, "FALSE");
                        }

                        #endregion

                        #region Apparel Products

                        /* Apparel includes all products that fall under 'Apparel & Accessories' (including all sub-categories)
                         * in Google’s product taxonomy.
                         */

                        //gender [gender] - Gender of the item
                        if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.Gender))
                        {
                            writer.WriteStartElement("g", "gender", googleBaseNamespace);
                            writer.WriteCData(googleProduct.Gender);
                            writer.WriteFullEndElement(); // g:gender
                        }

                        //age group [age_group] - Target age group of the item
                        if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.AgeGroup))
                        {
                            writer.WriteStartElement("g", "age_group", googleBaseNamespace);
                            writer.WriteCData(googleProduct.AgeGroup);
                            writer.WriteFullEndElement(); // g:age_group
                        }

                        //color [color] - Color of the item
                        if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.Color))
                        {
                            writer.WriteStartElement("g", "color", googleBaseNamespace);
                            writer.WriteCData(googleProduct.Color);
                            writer.WriteFullEndElement(); // g:color
                        }

                        //size [size] - Size of the item
                        if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.Size))
                        {
                            writer.WriteStartElement("g", "size", googleBaseNamespace);
                            writer.WriteCData(googleProduct.Size);
                            writer.WriteFullEndElement(); // g:size
                        }

                        #endregion

                        #region Tax & Shipping

                        //tax [tax]
                        //The tax attribute is an item-level override for merchant-level tax settings as defined in your Google Merchant Center account. This attribute is only accepted in the US, if your feed targets a country outside of the US, please do not use this attribute.
                        //IMPORTANT NOTE: Set tax in your Google Merchant Center account settings

                        //IMPORTANT NOTE: Set shipping in your Google Merchant Center account settings

                        //shipping weight [shipping_weight] - Weight of the item for shipping
                        //We accept only the following units of weight: lb, oz, g, kg.
                        if (_googleShoppingSettings.PassShippingInfoWeight)
                        {
                            string weightName;
                            var    shippingWeight   = product.Weight;
                            var    weightSystemName = (await _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId)).SystemKeyword;
                            switch (weightSystemName)
                            {
                            case "ounce":
                                weightName = "oz";
                                break;

                            case "lb":
                                weightName = "lb";
                                break;

                            case "grams":
                                weightName = "g";
                                break;

                            case "kg":
                                weightName = "kg";
                                break;

                            default:
                                //unknown weight
                                throw new Exception("Not supported weight. Google accepts the following units: lb, oz, g, kg.");
                            }
                            writer.WriteElementString("g", "shipping_weight", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", shippingWeight.ToString(new CultureInfo("en-US", false).NumberFormat), weightName));
                        }

                        //shipping length [shipping_length] - Length of the item for shipping
                        //shipping width [shipping_width] - Width of the item for shipping
                        //shipping height [shipping_height] - Height of the item for shipping
                        //We accept only the following units of length: in, cm
                        if (_googleShoppingSettings.PassShippingInfoDimensions)
                        {
                            string dimensionName;
                            var    length = product.Length;
                            var    width  = product.Width;
                            var    height = product.Height;
                            var    dimensionSystemName = (await _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId)).SystemKeyword;
                            switch (dimensionSystemName)
                            {
                            case "inches":
                                dimensionName = "in";
                                break;

                            //TODO support other dimensions (convert to cm)
                            default:
                                //unknown dimension
                                throw new Exception("Not supported dimension. Google accepts the following units: in, cm.");
                            }
                            writer.WriteElementString("g", "shipping_length", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", length.ToString(new CultureInfo("en-US", false).NumberFormat), dimensionName));
                            writer.WriteElementString("g", "shipping_width", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", width.ToString(new CultureInfo("en-US", false).NumberFormat), dimensionName));
                            writer.WriteElementString("g", "shipping_height", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", height.ToString(new CultureInfo("en-US", false).NumberFormat), dimensionName));
                        }

                        #endregion

                        writer.WriteEndElement(); // item
                    }
                }

                writer.WriteEndElement(); // channel
                writer.WriteEndElement(); // rss
                writer.WriteEndDocument();
            }
        }
        /// <summary>
        /// Generate a feed
        /// </summary>
        /// <param name="stream">Stream</param>
        /// <param name="store">Store</param>
        /// <returns>Generated feed</returns>
        public void GenerateFeed(Stream stream, Store store)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            if (store == null)
            {
                throw new ArgumentNullException("store");
            }

            const string zboziBaseNamespace = "http://www.zbozi.cz/ns/offer/1.0";

            var settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8
            };

            //language
            var languageId = 0;
            var languages  = _languageService.GetAllLanguages(storeId: store.Id);

            //if we have only one language, let's use it
            if (languages.Count == 1)
            {
                //let's use the first one
                var language = languages.FirstOrDefault();
                languageId = language != null ? language.Id : 0;
            }
            //otherwise, use the current one
            if (languageId == 0)
            {
                languageId = _workContext.WorkingLanguage.Id;
            }

            //we load all google products here using one SQL request (performance optimization)
            var allzboziProducts = _zboziService.GetAll();

            var shippingMethodList = _shippingService.GetAllShippingMethods(27);


            using (var writer = XmlWriter.Create(stream, settings))
            {
                //Generate feed according to the following specs: https://napoveda.seznam.cz/cz/zbozi/specifikace-xml-pro-obchody/specifikace-xml-feedu/
                writer.WriteStartDocument();
                writer.WriteStartElement("SHOP", zboziBaseNamespace);
                //writer.WriteAttributeString("xmlns", zboziBaseNamespace);
                //writer.WriteAttributeString("xmlns", "", null, zboziBaseNamespace);

                var products1 = _productService.SearchProducts(storeId: store.Id, visibleIndividuallyOnly: true);
                foreach (var product1 in products1)
                {
                    var productsToProcess = new List <Product>();
                    switch (product1.ProductType)
                    {
                    case ProductType.SimpleProduct:
                    {
                        //simple product doesn't have child products
                        productsToProcess.Add(product1);
                    }
                    break;

                    case ProductType.GroupedProduct:
                    {
                        //grouped products could have several child products
                        var associatedProducts = _productService.GetAssociatedProducts(product1.Id, store.Id);
                        productsToProcess.AddRange(associatedProducts);
                    }
                    break;

                    default:
                        continue;
                    }
                    foreach (var product in productsToProcess)
                    {
                        writer.WriteStartElement("SHOPITEM");

                        #region Basic Product Information

                        //id [id]- An identifier of the item
                        writer.WriteElementString("ITEM_ID", product.Id.ToString());

                        //title [title] - Title of the item
                        writer.WriteStartElement("PRODUCT");
                        var title = product.GetLocalized(x => x.Name, languageId);
                        //title should be not longer than 70 characters
                        if (title.Length > 70)
                        {
                            title = title.Substring(0, 70);
                        }
                        writer.WriteCData(title);
                        writer.WriteEndElement();                         // title

                        //description [description] - Description of the item
                        writer.WriteStartElement("DESCRIPTION");
                        string description = product.GetLocalized(x => x.FullDescription, languageId);
                        if (String.IsNullOrEmpty(description))
                        {
                            description = product.GetLocalized(x => x.ShortDescription, languageId);
                        }
                        if (String.IsNullOrEmpty(description))
                        {
                            description = product.GetLocalized(x => x.Name, languageId);                             //description is required
                        }
                        //resolving character encoding issues in your data feed
                        description = StripInvalidChars(description, true);
                        writer.WriteCData(description);
                        writer.WriteEndElement();                         // description



                        //google product category [google_product_category] - Google's category of the item
                        //the category of the product according to Google’s product taxonomy. http://www.google.com/support/merchants/bin/answer.py?answer=160081
                        string googleProductCategory = "";
                        //var googleProduct = _googleService.GetByProductId(product.Id);
                        var zboziProduct = allzboziProducts.FirstOrDefault(x => x.ProductId == product.Id);
                        if (zboziProduct != null)
                        {
                            googleProductCategory = zboziProduct.Taxonomy;
                        }
                        if (String.IsNullOrEmpty(googleProductCategory))
                        {
                            googleProductCategory = _zboziShoppingSettings.DefaultGoogleCategory;
                        }
                        if (String.IsNullOrEmpty(googleProductCategory))
                        {
                            throw new NopException("Není nastavena základní kategorie");
                        }
                        writer.WriteStartElement("CATEGORYTEXT");
                        writer.WriteCData(googleProductCategory);
                        writer.WriteFullEndElement();                         // g:google_product_category

                        string zboziCPC = "";
                        if (zboziProduct != null)
                        {
                            zboziCPC = zboziProduct.MAX_CPC;
                        }

                        if (!string.IsNullOrEmpty(zboziCPC))
                        {
                            decimal result = 0;
                            if (decimal.TryParse(zboziCPC, out result))
                            {
                                writer.WriteElementString("MAX_CPC", zboziCPC);
                            }
                            else
                            {
                                throw new Exception("Parametr MAX_CPC musí obsahovat číselnou hodnotu. ");
                            }
                        }

                        string zboziCPC_serach = "";
                        if (zboziProduct != null)
                        {
                            zboziCPC_serach = zboziProduct.MAX_CPC_SEARCH;
                        }

                        if (!string.IsNullOrEmpty(zboziCPC_serach))
                        {
                            decimal result = 0;
                            if (decimal.TryParse(zboziCPC_serach, out result))
                            {
                                writer.WriteElementString("MAX_CPC_SEARCH", zboziCPC_serach);
                            }
                            else
                            {
                                throw new Exception("Parametr MAX_CPC_SEARCH musí obsahovat číselnou hodnotu. ");
                            }
                        }



                        string EXTRA_MESSAGE = "";
                        if (zboziProduct != null)
                        {
                            EXTRA_MESSAGE = zboziProduct.EXTRA_MESSAGE;
                        }

                        if (!string.IsNullOrEmpty(EXTRA_MESSAGE))
                        {
                            writer.WriteElementString("EXTRA_MESSAGE", EXTRA_MESSAGE);
                        }



                        string productName = "";
                        if (zboziProduct != null)
                        {
                            productName = zboziProduct.ProductName;
                        }

                        if (!string.IsNullOrEmpty(productName))
                        {
                            writer.WriteStartElement("PRODUCTNAME");
                            writer.WriteCData(productName);
                            writer.WriteFullEndElement();                             //
                        }
                        else
                        {
                            writer.WriteStartElement("PRODUCTNAME");
                            writer.WriteCData(title);
                            writer.WriteFullEndElement();                             //
                        }



                        // params
                        var parameters = "";
                        if (zboziProduct != null)
                        {
                            parameters = zboziProduct.Params;
                        }

                        if (string.IsNullOrEmpty(parameters) && product.ProductSpecificationAttributes.Count() > 0)
                        {
                            // progenerujeme vsechny parametry
                            foreach (var productSpecificAtribut in product.ProductSpecificationAttributes)
                            {
                                var option = productSpecificAtribut.SpecificationAttributeOption;
                                var name   = option.SpecificationAttribute.Name;
                                var value  = option.Name;

                                writer.WriteStartElement("PARAM");

                                writer.WriteStartElement("PARAM_NAME");
                                writer.WriteString(name);
                                writer.WriteEndElement();

                                writer.WriteStartElement("VAL");
                                writer.WriteString(value);
                                writer.WriteEndElement();

                                writer.WriteFullEndElement();                                 // g:


                                //var values = mapping.ProductAttributeValues.Where(e=>e.)
                            }
                        }
                        else
                        {
                            // prepiseme hodnotu
                        }


                        ////product type [product_type] - Your category of the item
                        //var defaultProductCategory = _categoryService
                        //    .GetProductCategoriesByProductId(product.Id, store.Id)
                        //    .FirstOrDefault();
                        //if (defaultProductCategory != null)
                        //{
                        //    //TODO localize categories
                        //    var category = defaultProductCategory.Category
                        //        .GetFormattedBreadCrumb(_categoryService, separator: ">", languageId: languageId);
                        //    if (!String.IsNullOrEmpty((category)))
                        //    {
                        //        writer.WriteStartElement("g", "product_type", googleBaseNamespace);
                        //        writer.WriteCData(category);
                        //        writer.WriteFullEndElement(); // g:product_type
                        //    }
                        //}

                        //link [link] - URL directly linking to your item's page on your website
                        var productUrl = string.Format("{0}{1}", store.Url, product.GetSeName(languageId));
                        writer.WriteElementString("URL", productUrl);

                        //image link [image_link] - URL of an image of the item
                        //additional images [additional_image_link]
                        //up to 10 pictures
                        const int maximumPictures = 10;
                        var       pictures        = _pictureService.GetPicturesByProductId(product.Id, maximumPictures);
                        for (int i = 0; i < pictures.Count; i++)
                        {
                            var picture  = pictures[i];
                            var imageUrl = _pictureService.GetPictureUrl(picture,
                                                                         _zboziShoppingSettings.ProductPictureSize,
                                                                         storeLocation: store.Url);

                            if (i == 0)
                            {
                                //default image
                                writer.WriteElementString("IMGURL", imageUrl);
                            }
                            //else
                            //{
                            //	//additional image
                            //	writer.WriteElementString("IMGURL_ALTERNATIVE", imageUrl);
                            //}
                        }
                        if (!pictures.Any())
                        {
                            //no picture? submit a default one
                            var imageUrl = _pictureService.GetDefaultPictureUrl(_zboziShoppingSettings.ProductPictureSize, storeLocation: store.Url);
                            writer.WriteElementString("IMGURL", imageUrl);
                        }


                        #endregion

                        #region Availability & Price


                        //price [price] - Price of the item
                        var     currency = GetUsedCurrency();
                        decimal finalPriceBase;
                        if (_zboziShoppingSettings.PricesConsiderPromotions)
                        {
                            var minPossiblePrice = _priceCalculationService.GetFinalPrice(product, _workContext.CurrentCustomer);

                            if (product.HasTierPrices)
                            {
                                //calculate price for the maximum quantity if we have tier prices, and choose minimal
                                minPossiblePrice = Math.Min(minPossiblePrice,
                                                            _priceCalculationService.GetFinalPrice(product, _workContext.CurrentCustomer, quantity: int.MaxValue));
                            }

                            decimal taxRate;
                            finalPriceBase = _taxService.GetProductPrice(product, minPossiblePrice, out taxRate);
                        }
                        else
                        {
                            finalPriceBase = product.Price;
                        }
                        decimal price = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, currency);
                        //round price now so it matches the product details page
                        price = RoundingHelper.RoundPrice(price);

                        //writer.WriteElementString("g", "price", googleBaseNamespace,
                        //                          price.ToString(new CultureInfo("en-US", false).NumberFormat) + " " +
                        //                          currency.CurrencyCode);


                        if (price == 0)
                        {
                            writer.WriteElementString("PRICE_VAT", "0");
                        }
                        else
                        {
                            writer.WriteElementString("PRICE_VAT", price.ToString("#,##"));
                        }

                        #endregion

                        #region Unique Product Identifiers

                        /* Unique product identifiers such as UPC, EAN, JAN or ISBN allow us to show your listing on the appropriate product page. If you don't provide the required unique product identifiers, your store may not appear on product pages, and all your items may be removed from Product Search.
                         * We require unique product identifiers for all products - except for custom made goods. For apparel, you must submit the 'brand' attribute. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute. In all cases, we recommend you submit all three attributes.
                         * You need to submit at least two attributes of 'brand', 'gtin' and 'mpn', but we recommend that you submit all three if available. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute, but we recommend that you include 'brand' and 'mpn' if available.
                         */

                        //GTIN [gtin] - GTIN
                        var gtin = product.Gtin;
                        if (!String.IsNullOrEmpty(gtin))
                        {
                            writer.WriteStartElement("EAN");
                            writer.WriteCData(gtin);
                            writer.WriteFullEndElement();                             // g:gtin
                        }

                        //brand [brand] - Brand of the item
                        var defaultManufacturer =
                            _manufacturerService.GetProductManufacturersByProductId((product.Id)).FirstOrDefault();
                        if (defaultManufacturer != null)
                        {
                            writer.WriteStartElement("MANUFACTURER");
                            writer.WriteCData(defaultManufacturer.Manufacturer.Name);
                            writer.WriteFullEndElement();                             // g:brand
                        }


                        //mpn [mpn] - Manufacturer Part Number (MPN) of the item
                        var mpn = product.ManufacturerPartNumber;
                        if (!String.IsNullOrEmpty(mpn))
                        {
                            writer.WriteStartElement("PRODUCTNO");
                            writer.WriteCData(mpn);
                            writer.WriteFullEndElement();                             // g:mpn
                        }

                        //identifier exists [identifier_exists] - Submit custom goods
                        //if (googleProduct != null && googleProduct.CustomGoods)
                        //{
                        //    writer.WriteElementString("g", "identifier_exists", googleBaseNamespace, "FALSE");
                        //}

                        #endregion

                        #region Apparel Products



                        #endregion

                        #region Tax & Shipping


                        string deliveryDate = "0";

                        if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
                            product.BackorderMode == BackorderMode.NoBackorders &&
                            product.GetTotalStockQuantity() <= 0)
                        {
                            // neni skladem a ma dobu pro doruceni
                            if (product.DeliveryDateId > 0)
                            {
                                var deliveryDateRecord = _dateRangeService.GetDeliveryDateById(product.DeliveryDateId);
                                deliveryDate = deliveryDateRecord.Name;
                            }
                            else
                            {
                                deliveryDate = _zboziShoppingSettings.DefaultDeliveryDate;
                            }
                        }

                        writer.WriteStartElement("DELIVERY_DATE");
                        writer.WriteString(deliveryDate);
                        writer.WriteFullEndElement();                         //



                        #endregion

                        writer.WriteEndElement();                         // item
                    }
                }


                writer.WriteEndElement();                 // shop
                writer.WriteEndDocument();
            }
        }
Beispiel #8
0
 public void can_round_price(decimal price, decimal roundedValue)
 {
     RoundingHelper.RoundPrice(price).ShouldEqual(roundedValue);
 }
Beispiel #9
0
        GetShoppingCartTotal(IList <ShoppingCartItem> cart, bool?useRewardPoints = null, bool usePaymentMethodAdditionalFee = true)
        {
            var redeemedRewardPoints       = 0;
            var redeemedRewardPointsAmount = decimal.Zero;

            var customer = _workContext.CurrentCustomer;
            var currency = await _currencyService.GetPrimaryExchangeRateCurrency();

            string paymentMethodSystemName = "";

            if (customer != null)
            {
                paymentMethodSystemName = customer.GetAttributeFromEntity <string>(
                    SystemCustomerAttributeNames.SelectedPaymentMethod,
                    _storeContext.CurrentStore.Id);
            }

            //subtotal without tax
            var subTotal = await GetShoppingCartSubTotal(cart, false);

            decimal subTotalWithDiscountBase = subTotal.subTotalWithDiscount;

            //subtotal with discount
            decimal subtotalBase = subTotalWithDiscountBase;

            //shipping without tax
            var shippingTotal = await GetShoppingCartShippingTotal(cart, false);

            decimal?shoppingCartShipping = shippingTotal.shoppingCartShippingTotal;

            //payment method additional fee without tax
            decimal paymentMethodAdditionalFeeWithoutTax = decimal.Zero;

            if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName))
            {
                decimal paymentMethodAdditionalFee = await _paymentService.GetAdditionalHandlingFee(cart,
                                                                                                    paymentMethodSystemName);

                paymentMethodAdditionalFeeWithoutTax = (await
                                                        _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee,
                                                                                                  false, customer)).paymentPrice;
            }

            //tax
            decimal shoppingCartTax = (await GetTaxTotal(cart, usePaymentMethodAdditionalFee)).taxtotal;

            //order total
            decimal resultTemp = decimal.Zero;

            resultTemp += subtotalBase;
            if (shoppingCartShipping.HasValue)
            {
                resultTemp += shoppingCartShipping.Value;
            }
            resultTemp += paymentMethodAdditionalFeeWithoutTax;
            resultTemp += shoppingCartTax;
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp, currency);
            }
            #region Order total discount

            var totalDiscount = await GetOrderTotalDiscount(customer, resultTemp);

            var discountAmount   = totalDiscount.orderTotalDiscount;
            var appliedDiscounts = totalDiscount.appliedDiscounts;

            //sub totals with discount
            if (resultTemp < discountAmount)
            {
                discountAmount = resultTemp;
            }

            //reduce subtotal
            resultTemp -= discountAmount;

            if (resultTemp < decimal.Zero)
            {
                resultTemp = decimal.Zero;
            }
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp, currency);
            }

            #endregion

            #region Applied gift cards

            //let's apply gift cards now (gift cards that can be used)
            var appliedGiftCards = new List <AppliedGiftCard>();
            if (!cart.IsRecurring())
            {
                //we don't apply gift cards for recurring products
                var giftCards = await GetActiveGiftCards(customer);

                if (giftCards != null)
                {
                    foreach (var gc in giftCards)
                    {
                        if (resultTemp > decimal.Zero)
                        {
                            decimal remainingAmount = gc.GetGiftCardRemainingAmount();
                            decimal amountCanBeUsed = resultTemp > remainingAmount ?
                                                      remainingAmount :
                                                      resultTemp;

                            //reduce subtotal
                            resultTemp -= amountCanBeUsed;

                            var appliedGiftCard = new AppliedGiftCard
                            {
                                GiftCard        = gc,
                                AmountCanBeUsed = amountCanBeUsed
                            };
                            appliedGiftCards.Add(appliedGiftCard);
                        }
                    }
                }
            }

            #endregion

            if (resultTemp < decimal.Zero)
            {
                resultTemp = decimal.Zero;
            }
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp, currency);
            }

            if (!shoppingCartShipping.HasValue)
            {
                //we have errors
                return(null, discountAmount, appliedDiscounts, appliedGiftCards, redeemedRewardPoints, redeemedRewardPointsAmount);
            }

            decimal orderTotal = resultTemp;

            #region Reward points

            if (_rewardPointsSettings.Enabled)
            {
                if (!useRewardPoints.HasValue)
                {
                    useRewardPoints = customer.GetAttributeFromEntity <bool>(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _storeContext.CurrentStore.Id);
                }

                if (useRewardPoints.Value)
                {
                    int rewardPointsBalance = await _rewardPointsService.GetRewardPointsBalance(customer.Id, _storeContext.CurrentStore.Id);

                    if (CheckMinimumRewardPointsToUseRequirement(rewardPointsBalance))
                    {
                        decimal rewardPointsBalanceAmount = await ConvertRewardPointsToAmount(rewardPointsBalance);

                        if (orderTotal > decimal.Zero)
                        {
                            if (orderTotal > rewardPointsBalanceAmount)
                            {
                                redeemedRewardPoints       = rewardPointsBalance;
                                redeemedRewardPointsAmount = rewardPointsBalanceAmount;
                            }
                            else
                            {
                                redeemedRewardPointsAmount = orderTotal;
                                redeemedRewardPoints       = ConvertAmountToRewardPoints(redeemedRewardPointsAmount);
                            }
                        }
                    }
                }
            }

            #endregion

            orderTotal = orderTotal - redeemedRewardPointsAmount;
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                orderTotal = RoundingHelper.RoundPrice(orderTotal, currency);
            }

            return(orderTotal, discountAmount, appliedDiscounts, appliedGiftCards, redeemedRewardPoints, redeemedRewardPointsAmount);
        }
Beispiel #10
0
        /// <summary>
        /// Gets the final price
        /// </summary>
        /// <param name="product">Product</param>
        /// <param name="customer">The customer</param>
        /// <param name="currency">Currency</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>
        /// <returns>Final price</returns>
        public virtual async Task <(double finalPrice, double discountAmount, List <ApplyDiscount> appliedDiscounts, TierPrice preferredTierPrice)> GetFinalPrice(
            Product product,
            Customer customer,
            Currency currency,
            double additionalCharge,
            bool includeDiscounts,
            int quantity,
            DateTime?rentalStartDate,
            DateTime?rentalEndDate)
        {
            if (product == null)
            {
                throw new ArgumentNullException(nameof(product));
            }

            double discountAmount   = 0;
            var    appliedDiscounts = new List <ApplyDiscount>();

            async Task <ProductPrice> PrepareModel()
            {
                var result = new ProductPrice();

                //initial price
                double price =
                    product.ProductPrices.FirstOrDefault(x => x.CurrencyCode == currency.CurrencyCode)?.Price ??
                    await _currencyService.ConvertFromPrimaryStoreCurrency(product.Price, currency);

                //tier prices
                var tierPrice = product.GetPreferredTierPrice(customer, _workContext.CurrentStore.Id, currency.CurrencyCode, quantity);

                if (tierPrice != null)
                {
                    price = tierPrice.Price;
                    result.PreferredTierPrice = tierPrice;
                }

                //customer product price
                if (_catalogSettings.CustomerProductPrice)
                {
                    var customerPrice = await _mediator.Send(new GetPriceByCustomerProductQuery()
                    {
                        CustomerId = customer.Id, ProductId = product.Id
                    });

                    if (customerPrice.HasValue && customerPrice.Value < await _currencyService.ConvertToPrimaryStoreCurrency(price, currency))
                    {
                        price = await _currencyService.ConvertFromPrimaryStoreCurrency(customerPrice.Value, currency);
                    }
                }

                //additional charge
                price += additionalCharge;

                //reservations
                if (product.ProductTypeId == ProductType.Reservation)
                {
                    if (rentalStartDate.HasValue && rentalEndDate.HasValue)
                    {
                        double d = 0;
                        if (product.IncBothDate)
                        {
                            _ = double.TryParse(((rentalEndDate - rentalStartDate).Value.TotalDays + 1).ToString(), out d);
                        }
                        else
                        {
                            _ = double.TryParse((rentalEndDate - rentalStartDate).Value.TotalDays.ToString(), out d);
                        }
                        price *= d;
                    }
                }

                if (includeDiscounts)
                {
                    //discount
                    var discountamount = await GetDiscountAmount(product, customer, currency, price);

                    double tmpDiscountAmount = discountamount.discountAmount;
                    List <ApplyDiscount> tmpAppliedDiscounts = discountamount.appliedDiscounts;
                    price -= tmpDiscountAmount;

                    if (tmpAppliedDiscounts != null)
                    {
                        result.AppliedDiscounts      = tmpAppliedDiscounts.ToList();
                        result.AppliedDiscountAmount = tmpDiscountAmount;
                    }
                }

                if (price < 0)
                {
                    price = 0;
                }

                //rounding
                if (_shoppingCartSettings.RoundPrices)
                {
                    result.Price = RoundingHelper.RoundPrice(price, currency);
                }
                else
                {
                    result.Price = price;
                }

                return(result);
            }

            var modelprice = await PrepareModel();

            if (includeDiscounts)
            {
                appliedDiscounts = modelprice.AppliedDiscounts.ToList();
                if (appliedDiscounts.Any())
                {
                    discountAmount = modelprice.AppliedDiscountAmount;
                }
            }

            return(modelprice.Price, discountAmount, appliedDiscounts, modelprice.PreferredTierPrice);
        }
Beispiel #11
0
        /// <summary>
        /// Gets the shopping cart unit price
        /// </summary>
        /// <param name="product">Product</param>
        /// <param name="customer">Customer</param>
        /// <param name="currency">The currency</param>
        /// <param name="shoppingCartType">Shopping cart type</param>
        /// <param name="quantity">Quantity</param>
        /// <param name="attributes">Product atrributes</param>
        /// <param name="customerEnteredPrice">Customer entered price</param>
        /// <param name="rentalStartDate">Rental start date</param>
        /// <param name="rentalEndDate">Rental end date</param>
        /// <param name="includeDiscounts">Include discounts or not for price</param>
        /// <returns>Shopping cart unit price</returns>
        public virtual async Task <(double unitprice, double discountAmount, List <ApplyDiscount> appliedDiscounts)> GetUnitPrice(
            Product product,
            Customer customer,
            Currency currency,
            ShoppingCartType shoppingCartType,
            int quantity,
            IList <CustomAttribute> attributes,
            double?customerEnteredPrice,
            DateTime?rentalStartDate,
            DateTime?rentalEndDate,
            bool includeDiscounts)
        {
            if (product == null)
            {
                throw new ArgumentNullException(nameof(product));
            }

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

            double discountAmount   = 0;
            var    appliedDiscounts = new List <ApplyDiscount>();

            double?finalPrice = null;

            if (customerEnteredPrice.HasValue)
            {
                finalPrice = await _currencyService.ConvertFromPrimaryStoreCurrency(customerEnteredPrice.Value, currency);
            }

            if (!finalPrice.HasValue)
            {
                var combination = _productAttributeParser.FindProductAttributeCombination(product, attributes);
                if (combination != null)
                {
                    if (combination.OverriddenPrice.HasValue)
                    {
                        finalPrice = await _currencyService.ConvertFromPrimaryStoreCurrency(combination.OverriddenPrice.Value, currency);
                    }
                    if (combination.TierPrices.Any())
                    {
                        var storeId          = _workContext.CurrentStore.Id;
                        var actualTierPrices = combination.TierPrices.Where(x => string.IsNullOrEmpty(x.StoreId) || x.StoreId == storeId)
                                               .Where(x => string.IsNullOrEmpty(x.CustomerGroupId) ||
                                                      customer.Groups.Contains(x.CustomerGroupId)).ToList();
                        var tierPrice = actualTierPrices.LastOrDefault(price => quantity >= price.Quantity);
                        if (tierPrice != null)
                        {
                            finalPrice = await _currencyService.ConvertFromPrimaryStoreCurrency(tierPrice.Price, currency);
                        }
                    }
                }
            }
            if (!finalPrice.HasValue)
            {
                //summarize price of all attributes
                double attributesTotalPrice = 0;
                if (attributes != null && attributes.Any())
                {
                    if (product.ProductTypeId != ProductType.BundledProduct)
                    {
                        var attributeValues = _productAttributeParser.ParseProductAttributeValues(product, attributes);
                        if (attributeValues != null)
                        {
                            foreach (var attributeValue in attributeValues)
                            {
                                attributesTotalPrice += await GetProductAttributeValuePriceAdjustment(attributeValue);
                            }
                        }
                    }
                    else
                    {
                        foreach (var item in product.BundleProducts)
                        {
                            var p1 = await _productService.GetProductById(item.ProductId);

                            if (p1 != null)
                            {
                                var attributeValues = _productAttributeParser.ParseProductAttributeValues(p1, attributes);
                                if (attributeValues != null)
                                {
                                    foreach (var attributeValue in attributeValues)
                                    {
                                        attributesTotalPrice += (item.Quantity * await GetProductAttributeValuePriceAdjustment(attributeValue));
                                    }
                                }
                            }
                        }
                    }
                }

                if (product.EnteredPrice)
                {
                    finalPrice = customerEnteredPrice;
                }
                else
                {
                    int qty = 0;
                    if (_shoppingCartSettings.GroupTierPrices)
                    {
                        qty = customer.ShoppingCartItems
                              .Where(x => x.ProductId == product.Id)
                              .Where(x => x.ShoppingCartTypeId == shoppingCartType)
                              .Sum(x => x.Quantity);
                    }
                    if (qty == 0)
                    {
                        qty = quantity;
                    }

                    var getfinalPrice = await GetFinalPrice(product,
                                                            customer,
                                                            currency,
                                                            attributesTotalPrice,
                                                            includeDiscounts,
                                                            qty,
                                                            rentalStartDate,
                                                            rentalEndDate);

                    finalPrice       = getfinalPrice.finalPrice;
                    discountAmount   = getfinalPrice.discountAmount;
                    appliedDiscounts = getfinalPrice.appliedDiscounts;
                }
            }

            if (!finalPrice.HasValue)
            {
                finalPrice = 0;
            }

            //rounding
            if (_shoppingCartSettings.RoundPrices)
            {
                finalPrice = RoundingHelper.RoundPrice(finalPrice.Value, _workContext.WorkingCurrency);
            }
            return(finalPrice.Value, discountAmount, appliedDiscounts);
        }
        /// <summary>
        /// Generate a feed
        /// </summary>
        /// <param name="stream">Stream</param>
        /// <param name="store">Store</param>
        /// <returns>Generated feed</returns>
        public void GenerateFeed(Stream stream, Store store)
        {
            if (stream == null)
            {
                throw new ArgumentNullException("stream");
            }

            if (store == null)
            {
                throw new ArgumentNullException("store");
            }

            const string googleBaseNamespace = "http://base.google.com/ns/1.0";

            var settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8
            };

            //language
            var languageId = 0;
            var languages  = _languageService.GetAllLanguages(storeId: store.Id);

            //if we have only one language, let's use it
            if (languages.Count == 1)
            {
                //let's use the first one
                var language = languages.FirstOrDefault();
                languageId = language != null ? language.Id : 0;
            }
            //otherwise, use the current one
            if (languageId == 0)
            {
                languageId = _workContext.WorkingLanguage.Id;
            }

            //we load all google products here using one SQL request (performance optimization)
            var allGoogleProducts = _googleService.GetAll();

            using (var writer = XmlWriter.Create(stream, settings))
            {
                //Generate feed according to the following specs: http://www.google.com/support/merchants/bin/answer.py?answer=188494&expand=GB
                writer.WriteStartDocument();
                writer.WriteStartElement("rss");
                writer.WriteAttributeString("version", "2.0");
                writer.WriteAttributeString("xmlns", "g", null, googleBaseNamespace);
                writer.WriteStartElement("channel");
                writer.WriteElementString("title", "Google Base feed");
                writer.WriteElementString("link", "http://base.google.com/base/");
                writer.WriteElementString("description", "Information about products");


                var products1 = _productService.SearchProducts(storeId: store.Id, visibleIndividuallyOnly: true);
                foreach (var product1 in products1)
                {
                    var productsToProcess = new List <Product>();
                    switch (product1.ProductType)
                    {
                    case ProductType.SimpleProduct:
                    {
                        //simple product doesn't have child products
                        productsToProcess.Add(product1);
                    }
                    break;

                    case ProductType.GroupedProduct:
                    {
                        //grouped products could have several child products
                        var associatedProducts = _productService.GetAssociatedProducts(product1.Id, store.Id);
                        productsToProcess.AddRange(associatedProducts);
                    }
                    break;

                    default:
                        continue;
                    }
                    foreach (var product in productsToProcess)
                    {
                        writer.WriteStartElement("item");

                        #region Basic Product Information

                        //id [id]- An identifier of the item
                        writer.WriteElementString("g", "id", googleBaseNamespace, product.Id.ToString());

                        //title [title] - Title of the item
                        writer.WriteStartElement("title");
                        var title = product.GetLocalized(x => x.Name, languageId);
                        //title should be not longer than 70 characters
                        if (title.Length > 70)
                        {
                            title = title.Substring(0, 70);
                        }
                        writer.WriteCData(title);
                        writer.WriteEndElement(); // title

                        //description [description] - Description of the item
                        writer.WriteStartElement("description");
                        string description = product.GetLocalized(x => x.FullDescription, languageId);
                        if (String.IsNullOrEmpty(description))
                        {
                            description = product.GetLocalized(x => x.ShortDescription, languageId);
                        }
                        if (String.IsNullOrEmpty(description))
                        {
                            description = product.GetLocalized(x => x.Name, languageId); //description is required
                        }
                        //resolving character encoding issues in your data feed
                        description = StripInvalidChars(description, true);
                        writer.WriteCData(description);
                        writer.WriteEndElement(); // description



                        //google product category [google_product_category] - Google's category of the item
                        //the category of the product according to Google’s product taxonomy. http://www.google.com/support/merchants/bin/answer.py?answer=160081
                        string googleProductCategory = "";
                        //var googleProduct = _googleService.GetByProductId(product.Id);
                        var googleProduct = allGoogleProducts.FirstOrDefault(x => x.ProductId == product.Id);
                        if (googleProduct != null)
                        {
                            googleProductCategory = googleProduct.Taxonomy;
                        }
                        if (String.IsNullOrEmpty(googleProductCategory))
                        {
                            googleProductCategory = _googleShoppingSettings.DefaultGoogleCategory;
                        }
                        if (String.IsNullOrEmpty(googleProductCategory))
                        {
                            throw new NopException("Default Google category is not set");
                        }
                        writer.WriteStartElement("g", "google_product_category", googleBaseNamespace);
                        writer.WriteCData(googleProductCategory);
                        writer.WriteFullEndElement(); // g:google_product_category

                        //product type [product_type] - Your category of the item
                        var defaultProductCategory = _categoryService
                                                     .GetProductCategoriesByProductId(product.Id, store.Id)
                                                     .FirstOrDefault();
                        if (defaultProductCategory != null)
                        {
                            //TODO localize categories
                            var category = defaultProductCategory.Category
                                           .GetFormattedBreadCrumb(_categoryService, separator: ">", languageId: languageId);
                            if (!String.IsNullOrEmpty((category)))
                            {
                                writer.WriteStartElement("g", "product_type", googleBaseNamespace);
                                writer.WriteCData(category);
                                writer.WriteFullEndElement(); // g:product_type
                            }
                        }

                        //link [link] - URL directly linking to your item's page on your website
                        var productUrl = string.Format("{0}{1}", store.Url, product.GetSeName(languageId));
                        writer.WriteElementString("link", productUrl);

                        //image link [image_link] - URL of an image of the item
                        //additional images [additional_image_link]
                        //up to 10 pictures
                        const int maximumPictures = 10;
                        var       pictures        = _pictureService.GetPicturesByProductId(product.Id, maximumPictures);
                        for (int i = 0; i < pictures.Count; i++)
                        {
                            var picture  = pictures[i];
                            var imageUrl = _pictureService.GetPictureUrl(picture,
                                                                         _googleShoppingSettings.ProductPictureSize,
                                                                         storeLocation: store.Url);

                            if (i == 0)
                            {
                                //default image
                                writer.WriteElementString("g", "image_link", googleBaseNamespace, imageUrl);
                            }
                            else
                            {
                                //additional image
                                writer.WriteElementString("g", "additional_image_link", googleBaseNamespace, imageUrl);
                            }
                        }
                        if (!pictures.Any())
                        {
                            //no picture? submit a default one
                            var imageUrl = _pictureService.GetDefaultPictureUrl(_googleShoppingSettings.ProductPictureSize, storeLocation: store.Url);
                            writer.WriteElementString("g", "image_link", googleBaseNamespace, imageUrl);
                        }

                        //condition [condition] - Condition or state of the item
                        writer.WriteElementString("g", "condition", googleBaseNamespace, "new");

                        writer.WriteElementString("g", "expiration_date", googleBaseNamespace, DateTime.Now.AddDays(_googleShoppingSettings.ExpirationNumberOfDays).ToString("yyyy-MM-dd"));

                        #endregion

                        #region Availability & Price

                        //availability [availability] - Availability status of the item
                        string availability = "in stock"; //in stock by default
                        if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
                            product.GetTotalStockQuantity() <= 0)
                        {
                            availability = "out of stock";
                        }
                        //uncomment th code below in order to support "preorder" value for "availability"
                        //if (product.AvailableForPreOrder &&
                        //    (!product.PreOrderAvailabilityStartDateTimeUtc.HasValue ||
                        //    product.PreOrderAvailabilityStartDateTimeUtc.Value >= DateTime.UtcNow))
                        //{
                        //    availability = "preorder";
                        //}
                        writer.WriteElementString("g", "availability", googleBaseNamespace, availability);

                        //price [price] - Price of the item
                        var     currency = GetUsedCurrency();
                        decimal finalPriceBase;
                        if (_googleShoppingSettings.PricesConsiderPromotions)
                        {
                            var minPossiblePrice = _priceCalculationService.GetFinalPrice(product, _workContext.CurrentCustomer);

                            if (product.HasTierPrices)
                            {
                                //calculate price for the maximum quantity if we have tier prices, and choose minimal
                                minPossiblePrice = Math.Min(minPossiblePrice,
                                                            _priceCalculationService.GetFinalPrice(product, _workContext.CurrentCustomer, quantity: int.MaxValue));
                            }

                            decimal taxRate;
                            finalPriceBase = _taxService.GetProductPrice(product, minPossiblePrice, out taxRate);
                        }
                        else
                        {
                            finalPriceBase = product.Price;
                        }
                        decimal price = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, currency);
                        //round price now so it matches the product details page
                        price = RoundingHelper.RoundPrice(price);

                        writer.WriteElementString("g", "price", googleBaseNamespace,
                                                  price.ToString(new CultureInfo("en-US", false).NumberFormat) + " " +
                                                  currency.CurrencyCode);

                        #endregion

                        #region Unique Product Identifiers

                        /* Unique product identifiers such as UPC, EAN, JAN or ISBN allow us to show your listing on the appropriate product page. If you don't provide the required unique product identifiers, your store may not appear on product pages, and all your items may be removed from Product Search.
                         * We require unique product identifiers for all products - except for custom made goods. For apparel, you must submit the 'brand' attribute. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute. In all cases, we recommend you submit all three attributes.
                         * You need to submit at least two attributes of 'brand', 'gtin' and 'mpn', but we recommend that you submit all three if available. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute, but we recommend that you include 'brand' and 'mpn' if available.
                         */



                        //brand [brand] - Brand of the item
                        var defaultDestination =
                            _destinationService.GetProductDestinationsByProductId((product.Id)).FirstOrDefault();
                        if (defaultDestination != null)
                        {
                            writer.WriteStartElement("g", "brand", googleBaseNamespace);
                            writer.WriteCData(defaultDestination.Destination.Name);
                            writer.WriteFullEndElement(); // g:brand
                        }



                        //identifier exists [identifier_exists] - Submit custom goods
                        if (googleProduct != null && googleProduct.CustomGoods)
                        {
                            writer.WriteElementString("g", "identifier_exists", googleBaseNamespace, "FALSE");
                        }

                        #endregion



                        writer.WriteEndElement(); // item
                    }
                }

                writer.WriteEndElement(); // channel
                writer.WriteEndElement(); // rss
                writer.WriteEndDocument();
            }
        }
        /// <summary>
        /// Gets shopping cart total
        /// </summary>
        public virtual decimal?GetShoppingCartTotal(IList <ShoppingCartItem> cart,
                                                    out decimal discountAmount, out List <DiscountForCaching> appliedDiscounts,
                                                    out int redeemedRewardPoints, out decimal redeemedRewardPointsAmount,
                                                    bool?useRewardPoints = null, bool usePaymentMethodAdditionalFee = true)
        {
            redeemedRewardPoints       = 0;
            redeemedRewardPointsAmount = decimal.Zero;

            var    customer = cart.GetCustomer();
            string paymentMethodSystemName = "";

            if (customer != null)
            {
                paymentMethodSystemName = customer.GetAttribute <string>(
                    SystemCustomerAttributeNames.SelectedPaymentMethod,
                    _genericAttributeService,
                    _storeContext.CurrentStore.Id);
            }


            //subtotal without tax
            decimal orderSubTotalDiscountAmount;
            List <DiscountForCaching> orderSubTotalAppliedDiscounts;
            decimal subTotalWithoutDiscountBase;
            decimal subTotalWithDiscountBase;

            GetShoppingCartSubTotal(cart, false,
                                    out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscounts,
                                    out subTotalWithoutDiscountBase, out subTotalWithDiscountBase);
            //subtotal with discount
            decimal subtotalBase = subTotalWithDiscountBase;



            //payment method additional fee without tax
            decimal paymentMethodAdditionalFeeWithoutTax = decimal.Zero;

            if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName))
            {
                decimal paymentMethodAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart,
                                                                                              paymentMethodSystemName);
                paymentMethodAdditionalFeeWithoutTax =
                    _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee,
                                                              false, customer);
            }



            //tax
            decimal shoppingCartTax = GetTaxTotal(cart, usePaymentMethodAdditionalFee);



            //order total
            decimal resultTemp = decimal.Zero;

            resultTemp += subtotalBase;

            resultTemp += paymentMethodAdditionalFeeWithoutTax;
            resultTemp += shoppingCartTax;
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp);
            }

            #region Order total discount

            discountAmount = GetOrderTotalDiscount(customer, resultTemp, out appliedDiscounts);

            //sub totals with discount
            if (resultTemp < discountAmount)
            {
                discountAmount = resultTemp;
            }

            //reduce subtotal
            resultTemp -= discountAmount;

            if (resultTemp < decimal.Zero)
            {
                resultTemp = decimal.Zero;
            }
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp);
            }

            #endregion


            if (resultTemp < decimal.Zero)
            {
                resultTemp = decimal.Zero;
            }
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp);
            }



            decimal orderTotal = resultTemp;

            #region Reward points

            if (_rewardPointsSettings.Enabled)
            {
                if (!useRewardPoints.HasValue)
                {
                    useRewardPoints = customer.GetAttribute <bool>(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout, _genericAttributeService, _storeContext.CurrentStore.Id);
                }
                if (useRewardPoints.Value)
                {
                    int rewardPointsBalance = _rewardPointService.GetRewardPointsBalance(customer.Id, _storeContext.CurrentStore.Id);
                    if (CheckMinimumRewardPointsToUseRequirement(rewardPointsBalance))
                    {
                        decimal rewardPointsBalanceAmount = ConvertRewardPointsToAmount(rewardPointsBalance);
                        if (orderTotal > decimal.Zero)
                        {
                            if (orderTotal > rewardPointsBalanceAmount)
                            {
                                redeemedRewardPoints       = rewardPointsBalance;
                                redeemedRewardPointsAmount = rewardPointsBalanceAmount;
                            }
                            else
                            {
                                redeemedRewardPointsAmount = orderTotal;
                                redeemedRewardPoints       = ConvertAmountToRewardPoints(redeemedRewardPointsAmount);
                            }
                        }
                    }
                }
            }

            #endregion

            orderTotal = orderTotal - redeemedRewardPointsAmount;
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                orderTotal = RoundingHelper.RoundPrice(orderTotal);
            }
            return(orderTotal);
        }
        /// <summary>
        /// Update order totals
        /// </summary>
        /// <param name="updateOrderParameters">Parameters for the updating order</param>
        /// <param name="restoredCart">Shopping cart</param>
        public virtual void UpdateOrderTotals(UpdateOrderParameters updateOrderParameters, IList <ShoppingCartItem> restoredCart)
        {
            var updatedOrder     = updateOrderParameters.UpdatedOrder;
            var updatedOrderItem = updateOrderParameters.UpdatedOrderItem;

            //get the customer
            var customer = restoredCart.GetCustomer();

            #region Sub total

            var subTotalExclTax  = decimal.Zero;
            var subTotalInclTax  = decimal.Zero;
            var subTotalTaxRates = new SortedDictionary <decimal, decimal>();

            foreach (var shoppingCartItem in restoredCart)
            {
                var itemSubTotalExclTax = decimal.Zero;
                var itemSubTotalInclTax = decimal.Zero;
                var taxRate             = decimal.Zero;
                var itemDiscounts       = new List <DiscountForCaching>();

                //calculate subtotal for the updated order item
                if (shoppingCartItem.Id == updatedOrderItem.Id)
                {
                    //update order item
                    updatedOrderItem.UnitPriceExclTax      = updateOrderParameters.PriceExclTax;
                    updatedOrderItem.UnitPriceInclTax      = updateOrderParameters.PriceInclTax;
                    updatedOrderItem.DiscountAmountExclTax = updateOrderParameters.DiscountAmountExclTax;
                    updatedOrderItem.DiscountAmountInclTax = updateOrderParameters.DiscountAmountInclTax;
                    updatedOrderItem.PriceExclTax          = itemSubTotalExclTax = updateOrderParameters.SubTotalExclTax;
                    updatedOrderItem.PriceInclTax          = itemSubTotalInclTax = updateOrderParameters.SubTotalInclTax;
                    updatedOrderItem.Quantity = shoppingCartItem.Quantity;

                    taxRate = Math.Round((100 * (itemSubTotalInclTax - itemSubTotalExclTax)) / itemSubTotalExclTax, 3);
                }
                else
                {
                    //get the already calculated subtotal from the order item
                    itemSubTotalExclTax = updatedOrder.OrderItems.FirstOrDefault(item => item.Id == shoppingCartItem.Id).PriceExclTax;
                    itemSubTotalInclTax = updatedOrder.OrderItems.FirstOrDefault(item => item.Id == shoppingCartItem.Id).PriceInclTax;
                    taxRate             = Math.Round((100 * (itemSubTotalInclTax - itemSubTotalExclTax)) / itemSubTotalExclTax, 3);
                }

                foreach (var discount in itemDiscounts)
                {
                    if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount))
                    {
                        updateOrderParameters.AppliedDiscounts.Add(discount);
                    }
                }

                subTotalExclTax += itemSubTotalExclTax;
                subTotalInclTax += itemSubTotalInclTax;

                //tax rates
                var itemTaxValue = itemSubTotalInclTax - itemSubTotalExclTax;
                if (taxRate > decimal.Zero && itemTaxValue > decimal.Zero)
                {
                    if (!subTotalTaxRates.ContainsKey(taxRate))
                    {
                        subTotalTaxRates.Add(taxRate, itemTaxValue);
                    }
                    else
                    {
                        subTotalTaxRates[taxRate] = subTotalTaxRates[taxRate] + itemTaxValue;
                    }
                }
            }

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

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

            //We calculate discount amount on order subtotal excl tax (discount first)
            //calculate discount amount ('Applied to order subtotal' discount)
            List <DiscountForCaching> subTotalDiscounts;
            var discountAmountExclTax = GetOrderSubtotalDiscount(customer, subTotalExclTax, out subTotalDiscounts);
            if (subTotalExclTax < discountAmountExclTax)
            {
                discountAmountExclTax = subTotalExclTax;
            }
            var discountAmountInclTax = discountAmountExclTax;

            //add tax for shopping items
            var tempTaxRates = new Dictionary <decimal, decimal>(subTotalTaxRates);
            foreach (var kvp in tempTaxRates)
            {
                if (kvp.Value != decimal.Zero && subTotalExclTax > decimal.Zero)
                {
                    var discountTaxValue = kvp.Value * (discountAmountExclTax / subTotalExclTax);
                    discountAmountInclTax    += discountTaxValue;
                    subTotalTaxRates[kvp.Key] = kvp.Value - discountTaxValue;
                }
            }

            //rounding
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                subTotalExclTax       = RoundingHelper.RoundPrice(subTotalExclTax);
                subTotalInclTax       = RoundingHelper.RoundPrice(subTotalInclTax);
                discountAmountExclTax = RoundingHelper.RoundPrice(discountAmountExclTax);
                discountAmountInclTax = RoundingHelper.RoundPrice(discountAmountInclTax);
            }

            updatedOrder.OrderSubtotalExclTax         = subTotalExclTax;
            updatedOrder.OrderSubtotalInclTax         = subTotalInclTax;
            updatedOrder.OrderSubTotalDiscountExclTax = discountAmountExclTax;
            updatedOrder.OrderSubTotalDiscountInclTax = discountAmountInclTax;

            foreach (var discount in subTotalDiscounts)
            {
                if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount))
                {
                    updateOrderParameters.AppliedDiscounts.Add(discount);
                }
            }

            #endregion

            #region Tax rates

            var taxRates = new SortedDictionary <decimal, decimal>();

            //order subtotal taxes
            var subTotalTax = decimal.Zero;
            foreach (var kvp in subTotalTaxRates)
            {
                subTotalTax += kvp.Value;
                if (kvp.Key > decimal.Zero && kvp.Value > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(kvp.Key))
                    {
                        taxRates.Add(kvp.Key, kvp.Value);
                    }
                    else
                    {
                        taxRates[kvp.Key] = taxRates[kvp.Key] + kvp.Value;
                    }
                }
            }


            //payment method additional fee tax
            var paymentMethodAdditionalFeeTax = decimal.Zero;
            if (_taxSettings.PaymentMethodAdditionalFeeIsTaxable)
            {
                paymentMethodAdditionalFeeTax = updatedOrder.PaymentMethodAdditionalFeeInclTax - updatedOrder.PaymentMethodAdditionalFeeExclTax;
                if (paymentMethodAdditionalFeeTax < decimal.Zero)
                {
                    paymentMethodAdditionalFeeTax = decimal.Zero;
                }

                if (updatedOrder.PaymentMethodAdditionalFeeExclTax > decimal.Zero)
                {
                    var paymentTaxRate = Math.Round(100 * paymentMethodAdditionalFeeTax / updatedOrder.PaymentMethodAdditionalFeeExclTax, 3);
                    if (paymentTaxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero)
                    {
                        if (!taxRates.ContainsKey(paymentTaxRate))
                        {
                            taxRates.Add(paymentTaxRate, paymentMethodAdditionalFeeTax);
                        }
                        else
                        {
                            taxRates[paymentTaxRate] = taxRates[paymentTaxRate] + paymentMethodAdditionalFeeTax;
                        }
                    }
                }
            }

            //add at least one tax rate (0%)
            if (!taxRates.Any())
            {
                taxRates.Add(decimal.Zero, decimal.Zero);
            }

            //summarize taxes
            var taxTotal = subTotalTax + paymentMethodAdditionalFeeTax;
            if (taxTotal < decimal.Zero)
            {
                taxTotal = decimal.Zero;
            }

            //round tax
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                taxTotal = RoundingHelper.RoundPrice(taxTotal);
            }

            updatedOrder.OrderTax = taxTotal;
            updatedOrder.TaxRates = taxRates.Aggregate(string.Empty, (current, next) =>
                                                       string.Format("{0}{1}:{2};   ", current, next.Key.ToString(CultureInfo.InvariantCulture), next.Value.ToString(CultureInfo.InvariantCulture)));

            #endregion

            #region Total

            var total = (subTotalExclTax - discountAmountExclTax) + updatedOrder.PaymentMethodAdditionalFeeExclTax + taxTotal;

            //get discounts for the order total
            List <DiscountForCaching> orderAppliedDiscounts;
            var discountAmountTotal = GetOrderTotalDiscount(customer, total, out orderAppliedDiscounts);
            if (total < discountAmountTotal)
            {
                discountAmountTotal = total;
            }
            total -= discountAmountTotal;



            //reward points
            var rewardPointsOfOrder = _rewardPointService.GetRewardPointsHistory(customer.Id, true).FirstOrDefault(history => history.UsedWithOrder == updatedOrder);
            if (rewardPointsOfOrder != null)
            {
                var rewardPoints       = -rewardPointsOfOrder.Points;
                var rewardPointsAmount = ConvertRewardPointsToAmount(rewardPoints);
                if (total < rewardPointsAmount)
                {
                    rewardPoints       = ConvertAmountToRewardPoints(total);
                    rewardPointsAmount = total;
                }
                if (total > decimal.Zero)
                {
                    total -= rewardPointsAmount;
                }

                //uncomment here for the return unused reward points if new order total less redeemed reward points amount
                //if (rewardPoints < -rewardPointsOfOrder.Points)
                //    _rewardPointService.AddRewardPointsHistoryEntry(customer, -rewardPointsOfOrder.Points - rewardPoints, _storeContext.CurrentStore.Id, "Return unused reward points");

                if (rewardPointsAmount != rewardPointsOfOrder.UsedAmount)
                {
                    rewardPointsOfOrder.UsedAmount = rewardPointsAmount;
                    rewardPointsOfOrder.Points     = -rewardPoints;
                    _rewardPointService.UpdateRewardPointsHistoryEntry(rewardPointsOfOrder);
                }
            }

            //rounding
            if (total < decimal.Zero)
            {
                total = decimal.Zero;
            }
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                total = RoundingHelper.RoundPrice(total);
            }

            updatedOrder.OrderDiscount = discountAmountTotal;
            updatedOrder.OrderTotal    = total;

            foreach (var discount in orderAppliedDiscounts)
            {
                if (!updateOrderParameters.AppliedDiscounts.ContainsDiscount(discount))
                {
                    updateOrderParameters.AppliedDiscounts.Add(discount);
                }
            }

            #endregion
        }
        /// <summary>
        /// Gets tax
        /// </summary>
        /// <param name="cart">Shopping cart</param>
        /// <param name="taxRates">Tax rates</param>
        /// <param name="usePaymentMethodAdditionalFee">A value indicating whether we should use payment method additional fee when calculating tax</param>
        /// <returns>Tax total</returns>
        public virtual decimal GetTaxTotal(IList <ShoppingCartItem> cart,
                                           out SortedDictionary <decimal, decimal> taxRates, bool usePaymentMethodAdditionalFee = true)
        {
            if (cart == null)
            {
                throw new ArgumentNullException("cart");
            }

            taxRates = new SortedDictionary <decimal, decimal>();

            var    customer = cart.GetCustomer();
            string paymentMethodSystemName = "";

            if (customer != null)
            {
                paymentMethodSystemName = customer.GetAttribute <string>(
                    SystemCustomerAttributeNames.SelectedPaymentMethod,
                    _genericAttributeService,
                    _storeContext.CurrentStore.Id);
            }

            //order sub total (items + checkout attributes)
            decimal  subTotalTaxTotal = decimal.Zero;
            decimal  orderSubTotalDiscountAmount;
            Discount orderSubTotalAppliedDiscount;
            decimal  subTotalWithoutDiscountBase;
            decimal  subTotalWithDiscountBase;
            SortedDictionary <decimal, decimal> orderSubTotalTaxRates;

            GetShoppingCartSubTotal(cart, false,
                                    out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscount,
                                    out subTotalWithoutDiscountBase, out subTotalWithDiscountBase,
                                    out orderSubTotalTaxRates);
            foreach (KeyValuePair <decimal, decimal> kvp in orderSubTotalTaxRates)
            {
                decimal taxRate  = kvp.Key;
                decimal taxValue = kvp.Value;
                subTotalTaxTotal += taxValue;

                if (taxRate > decimal.Zero && taxValue > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, taxValue);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + taxValue;
                    }
                }
            }

            //shipping
            decimal shippingTax = decimal.Zero;

            if (_taxSettings.ShippingIsTaxable)
            {
                decimal taxRate;
                decimal?shippingExclTax = GetShoppingCartShippingTotal(cart, false, out taxRate);
                decimal?shippingInclTax = GetShoppingCartShippingTotal(cart, true, out taxRate);
                if (shippingExclTax.HasValue && shippingInclTax.HasValue)
                {
                    shippingTax = shippingInclTax.Value - shippingExclTax.Value;
                    //ensure that tax is equal or greater than zero
                    if (shippingTax < decimal.Zero)
                    {
                        shippingTax = decimal.Zero;
                    }

                    //tax rates
                    if (taxRate > decimal.Zero && shippingTax > decimal.Zero)
                    {
                        if (!taxRates.ContainsKey(taxRate))
                        {
                            taxRates.Add(taxRate, shippingTax);
                        }
                        else
                        {
                            taxRates[taxRate] = taxRates[taxRate] + shippingTax;
                        }
                    }
                }
            }

            //payment method additional fee
            decimal paymentMethodAdditionalFeeTax = decimal.Zero;

            if (usePaymentMethodAdditionalFee && _taxSettings.PaymentMethodAdditionalFeeIsTaxable)
            {
                decimal taxRate;
                decimal paymentMethodAdditionalFee        = _paymentService.GetAdditionalHandlingFee(cart, paymentMethodSystemName);
                decimal paymentMethodAdditionalFeeExclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer, out taxRate);
                decimal paymentMethodAdditionalFeeInclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, true, customer, out taxRate);

                paymentMethodAdditionalFeeTax = paymentMethodAdditionalFeeInclTax - paymentMethodAdditionalFeeExclTax;
                //ensure that tax is equal or greater than zero
                if (paymentMethodAdditionalFeeTax < decimal.Zero)
                {
                    paymentMethodAdditionalFeeTax = decimal.Zero;
                }

                //tax rates
                if (taxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, paymentMethodAdditionalFeeTax);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + paymentMethodAdditionalFeeTax;
                    }
                }
            }

            //add at least one tax rate (0%)
            if (taxRates.Count == 0)
            {
                taxRates.Add(decimal.Zero, decimal.Zero);
            }

            //summarize taxes
            decimal taxTotal = subTotalTaxTotal + shippingTax + paymentMethodAdditionalFeeTax;

            //ensure that tax is equal or greater than zero
            if (taxTotal < decimal.Zero)
            {
                taxTotal = decimal.Zero;
            }
            //round tax
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                taxTotal = RoundingHelper.RoundPrice(taxTotal);
            }
            return(taxTotal);
        }
Beispiel #16
0
        /// <summary>
        /// Gets shopping cart shipping total
        /// </summary>
        /// <param name="cart">Cart</param>
        /// <param name="includingTax">A value indicating whether calculated price should include tax</param>
        /// <param name="taxRate">Applied tax rate</param>
        /// <param name="appliedDiscount">Applied discount</param>
        /// <returns>Shipping total</returns>
        public virtual decimal?GetShoppingCartShippingTotal(IList <ShoppingCartItem> cart, bool includingTax,
                                                            out decimal taxRate, out List <Discount> appliedDiscounts)
        {
            decimal?shippingTotal      = null;
            decimal?shippingTotalTaxed = null;

            appliedDiscounts = new List <Discount>();
            taxRate          = decimal.Zero;

            var customer = cart.GetCustomer();

            bool isFreeShipping = IsFreeShipping(cart);

            if (isFreeShipping)
            {
                return(decimal.Zero);
            }

            ShippingOption shippingOption = null;

            if (customer != null)
            {
                shippingOption = customer.GetAttribute <ShippingOption>(SystemCustomerAttributeNames.SelectedShippingOption, _storeContext.CurrentStore.Id);
            }

            if (shippingOption != null)
            {
                shippingTotal = AdjustShippingRate(shippingOption.Rate, cart, out appliedDiscounts);
            }
            else
            {
                //use fixed rate (if possible)
                Address shippingAddress = null;
                if (customer != null)
                {
                    shippingAddress = customer.ShippingAddress;
                }

                var shippingRateComputationMethods = _shippingService.LoadActiveShippingRateComputationMethods(_storeContext.CurrentStore.Id, cart);

                if (!shippingRateComputationMethods.Any() && !_shippingSettings.AllowPickUpInStore)
                {
                    throw new GrandException("Shipping rate computation method could not be loaded");
                }

                if (shippingRateComputationMethods.Count == 1)
                {
                    var shippingRateComputationMethod = shippingRateComputationMethods[0];

                    bool shippingFromMultipleLocations;
                    var  shippingOptionRequests = _shippingService.CreateShippingOptionRequests(cart,
                                                                                                shippingAddress,
                                                                                                _storeContext.CurrentStore.Id,
                                                                                                out shippingFromMultipleLocations);
                    decimal?fixedRate = null;
                    foreach (var shippingOptionRequest in shippingOptionRequests)
                    {
                        //calculate fixed rates for each request-package
                        var fixedRateTmp = shippingRateComputationMethod.GetFixedRate(shippingOptionRequest);
                        if (fixedRateTmp.HasValue)
                        {
                            if (!fixedRate.HasValue)
                            {
                                fixedRate = decimal.Zero;
                            }

                            fixedRate += fixedRateTmp.Value;
                        }
                    }

                    if (fixedRate.HasValue)
                    {
                        //adjust shipping rate
                        shippingTotal = AdjustShippingRate(fixedRate.Value, cart, out appliedDiscounts);
                    }
                }
            }

            if (shippingTotal.HasValue)
            {
                if (shippingTotal.Value < decimal.Zero)
                {
                    shippingTotal = decimal.Zero;
                }

                //round
                if (_shoppingCartSettings.RoundPricesDuringCalculation)
                {
                    shippingTotal = RoundingHelper.RoundPrice(shippingTotal.Value);
                }

                shippingTotalTaxed = _taxService.GetShippingPrice(shippingTotal.Value,
                                                                  includingTax,
                                                                  customer,
                                                                  out taxRate);

                //round
                if (_shoppingCartSettings.RoundPricesDuringCalculation)
                {
                    shippingTotalTaxed = RoundingHelper.RoundPrice(shippingTotalTaxed.Value);
                }
            }

            return(shippingTotalTaxed);
        }
        /// <summary>
        /// Gets shopping cart total
        /// </summary>
        /// <param name="cart">Cart</param>
        /// <param name="appliedGiftCards">Applied gift cards</param>
        /// <param name="discountAmount">Applied discount amount</param>
        /// <param name="appliedDiscount">Applied discount</param>
        /// <param name="redeemedRewardPoints">Reward points to redeem</param>
        /// <param name="redeemedRewardPointsAmount">Reward points amount in primary store currency to redeem</param>
        /// <param name="ignoreRewardPonts">A value indicating whether we should ignore reward points (if enabled and a customer is going to use them)</param>
        /// <param name="usePaymentMethodAdditionalFee">A value indicating whether we should use payment method additional fee when calculating order total</param>
        /// <returns>Shopping cart total;Null if shopping cart total couldn't be calculated now</returns>
        public virtual decimal?GetShoppingCartTotal(IList <ShoppingCartItem> cart,
                                                    out decimal discountAmount, out Discount appliedDiscount,
                                                    out List <AppliedGiftCard> appliedGiftCards,
                                                    out int redeemedRewardPoints, out decimal redeemedRewardPointsAmount,
                                                    bool ignoreRewardPonts = false, bool usePaymentMethodAdditionalFee = true)
        {
            redeemedRewardPoints       = 0;
            redeemedRewardPointsAmount = decimal.Zero;

            var    customer = cart.GetCustomer();
            string paymentMethodSystemName = "";

            if (customer != null)
            {
                paymentMethodSystemName = customer.GetAttribute <string>(
                    SystemCustomerAttributeNames.SelectedPaymentMethod,
                    _genericAttributeService,
                    _storeContext.CurrentStore.Id);
            }


            //subtotal without tax
            decimal  orderSubTotalDiscountAmount;
            Discount orderSubTotalAppliedDiscount;
            decimal  subTotalWithoutDiscountBase;
            decimal  subTotalWithDiscountBase;

            GetShoppingCartSubTotal(cart, false,
                                    out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscount,
                                    out subTotalWithoutDiscountBase, out subTotalWithDiscountBase);
            //subtotal with discount
            decimal subtotalBase = subTotalWithDiscountBase;



            //shipping without tax
            decimal?shoppingCartShipping = GetShoppingCartShippingTotal(cart, false);



            //payment method additional fee without tax
            decimal paymentMethodAdditionalFeeWithoutTax = decimal.Zero;

            if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName))
            {
                decimal paymentMethodAdditionalFee = _paymentService.GetAdditionalHandlingFee(cart,
                                                                                              paymentMethodSystemName);
                paymentMethodAdditionalFeeWithoutTax =
                    _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee,
                                                              false, customer);
            }



            //tax
            decimal shoppingCartTax = GetTaxTotal(cart, usePaymentMethodAdditionalFee);



            //order total
            decimal resultTemp = decimal.Zero;

            resultTemp += subtotalBase;
            if (shoppingCartShipping.HasValue)
            {
                resultTemp += shoppingCartShipping.Value;
            }
            resultTemp += paymentMethodAdditionalFeeWithoutTax;
            resultTemp += shoppingCartTax;
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp);
            }

            #region Order total discount

            discountAmount = GetOrderTotalDiscount(customer, resultTemp, out appliedDiscount);

            //sub totals with discount
            if (resultTemp < discountAmount)
            {
                discountAmount = resultTemp;
            }

            //reduce subtotal
            resultTemp -= discountAmount;

            if (resultTemp < decimal.Zero)
            {
                resultTemp = decimal.Zero;
            }
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp);
            }

            #endregion

            #region Applied gift cards

            //let's apply gift cards now (gift cards that can be used)
            appliedGiftCards = new List <AppliedGiftCard>();
            if (!cart.IsRecurring())
            {
                //we don't apply gift cards for recurring products
                var giftCards = _giftCardService.GetActiveGiftCardsAppliedByCustomer(customer);
                if (giftCards != null)
                {
                    foreach (var gc in giftCards)
                    {
                        if (resultTemp > decimal.Zero)
                        {
                            decimal remainingAmount = gc.GetGiftCardRemainingAmount();
                            decimal amountCanBeUsed = decimal.Zero;
                            if (resultTemp > remainingAmount)
                            {
                                amountCanBeUsed = remainingAmount;
                            }
                            else
                            {
                                amountCanBeUsed = resultTemp;
                            }

                            //reduce subtotal
                            resultTemp -= amountCanBeUsed;

                            var appliedGiftCard = new AppliedGiftCard();
                            appliedGiftCard.GiftCard        = gc;
                            appliedGiftCard.AmountCanBeUsed = amountCanBeUsed;
                            appliedGiftCards.Add(appliedGiftCard);
                        }
                    }
                }
            }

            #endregion

            if (resultTemp < decimal.Zero)
            {
                resultTemp = decimal.Zero;
            }
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                resultTemp = RoundingHelper.RoundPrice(resultTemp);
            }

            if (!shoppingCartShipping.HasValue)
            {
                //we have errors
                return(null);
            }

            decimal orderTotal = resultTemp;

            #region Reward points

            if (_rewardPointsSettings.Enabled &&
                !ignoreRewardPonts &&
                customer.GetAttribute <bool>(SystemCustomerAttributeNames.UseRewardPointsDuringCheckout,
                                             _genericAttributeService, _storeContext.CurrentStore.Id))
            {
                int rewardPointsBalance = customer.GetRewardPointsBalance();
                if (CheckMinimumRewardPointsToUseRequirement(rewardPointsBalance))
                {
                    decimal rewardPointsBalanceAmount = ConvertRewardPointsToAmount(rewardPointsBalance);
                    if (orderTotal > decimal.Zero)
                    {
                        if (orderTotal > rewardPointsBalanceAmount)
                        {
                            redeemedRewardPoints       = rewardPointsBalance;
                            redeemedRewardPointsAmount = rewardPointsBalanceAmount;
                        }
                        else
                        {
                            redeemedRewardPointsAmount = orderTotal;
                            redeemedRewardPoints       = ConvertAmountToRewardPoints(redeemedRewardPointsAmount);
                        }
                    }
                }
            }

            #endregion

            orderTotal = orderTotal - redeemedRewardPointsAmount;
            if (_shoppingCartSettings.RoundPricesDuringCalculation)
            {
                orderTotal = RoundingHelper.RoundPrice(orderTotal);
            }
            return(orderTotal);
        }
        GetShoppingCartSubTotal(IList <ShoppingCartItem> cart, bool includingTax)
        {
            var discountAmount          = decimal.Zero;
            var appliedDiscounts        = new List <ApplyDiscount>();
            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
            Customer customer = _workContext.CurrentCustomer;

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

            foreach (var shoppingCartItem in cart)
            {
                var product = await _productService.GetProductById(shoppingCartItem.ProductId);

                if (product == null)
                {
                    continue;
                }

                var subtotal = await _pricingService.GetSubTotal(shoppingCartItem, product);

                decimal sciSubTotal = subtotal.subTotal;

                decimal taxRate;
                var     pricesExcl = await _taxService.GetProductPrice(product, sciSubTotal, false, customer);

                decimal sciExclTax = pricesExcl.productprice;

                var pricesIncl = await _taxService.GetProductPrice(product, sciSubTotal, true, customer);

                decimal sciInclTax = pricesIncl.productprice;
                taxRate = pricesIncl.taxRate;

                subTotalExclTaxWithoutDiscount += sciExclTax;
                subTotalInclTaxWithoutDiscount += sciInclTax;

                //tax rates
                decimal sciTax = sciInclTax - sciExclTax;
                if (taxRate > decimal.Zero && sciTax > decimal.Zero)
                {
                    if (!taxRates.ContainsKey(taxRate))
                    {
                        taxRates.Add(taxRate, sciTax);
                    }
                    else
                    {
                        taxRates[taxRate] = taxRates[taxRate] + sciTax;
                    }
                }
            }

            //checkout attributes
            if (customer != null)
            {
                var checkoutAttributes = customer.GetUserFieldFromEntity <List <CustomAttribute> >(SystemCustomerFieldNames.CheckoutAttributes, _workContext.CurrentStore.Id);
                var attributeValues    = await _checkoutAttributeParser.ParseCheckoutAttributeValue(checkoutAttributes);

                foreach (var attributeValue in attributeValues)
                {
                    decimal taxRate;
                    var     checkoutAttributePriceExclTax = await _taxService.GetCheckoutAttributePrice(attributeValue.ca, attributeValue.cav, false, customer);

                    decimal caExclTax = await _currencyService.ConvertFromPrimaryStoreCurrency(checkoutAttributePriceExclTax.checkoutPrice, _workContext.WorkingCurrency);

                    var checkoutAttributePriceInclTax = await _taxService.GetCheckoutAttributePrice(attributeValue.ca, attributeValue.cav, true, customer);

                    decimal caInclTax = await _currencyService.ConvertFromPrimaryStoreCurrency(checkoutAttributePriceInclTax.checkoutPrice, _workContext.WorkingCurrency);

                    taxRate = checkoutAttributePriceInclTax.taxRate;

                    subTotalExclTaxWithoutDiscount += caExclTax;
                    subTotalInclTaxWithoutDiscount += caInclTax;

                    //tax rates
                    decimal caTax = caInclTax - caExclTax;
                    if (taxRate > decimal.Zero && caTax > decimal.Zero)
                    {
                        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.RoundPrices)
            {
                subTotalWithoutDiscount = RoundingHelper.RoundPrice(subTotalWithoutDiscount, _workContext.WorkingCurrency);
            }
            //We calculate discount amount on order subtotal excl tax (discount first)
            //calculate discount amount ('Applied to order subtotal' discount)
            var orderSubtotalDiscount = await GetOrderSubtotalDiscount(customer, _workContext.WorkingCurrency, subTotalExclTaxWithoutDiscount);

            decimal discountAmountExclTax = orderSubtotalDiscount.ordersubtotaldiscount;

            appliedDiscounts = orderSubtotalDiscount.appliedDiscounts;

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

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

            foreach (KeyValuePair <decimal, decimal> kvp in tempTaxRates)
            {
                decimal taxRate  = kvp.Key;
                decimal taxValue = kvp.Value;

                if (taxValue != decimal.Zero)
                {
                    //discount the tax amount that applies to subtotal items
                    if (subTotalExclTaxWithoutDiscount > decimal.Zero)
                    {
                        decimal discountTax = taxRates[taxRate] * (discountAmountExclTax / subTotalExclTaxWithoutDiscount);
                        discountAmountInclTax += discountTax;
                        taxValue = taxRates[taxRate] - discountTax;
                        if (_shoppingCartSettings.RoundPrices)
                        {
                            taxValue = RoundingHelper.RoundPrice(taxValue, _workContext.WorkingCurrency);
                        }
                        taxRates[taxRate] = taxValue;
                    }

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

            if (_shoppingCartSettings.RoundPrices)
            {
                discountAmountInclTax = RoundingHelper.RoundPrice(discountAmountInclTax, _workContext.WorkingCurrency);
                discountAmountExclTax = RoundingHelper.RoundPrice(discountAmountExclTax, _workContext.WorkingCurrency);
            }

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

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

            if (_shoppingCartSettings.RoundPrices)
            {
                subTotalWithDiscount = RoundingHelper.RoundPrice(subTotalWithDiscount, _workContext.WorkingCurrency);
            }

            return(discountAmount, appliedDiscounts, subTotalWithoutDiscount, subTotalWithDiscount, taxRates);
        }
Beispiel #19
0
        /// <summary>
        ///  Gets available shipping options
        /// </summary>
        /// <param name="cart">Shopping cart</param>
        /// <param name="shippingAddress">Shipping address</param>
        /// <param name="customer">Load records allowed only to a specified customer; pass null to ignore ACL permissions</param>
        /// <param name="allowedShippingRateComputationMethodSystemName">Filter by shipping rate computation method identifier; null to load shipping options of all shipping rate computation methods</param>
        /// <param name="storeId">Load records allowed only in a specified store; pass 0 to load all records</param>
        /// <returns>Shipping options</returns>
        public virtual GetShippingOptionResponse GetShippingOptions(IList <ShoppingCartItem> cart,
                                                                    Address shippingAddress, Customer customer = null, string allowedShippingRateComputationMethodSystemName = "",
                                                                    int storeId = 0)
        {
            if (cart == null)
            {
                throw new ArgumentNullException(nameof(cart));
            }

            var result = new GetShippingOptionResponse();

            //create a package
            var shippingOptionRequests = CreateShippingOptionRequests(cart, shippingAddress, storeId, out bool shippingFromMultipleLocations);

            result.ShippingFromMultipleLocations = shippingFromMultipleLocations;

            var shippingRateComputationMethods = LoadActiveShippingRateComputationMethods(customer, storeId);

            //filter by system name
            if (!string.IsNullOrWhiteSpace(allowedShippingRateComputationMethodSystemName))
            {
                shippingRateComputationMethods = shippingRateComputationMethods
                                                 .Where(srcm => allowedShippingRateComputationMethodSystemName.Equals(srcm.PluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase))
                                                 .ToList();
            }
            if (!shippingRateComputationMethods.Any())
            {
                //throw new NopException("Shipping rate computation method could not be loaded");
                return(result);
            }



            //request shipping options from each shipping rate computation methods
            foreach (var srcm in shippingRateComputationMethods)
            {
                //request shipping options (separately for each package-request)
                IList <ShippingOption> srcmShippingOptions = null;
                foreach (var shippingOptionRequest in shippingOptionRequests)
                {
                    var getShippingOptionResponse = srcm.GetShippingOptions(shippingOptionRequest);

                    if (getShippingOptionResponse.Success)
                    {
                        //success
                        if (srcmShippingOptions == null)
                        {
                            //first shipping option request
                            srcmShippingOptions = getShippingOptionResponse.ShippingOptions;
                        }
                        else
                        {
                            //get shipping options which already exist for prior requested packages for this scrm (i.e. common options)
                            srcmShippingOptions = srcmShippingOptions
                                                  .Where(existingso => getShippingOptionResponse.ShippingOptions.Any(newso => newso.Name == existingso.Name))
                                                  .ToList();

                            //and sum the rates
                            foreach (var existingso in srcmShippingOptions)
                            {
                                existingso.Rate += getShippingOptionResponse
                                                   .ShippingOptions
                                                   .First(newso => newso.Name == existingso.Name)
                                                   .Rate;
                            }
                        }
                    }
                    else
                    {
                        //errors
                        foreach (var error in getShippingOptionResponse.Errors)
                        {
                            result.AddError(error);
                            _logger.Warning($"Shipping ({srcm.PluginDescriptor.FriendlyName}). {error}");
                        }
                        //clear the shipping options in this case
                        srcmShippingOptions = new List <ShippingOption>();
                        break;
                    }
                }

                //add this scrm's options to the result
                if (srcmShippingOptions != null)
                {
                    foreach (var so in srcmShippingOptions)
                    {
                        //set system name if not set yet
                        if (string.IsNullOrEmpty(so.ShippingRateComputationMethodSystemName))
                        {
                            so.ShippingRateComputationMethodSystemName = srcm.PluginDescriptor.SystemName;
                        }
                        if (_shoppingCartSettings.RoundPricesDuringCalculation)
                        {
                            so.Rate = RoundingHelper.RoundPrice(so.Rate);
                        }
                        result.ShippingOptions.Add(so);
                    }
                }
            }

            if (_shippingSettings.ReturnValidOptionsIfThereAreAny)
            {
                //return valid options if there are any (no matter of the errors returned by other shipping rate computation methods).
                if (result.ShippingOptions.Any() && result.Errors.Any())
                {
                    result.Errors.Clear();
                }
            }

            //no shipping options loaded
            if (!result.ShippingOptions.Any() && !result.Errors.Any())
            {
                result.Errors.Add(_localizationService.GetResource("Checkout.ShippingOptionCouldNotBeLoaded"));
            }

            return(result);
        }
        /// <summary>
        /// Generate a feed
        /// </summary>
        /// <param name="stream">Stream</param>
        /// <param name="store">Store</param>
        /// <returns>Generated feed</returns>
        public void GenerateFeed(Stream stream, Store store)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

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

            const string googleBaseNamespace = "http://base.google.com/ns/1.0";

            var settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8
            };

            //language
            var languageId = 0;
            var languages  = _languageService.GetAllLanguages(storeId: store.Id);

            //if we have only one language, let's use it
            if (languages.Count == 1)
            {
                //let's use the first one
                var language = languages.FirstOrDefault();
                languageId = language != null ? language.Id : 0;
            }
            //otherwise, use the current one
            if (languageId == 0)
            {
                languageId = _workContext.WorkingLanguage.Id;
            }

            //we load all Google products here using one SQL request (performance optimization)
            var allGoogleProducts = _googleService.GetAll();



            //google
            GoogleWebAuthorizationBroker.Folder = "ShoppingContent.Sample";
            var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                new ClientSecrets
            {
                ClientId     = "584676283202-qphqjj998id40caks269cjag92dsc4rn.apps.googleusercontent.com",
                ClientSecret = "mv3j9LBNBKuGXjM1xLCi6Tbr"
            },
                new string[] { ShoppingContentService.Scope.Content },
                "user",
                CancellationToken.None).Result;

            // Create the service.
            var service = new ShoppingContentService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName       = "Shopping Content Arc",
            });



            using (var writer = XmlWriter.Create(stream, settings))
            {
                //Generate feed according to the following specs: http://www.google.com/support/merchants/bin/answer.py?answer=188494&expand=GB
                writer.WriteStartDocument();
                writer.WriteStartElement("rss");
                writer.WriteAttributeString("version", "2.0");
                writer.WriteAttributeString("xmlns", "g", null, googleBaseNamespace);
                writer.WriteStartElement("channel");
                writer.WriteElementString("title", "Google Base feed");
                writer.WriteElementString("link", "http://base.google.com/base/");
                writer.WriteElementString("description", "Information about products");


                var products1 = _productService.SearchProducts(storeId: store.Id, visibleIndividuallyOnly: true)
                                .Where(x => !x.ExcludeGoogleFeed);


                var query = from p in products1
                            from pc in p.ProductCategories.Where(pc => !pc.Category.Name.Contains("Copier") && !pc.Category.Name.Contains("Copiers - New"))
                            select p;

                foreach (var product1 in products1)
                {
                    Google.Apis.ShoppingContent.v2.Data.Product productG = new Google.Apis.ShoppingContent.v2.Data.Product();

                    var productsToProcess = new List <Core.Domain.Catalog.Product>();
                    productG.ContentLanguage       = "EN";
                    productG.TargetCountry         = "US";
                    productG.Channel               = "online";
                    productG.Availability          = "in stock";
                    productG.Condition             = "new";
                    productG.GoogleProductCategory = "Electronics > Print, Copy, Scan & Fax Accessories > Copier Accessories";
                    productG.Gtin = product1.Gtin;



                    var legacy_Reader = _dbContext.SqlQuery <string>($"SELECT LegacyCode FROM [dbo].LegacyIds where ItemID={product1.Id}").ToList();
                    if (legacy_Reader.Count > 0)
                    {
                        foreach (var id in legacy_Reader)
                        {
                            productG.Title += $" - {id}";
                        }
                    }

                    var categoryP = product1.ProductCategories.FirstOrDefault();
                    if (categoryP != null)
                    {
                        if (categoryP.Category.Name.Contains("Toner"))
                        {
                            productG.ProductType = "Electronics > Print, Copy, Scan & Fax Accessories > Printer Accessories > Toner & Inkjet Cartridges";
                        }
                        else
                        {
                            productG.ProductType = "Electronics > Print, Copy, Scan & Fax Accessories";
                        }
                    }



                    switch (product1.ProductType)
                    {
                    case ProductType.SimpleProduct:
                    {
                        //simple product doesn't have child products
                        productsToProcess.Add(product1);
                    }
                    break;

                    case ProductType.GroupedProduct:
                    {
                        //grouped products could have several child products
                        var associatedProducts = _productService.GetAssociatedProducts(product1.Id, store.Id);
                        productsToProcess.AddRange(associatedProducts);
                    }
                    break;

                    default:
                        continue;
                    }
                    foreach (var product in productsToProcess)
                    {
                        writer.WriteStartElement("item");

                        #region Basic Product Information

                        //id [id]- An identifier of the item
                        writer.WriteElementString("g", "id", googleBaseNamespace, product.Id.ToString());

                        //title [title] - Title of the item
                        writer.WriteStartElement("title");
                        var title = product.GetLocalized(x => x.Name, languageId);
                        //title should be not longer than 70 characters
                        if (title.Length > 70)
                        {
                            title = title.Substring(0, 70);
                        }
                        writer.WriteCData(title);
                        writer.WriteEndElement(); // title

                        //description [description] - Description of the item
                        writer.WriteStartElement("description");
                        string description = product.GetLocalized(x => x.FullDescription, languageId);
                        if (String.IsNullOrEmpty(description))
                        {
                            description = product.GetLocalized(x => x.ShortDescription, languageId);
                        }
                        if (String.IsNullOrEmpty(description))
                        {
                            description = product.GetLocalized(x => x.Name, languageId); //description is required
                        }
                        //resolving character encoding issues in your data feed
                        description = StripInvalidChars(description, true);
                        writer.WriteCData(description);
                        writer.WriteEndElement(); // description



                        //google product category [google_product_category] - Google's category of the item
                        //the category of the product according to Google’s product taxonomy. http://www.google.com/support/merchants/bin/answer.py?answer=160081
                        string googleProductCategory = "";
                        //var googleProduct = _googleService.GetByProductId(product.Id);
                        var googleProduct = allGoogleProducts.FirstOrDefault(x => x.ProductId == product.Id);
                        if (googleProduct != null)
                        {
                            googleProductCategory = googleProduct.Taxonomy;
                        }
                        if (String.IsNullOrEmpty(googleProductCategory))
                        {
                            googleProductCategory = _googleShoppingSettings.DefaultGoogleCategory;
                        }
                        if (String.IsNullOrEmpty(googleProductCategory))
                        {
                            throw new NopException("Default Google category is not set");
                        }
                        writer.WriteStartElement("g", "google_product_category", googleBaseNamespace);
                        writer.WriteCData(googleProductCategory);
                        writer.WriteFullEndElement(); // g:google_product_category

                        //product type [product_type] - Your category of the item
                        var defaultProductCategory = _categoryService
                                                     .GetProductCategoriesByProductId(product.Id, store.Id)
                                                     .FirstOrDefault();
                        if (defaultProductCategory != null)
                        {
                            //TODO localize categories
                            var category = defaultProductCategory.Category
                                           .GetFormattedBreadCrumb(_categoryService, separator: ">", languageId: languageId);
                            if (!String.IsNullOrEmpty(category))
                            {
                                writer.WriteStartElement("g", "product_type", googleBaseNamespace);
                                writer.WriteCData(category);
                                writer.WriteFullEndElement(); // g:product_type
                            }
                        }

                        //link [link] - URL directly linking to your item's page on your website
                        var productUrl = GetUrlHelper().RouteUrl("Product", new { SeName = product.GetSeName(languageId) }, GetHttpProtocol());

                        productG.Link = productUrl;

                        writer.WriteElementString("link", productUrl);

                        //image link [image_link] - URL of an image of the item
                        //additional images [additional_image_link]
                        //up to 10 pictures
                        const int maximumPictures = 10;
                        var       storeLocation   = _securitySettings.ForceSslForAllPages ?
                                                    (!string.IsNullOrWhiteSpace(store.SecureUrl) ? store.SecureUrl : store.Url.Replace("http://", "https://")):
                                                    store.Url;
                        var pictures = _pictureService.GetPicturesByProductId(product.Id, maximumPictures);
                        for (int i = 0; i < pictures.Count; i++)
                        {
                            var picture  = pictures[i];
                            var imageUrl = _pictureService.GetPictureUrl(picture,
                                                                         _googleShoppingSettings.ProductPictureSize,
                                                                         storeLocation: storeLocation);

                            if (i == 0)
                            {
                                //default image

                                productG.ImageLink = imageUrl;

                                writer.WriteElementString("g", "image_link", googleBaseNamespace, imageUrl);
                            }
                            else
                            {
                                //additional image

                                productG.AdditionalImageLinks.Add(imageUrl);

                                writer.WriteElementString("g", "additional_image_link", googleBaseNamespace, imageUrl);
                            }
                        }
                        if (!pictures.Any())
                        {
                            //no picture? submit a default one
                            var imageUrl = _pictureService.GetDefaultPictureUrl(_googleShoppingSettings.ProductPictureSize, storeLocation: storeLocation);

                            productG.ImageLink = imageUrl;

                            writer.WriteElementString("g", "image_link", googleBaseNamespace, imageUrl);
                        }

                        //condition [condition] - Condition or state of the item

                        productG.Condition      = "new";
                        productG.ExpirationDate = DateTime.Now.AddDays(_googleShoppingSettings.ExpirationNumberOfDays).ToString("yyyy-MM-dd");

                        writer.WriteElementString("g", "condition", googleBaseNamespace, "new");

                        writer.WriteElementString("g", "expiration_date", googleBaseNamespace, DateTime.Now.AddDays(_googleShoppingSettings.ExpirationNumberOfDays).ToString("yyyy-MM-dd"));

                        #endregion

                        #region Availability & Price

                        //availability [availability] - Availability status of the item
                        string availability = "in stock"; //in stock by default
                        if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock &&
                            product.BackorderMode == BackorderMode.NoBackorders &&
                            product.GetTotalStockQuantity() <= 0)
                        {
                            availability = "out of stock";
                        }
                        //uncomment th code below in order to support "preorder" value for "availability"
                        //if (product.AvailableForPreOrder &&
                        //    (!product.PreOrderAvailabilityStartDateTimeUtc.HasValue ||
                        //    product.PreOrderAvailabilityStartDateTimeUtc.Value >= DateTime.UtcNow))
                        //{
                        //    availability = "preorder";
                        //}

                        productG.Availability = availability;

                        writer.WriteElementString("g", "availability", googleBaseNamespace, availability);

                        //price [price] - Price of the item
                        var     currency = GetUsedCurrency();
                        decimal finalPriceBase;
                        if (_googleShoppingSettings.PricesConsiderPromotions)
                        {
                            var minPossiblePrice = _priceCalculationService.GetFinalPrice(product, _workContext.CurrentCustomer);

                            if (product.HasTierPrices)
                            {
                                //calculate price for the maximum quantity if we have tier prices, and choose minimal
                                minPossiblePrice = Math.Min(minPossiblePrice,
                                                            _priceCalculationService.GetFinalPrice(product, _workContext.CurrentCustomer, quantity: int.MaxValue));
                            }

                            finalPriceBase = _taxService.GetProductPrice(product, minPossiblePrice, out decimal _);
                        }
                        else
                        {
                            finalPriceBase = product.Price;
                        }
                        decimal price = _currencyService.ConvertFromPrimaryStoreCurrency(finalPriceBase, currency);
                        //round price now so it matches the product details page
                        price = RoundingHelper.RoundPrice(price);

                        Price priceG = new Price {
                            Currency = currency.CurrencyCode, Value = price.ToString(new CultureInfo("en-US", false).NumberFormat)
                        };

                        productG.Price = priceG;

                        writer.WriteElementString("g", "price", googleBaseNamespace,
                                                  price.ToString(new CultureInfo("en-US", false).NumberFormat) + " " +
                                                  currency.CurrencyCode);

                        #endregion

                        #region Unique Product Identifiers

                        /* Unique product identifiers such as UPC, EAN, JAN or ISBN allow us to show your listing on the appropriate product page. If you don't provide the required unique product identifiers, your store may not appear on product pages, and all your items may be removed from Product Search.
                         * We require unique product identifiers for all products - except for custom made goods. For apparel, you must submit the 'brand' attribute. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute. In all cases, we recommend you submit all three attributes.
                         * You need to submit at least two attributes of 'brand', 'gtin' and 'mpn', but we recommend that you submit all three if available. For media (such as books, movies, music and video games), you must submit the 'gtin' attribute, but we recommend that you include 'brand' and 'mpn' if available.
                         */

                        //GTIN [gtin] - GTIN
                        var gtin = product.Gtin;
                        if (!String.IsNullOrEmpty(gtin))
                        {
                            productG.Gtin = gtin;

                            writer.WriteStartElement("g", "gtin", googleBaseNamespace);
                            writer.WriteCData(gtin);
                            writer.WriteFullEndElement(); // g:gtin
                        }

                        //brand [brand] - Brand of the item
                        var defaultManufacturer =
                            _manufacturerService.GetProductManufacturersByProductId((product.Id)).FirstOrDefault();
                        if (defaultManufacturer != null)
                        {
                            productG.Brand = defaultManufacturer.Manufacturer.Name;

                            writer.WriteStartElement("g", "brand", googleBaseNamespace);
                            writer.WriteCData(defaultManufacturer.Manufacturer.Name);
                            writer.WriteFullEndElement(); // g:brand
                        }


                        //mpn [mpn] - Manufacturer Part Number (MPN) of the item
                        var mpn = product.ManufacturerPartNumber;
                        if (!String.IsNullOrEmpty(mpn))
                        {
                            productG.Mpn = mpn;

                            writer.WriteStartElement("g", "mpn", googleBaseNamespace);
                            writer.WriteCData(mpn);
                            writer.WriteFullEndElement(); // g:mpn
                        }

                        //identifier exists [identifier_exists] - Submit custom goods
                        if (googleProduct != null && googleProduct.CustomGoods)
                        {
                            productG.IdentifierExists = false;

                            writer.WriteElementString("g", "identifier_exists", googleBaseNamespace, "FALSE");
                        }

                        #endregion

                        #region Apparel Products

                        /* Apparel includes all products that fall under 'Apparel & Accessories' (including all sub-categories)
                         * in Google’s product taxonomy.
                         */

                        //gender [gender] - Gender of the item
                        if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.Gender))
                        {
                            productG.Gender = googleProduct.Gender;

                            writer.WriteStartElement("g", "gender", googleBaseNamespace);
                            writer.WriteCData(googleProduct.Gender);
                            writer.WriteFullEndElement(); // g:gender
                        }

                        //age group [age_group] - Target age group of the item
                        if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.AgeGroup))
                        {
                            productG.AgeGroup = googleProduct.AgeGroup;

                            writer.WriteStartElement("g", "age_group", googleBaseNamespace);
                            writer.WriteCData(googleProduct.AgeGroup);
                            writer.WriteFullEndElement(); // g:age_group
                        }

                        //color [color] - Color of the item
                        if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.Color))
                        {
                            productG.Color = googleProduct.Color;

                            writer.WriteStartElement("g", "color", googleBaseNamespace);
                            writer.WriteCData(googleProduct.Color);
                            writer.WriteFullEndElement(); // g:color
                        }

                        //size [size] - Size of the item
                        if (googleProduct != null && !String.IsNullOrEmpty(googleProduct.Size))
                        {
                            productG.Sizes.Add(googleProduct.Size);

                            writer.WriteStartElement("g", "size", googleBaseNamespace);
                            writer.WriteCData(googleProduct.Size);
                            writer.WriteFullEndElement(); // g:size
                        }

                        #endregion

                        #region Tax & Shipping

                        //tax [tax]
                        //The tax attribute is an item-level override for merchant-level tax settings as defined in your Google Merchant Center account. This attribute is only accepted in the US, if your feed targets a country outside of the US, please do not use this attribute.
                        //IMPORTANT NOTE: Set tax in your Google Merchant Center account settings

                        //IMPORTANT NOTE: Set shipping in your Google Merchant Center account settings

                        //shipping weight [shipping_weight] - Weight of the item for shipping
                        //We accept only the following units of weight: lb, oz, g, kg.
                        if (_googleShoppingSettings.PassShippingInfoWeight)
                        {
                            string weightName;
                            var    shippingWeight   = product.Weight;
                            var    weightSystemName = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).SystemKeyword;
                            switch (weightSystemName)
                            {
                            case "ounce":
                                weightName = "oz";
                                break;

                            case "lb":
                                weightName = "lb";
                                break;

                            case "grams":
                                weightName = "g";
                                break;

                            case "kg":
                                weightName = "kg";
                                break;

                            default:
                                //unknown weight
                                throw new Exception("Not supported weight. Google accepts the following units: lb, oz, g, kg.");
                            }
                            ProductShippingDimension psd = new ProductShippingDimension
                            {
                                Unit  = weightName,
                                Value = double.Parse(shippingWeight.ToString())
                            };

                            productG.ShippingHeight = psd;

                            writer.WriteElementString("g", "shipping_weight", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", shippingWeight.ToString(new CultureInfo("en-US", false).NumberFormat), weightName));
                        }

                        //shipping length [shipping_length] - Length of the item for shipping
                        //shipping width [shipping_width] - Width of the item for shipping
                        //shipping height [shipping_height] - Height of the item for shipping
                        //We accept only the following units of length: in, cm
                        if (_googleShoppingSettings.PassShippingInfoDimensions)
                        {
                            string dimensionName;
                            var    length = product.Length;
                            var    width  = product.Width;
                            var    height = product.Height;
                            var    dimensionSystemName = _measureService.GetMeasureDimensionById(_measureSettings.BaseDimensionId).SystemKeyword;
                            switch (dimensionSystemName)
                            {
                            case "inches":
                                dimensionName = "in";
                                break;

                            //TODO support other dimensions (convert to cm)
                            default:
                                //unknown dimension
                                throw new Exception("Not supported dimension. Google accepts the following units: in, cm.");
                            }

                            ProductShippingDimension psd = new ProductShippingDimension
                            {
                                Unit  = dimensionName,
                                Value = double.Parse(length.ToString())
                            };
                            productG.ShippingLength = psd;

                            psd = new ProductShippingDimension
                            {
                                Unit  = dimensionName,
                                Value = double.Parse(width.ToString())
                            };
                            productG.ShippingWidth = psd;

                            psd = new ProductShippingDimension
                            {
                                Unit  = dimensionName,
                                Value = double.Parse(height.ToString())
                            };
                            productG.ShippingHeight = psd;

                            writer.WriteElementString("g", "shipping_length", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", length.ToString(new CultureInfo("en-US", false).NumberFormat), dimensionName));
                            writer.WriteElementString("g", "shipping_width", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", width.ToString(new CultureInfo("en-US", false).NumberFormat), dimensionName));
                            writer.WriteElementString("g", "shipping_height", googleBaseNamespace, string.Format(CultureInfo.InvariantCulture, "{0} {1}", height.ToString(new CultureInfo("en-US", false).NumberFormat), dimensionName));
                        }

                        #endregion

                        writer.WriteEndElement(); // item


                        //used in in description
                        string usedinquery = $@"SELECT     ItemsCompatability.ItemID 
                        FROM         ItemsCompatability INNER JOIN
                                              Items ON ItemsCompatability.ItemID = Items.ItemID
                        WHERE     (ItemsCompatability.ItemIDPart ={product.Id})
                        UNION
                        SELECT     [Groups-Items].ItemID 
                        FROM         [Groups-Items] INNER JOIN
                                              Items AS Items_1 ON [Groups-Items].ItemID = Items_1.ItemID
                        WHERE     ([Groups-Items].GroupID IN
                                                  (SELECT     GroupID
                                                    FROM          [Relations-Groups-Items]
                                                    WHERE      (ItemID ={product.Id}) AND (Direction = 'B')))
                        UNION
                        SELECT     [Relations-Groups-Items].ItemID 
                        FROM         Items INNER JOIN
                                              [Relations-Groups-Items] ON Items.ItemID = [Relations-Groups-Items].ItemID
                        WHERE     ([Relations-Groups-Items].Direction = 'A') AND ([Relations-Groups-Items].GroupID IN
                                                  (SELECT     GroupID
                                                    FROM          [Groups-Items]
                                                    WHERE      (ItemID ={product.Id})))";


                        var usedin_Reader = _dbContext.SqlQuery <int>(usedinquery).ToList();
                        if (usedin_Reader.Count > 0)
                        {
                            var usedIn = _productService.GetProductsByIds(usedin_Reader.ToArray());

                            foreach (var item in usedIn)
                            {
                                productG.Description = $"{productG.Description} {item.Name}, ";
                            }
                            productG.Description  = productG.Description.TrimEnd(',');
                            productG.Description += ".";
                        }
                        else
                        {
                            productG.Description += "N/A";
                        }
                    }
                }

                writer.WriteEndElement(); // channel
                writer.WriteEndElement(); // rss
                writer.WriteEndDocument();
            }
        }