private async Task <decimal> GetShippingTaxAsync( SortedDictionary <decimal, decimal> taxRates, TaxTotalRequest taxTotalRequest ) { var shippingTax = decimal.Zero; if (_taxSettings.ShippingIsTaxable) { var(shippingExclTax, _, _) = await _orderTotalCalculationService .GetShoppingCartShippingTotalAsync(taxTotalRequest.ShoppingCart, false); var(shippingInclTax, taxRate, _) = await _orderTotalCalculationService .GetShoppingCartShippingTotalAsync(taxTotalRequest.ShoppingCart, true); if (shippingExclTax.HasValue && shippingInclTax.HasValue) { shippingTax = shippingInclTax.Value - shippingExclTax.Value; if (shippingTax < decimal.Zero) { shippingTax = decimal.Zero; } if (taxRate > decimal.Zero && shippingTax > decimal.Zero) { if (!taxRates.ContainsKey(taxRate)) { taxRates.Add(taxRate, shippingTax); } else { taxRates[taxRate] = taxRates[taxRate] + shippingTax; } } } } return(shippingTax); }
public async Task CanGetShippingTotalWithFixedShippingRateExcludingTax() { var product = await _productService.GetProductBySkuAsync("FR_451_RB"); product.AdditionalShippingCharge = 21.25M; product.IsFreeShipping = false; await _productService.UpdateProductAsync(product); var(shipping, taxRate, appliedDiscounts) = await _orderTotalCalcService.GetShoppingCartShippingTotalAsync(await GetShoppingCartAsync(), false); product.AdditionalShippingCharge = 0M; product.IsFreeShipping = true; await _productService.UpdateProductAsync(product); shipping.Should().NotBeNull(); //10 - default fixed shipping rate, 42.5 - additional shipping change shipping.Should().Be(52.5M); appliedDiscounts.Count.Should().Be(0); //10 - default fixed tax rate taxRate.Should().Be(10); }
/// <summary> /// Gets tax total /// </summary> /// <param name="taxTotalRequest">Tax total request</param> /// <returns>Tax total</returns> public async Task <TaxTotalResult> GetTaxTotalAsync(TaxTotalRequest taxTotalRequest) { var taxRates = new SortedDictionary <decimal, decimal>(); var cart = taxTotalRequest.ShoppingCart; var customer = taxTotalRequest.Customer; var storeId = taxTotalRequest.StoreId; var usePaymentMethodAdditionalFee = taxTotalRequest.UsePaymentMethodAdditionalFee; var paymentMethodSystemName = string.Empty; if (customer != null) { paymentMethodSystemName = await _genericAttributeService.GetAttributeAsync <string>(customer, NopCustomerDefaults.SelectedPaymentMethodAttribute, storeId); } //order sub total (items + checkout attributes) var subTotalTaxTotal = decimal.Zero; var(_, _, _, _, orderSubTotalTaxRates) = await _orderTotalCalculationService.GetShoppingCartSubTotalAsync(cart, false); foreach (var kvp in orderSubTotalTaxRates) { var taxRate = kvp.Key; var taxValue = kvp.Value; subTotalTaxTotal += taxValue; if (taxRate <= decimal.Zero || taxValue <= decimal.Zero) { continue; } if (!taxRates.ContainsKey(taxRate)) { taxRates.Add(taxRate, taxValue); } else { taxRates[taxRate] = taxRates[taxRate] + taxValue; } } //shipping var shippingTax = decimal.Zero; if (_taxSettings.ShippingIsTaxable) { var(shippingExclTax, _, _) = await _orderTotalCalculationService.GetShoppingCartShippingTotalAsync(cart, false); var(shippingInclTax, taxRate, _) = await _orderTotalCalculationService.GetShoppingCartShippingTotalAsync(cart, true); 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 var paymentMethodAdditionalFeeTax = decimal.Zero; if (usePaymentMethodAdditionalFee && _taxSettings.PaymentMethodAdditionalFeeIsTaxable) { var paymentMethodAdditionalFee = await _paymentService.GetAdditionalHandlingFeeAsync(cart, paymentMethodSystemName); var(paymentMethodAdditionalFeeExclTax, _) = await _taxService.GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, false, customer); var(paymentMethodAdditionalFeeInclTax, taxRate) = await _taxService.GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, true, customer); 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 var taxTotal = subTotalTaxTotal + shippingTax + paymentMethodAdditionalFeeTax; //ensure that tax is equal or greater than zero if (taxTotal < decimal.Zero) { taxTotal = decimal.Zero; } return(new TaxTotalResult { TaxTotal = taxTotal, TaxRates = taxRates }); }
/// <summary> /// Handle shopping cart changed event /// </summary> /// <param name="cartItem">Shopping cart item</param> /// <returns>A task that represents the asynchronous operation</returns> public async Task HandleShoppingCartChangedEventAsync(ShoppingCartItem cartItem) { //whether marketing automation is enabled if (!_sendinblueSettings.UseMarketingAutomation) { return; } var customer = await _customerService.GetCustomerByIdAsync(cartItem.CustomerId); try { //create API client var client = CreateMarketingAutomationClient(); //first, try to identify current customer await client.IdentifyAsync(new Identify(customer.Email)); //get shopping cart GUID var shoppingCartGuid = await _genericAttributeService .GetAttributeAsync <Guid?>(customer, SendinblueDefaults.ShoppingCartGuidAttribute); //create track event object var trackEvent = new TrackEvent(customer.Email, string.Empty); //get current customer's shopping cart var store = await _storeContext.GetCurrentStoreAsync(); var cart = await _shoppingCartService .GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id); if (cart.Any()) { //get URL helper var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext); //get shopping cart amounts var(_, cartDiscount, _, cartSubtotal, _) = await _orderTotalCalculationService.GetShoppingCartSubTotalAsync(cart, await _workContext.GetTaxDisplayTypeAsync() == TaxDisplayType.IncludingTax); var cartTax = await _orderTotalCalculationService.GetTaxTotalAsync(cart, false); var cartShipping = await _orderTotalCalculationService.GetShoppingCartShippingTotalAsync(cart); var(cartTotal, _, _, _, _, _) = await _orderTotalCalculationService.GetShoppingCartTotalAsync(cart, false, false); //get products data by shopping cart items var itemsData = await cart.Where(item => item.ProductId != 0).SelectAwait(async item => { var product = await _productService.GetProductByIdAsync(item.ProductId); //try to get product attribute combination var combination = await _productAttributeParser.FindProductAttributeCombinationAsync(product, item.AttributesXml); //get default product picture var picture = await _pictureService.GetProductPictureAsync(product, item.AttributesXml); //get product SEO slug name var seName = await _urlRecordService.GetSeNameAsync(product); //create product data return(new { id = product.Id, name = product.Name, variant_id = combination?.Id ?? product.Id, variant_name = combination?.Sku ?? product.Name, sku = combination?.Sku ?? product.Sku, category = await(await _categoryService.GetProductCategoriesByProductIdAsync(item.ProductId)).AggregateAwaitAsync(",", async(all, pc) => { var res = (await _categoryService.GetCategoryByIdAsync(pc.CategoryId)).Name; res = all == "," ? res : all + ", " + res; return res; }), url = urlHelper.RouteUrl("Product", new { SeName = seName }, _webHelper.GetCurrentRequestProtocol()), image = (await _pictureService.GetPictureUrlAsync(picture)).Url, quantity = item.Quantity, price = (await _shoppingCartService.GetSubTotalAsync(item, true)).subTotal }); }).ToArrayAsync(); //prepare cart data var cartData = new { affiliation = store.Name, subtotal = cartSubtotal, shipping = cartShipping ?? decimal.Zero, total_before_tax = cartSubtotal + cartShipping ?? decimal.Zero, tax = cartTax, discount = cartDiscount, revenue = cartTotal ?? decimal.Zero, url = urlHelper.RouteUrl("ShoppingCart", null, _webHelper.GetCurrentRequestProtocol()), currency = (await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId))?.CurrencyCode, //gift_wrapping = string.Empty, //currently we can't get this value items = itemsData }; //if there is a single item in the cart, so the cart is just created if (cart.Count == 1) { shoppingCartGuid = Guid.NewGuid(); } else { //otherwise cart is updated shoppingCartGuid ??= Guid.NewGuid(); } trackEvent.EventName = SendinblueDefaults.CartUpdatedEventName; trackEvent.EventData = new { id = $"cart:{shoppingCartGuid}", data = cartData }; } else { //there are no items in the cart, so the cart is deleted shoppingCartGuid ??= Guid.NewGuid(); trackEvent.EventName = SendinblueDefaults.CartDeletedEventName; trackEvent.EventData = new { id = $"cart:{shoppingCartGuid}" }; } //track event await client.TrackEventAsync(trackEvent); //update GUID for the current customer's shopping cart await _genericAttributeService.SaveAttributeAsync(customer, SendinblueDefaults.ShoppingCartGuidAttribute, shoppingCartGuid); } catch (Exception exception) { //log full error await _logger.ErrorAsync($"Sendinblue Marketing Automation error: {exception.Message}.", exception, customer); } }
/// <summary> /// Gets tax total /// </summary> /// <param name="taxTotalRequest">Tax total request</param> /// <returns> /// A task that represents the asynchronous operation /// The task result contains the ax total /// </returns> public async Task <TaxTotalResult> GetTaxTotalAsync(TaxTotalRequest taxTotalRequest) { if (_httpContextAccessor.HttpContext.Items.TryGetValue("nop.TaxTotal", out var result) && result is (TaxTotalResult taxTotalResult, decimal paymentTax)) { //short-circuit to avoid circular reference when calculating payment method additional fee during the checkout process if (!taxTotalRequest.UsePaymentMethodAdditionalFee) { return new TaxTotalResult { TaxTotal = taxTotalResult.TaxTotal - paymentTax } } ; return(taxTotalResult); } var taxRates = new SortedDictionary <decimal, decimal>(); var taxTotal = decimal.Zero; //order sub total (items + checkout attributes) var(_, _, _, _, orderSubTotalTaxRates) = await _orderTotalCalculationService .GetShoppingCartSubTotalAsync(taxTotalRequest.ShoppingCart, false); var subTotalTaxTotal = decimal.Zero; foreach (var kvp in orderSubTotalTaxRates) { var taxRate = kvp.Key; var 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; } } } taxTotal += subTotalTaxTotal; //shipping var shippingTax = decimal.Zero; if (_taxSettings.ShippingIsTaxable) { var(shippingExclTax, _, _) = await _orderTotalCalculationService .GetShoppingCartShippingTotalAsync(taxTotalRequest.ShoppingCart, false); var(shippingInclTax, taxRate, _) = await _orderTotalCalculationService .GetShoppingCartShippingTotalAsync(taxTotalRequest.ShoppingCart, true); if (shippingExclTax.HasValue && shippingInclTax.HasValue) { shippingTax = shippingInclTax.Value - shippingExclTax.Value; if (shippingTax < decimal.Zero) { shippingTax = decimal.Zero; } if (taxRate > decimal.Zero && shippingTax > decimal.Zero) { if (!taxRates.ContainsKey(taxRate)) { taxRates.Add(taxRate, shippingTax); } else { taxRates[taxRate] = taxRates[taxRate] + shippingTax; } } } } taxTotal += shippingTax; //short-circuit to avoid circular reference when calculating payment method additional fee during the checkout process if (!taxTotalRequest.UsePaymentMethodAdditionalFee) { return new TaxTotalResult { TaxTotal = taxTotal } } ; //payment method additional fee var paymentMethodAdditionalFeeTax = decimal.Zero; if (_taxSettings.PaymentMethodAdditionalFeeIsTaxable) { var paymentMethodSystemName = taxTotalRequest.Customer != null ? await _genericAttributeService .GetAttributeAsync <string>(taxTotalRequest.Customer, NopCustomerDefaults.SelectedPaymentMethodAttribute, taxTotalRequest.StoreId) : string.Empty; var paymentMethodAdditionalFee = await _paymentService .GetAdditionalHandlingFeeAsync(taxTotalRequest.ShoppingCart, paymentMethodSystemName); var(paymentMethodAdditionalFeeExclTax, _) = await _taxService .GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, false, taxTotalRequest.Customer); var(paymentMethodAdditionalFeeInclTax, taxRate) = await _taxService .GetPaymentMethodAdditionalFeeAsync(paymentMethodAdditionalFee, true, taxTotalRequest.Customer); paymentMethodAdditionalFeeTax = paymentMethodAdditionalFeeInclTax - paymentMethodAdditionalFeeExclTax; if (paymentMethodAdditionalFeeTax < decimal.Zero) { paymentMethodAdditionalFeeTax = decimal.Zero; } if (taxRate > decimal.Zero && paymentMethodAdditionalFeeTax > decimal.Zero) { if (!taxRates.ContainsKey(taxRate)) { taxRates.Add(taxRate, paymentMethodAdditionalFeeTax); } else { taxRates[taxRate] = taxRates[taxRate] + paymentMethodAdditionalFeeTax; } } } taxTotal += paymentMethodAdditionalFeeTax; //add at least one tax rate (0%) if (!taxRates.Any()) { taxRates.Add(decimal.Zero, decimal.Zero); } if (taxTotal < decimal.Zero) { taxTotal = decimal.Zero; } taxTotalResult = new TaxTotalResult { TaxTotal = taxTotal, TaxRates = taxRates, }; //store values within the scope of the request to avoid duplicate calculations _httpContextAccessor.HttpContext.Items.TryAdd("nop.TaxTotal", (taxTotalResult, paymentMethodAdditionalFeeTax)); return(taxTotalResult); }