예제 #1
0
        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);
        }