/// <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(); } }
public void can_round_price(decimal price, decimal roundedValue) { RoundingHelper.RoundPrice(price).ShouldEqual(roundedValue); }
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); }
/// <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); }
/// <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); }
/// <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); }
/// <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(); } }