Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

FixedOrByCountryStateZipTaxProvider.cs

using Microsoft.AspNetCore.Http;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Tax;
using Nop.Plugin.Tax.FixedOrByCountryStateZip.Domain;
using Nop.Plugin.Tax.FixedOrByCountryStateZip.Infrastructure.Cache;
using Nop.Plugin.Tax.FixedOrByCountryStateZip.Services;
using Nop.Services.Common;
using Nop.Services.Configuration;
using Nop.Services.Localization;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Plugins;
using Nop.Services.Tax;

namespace Nop.Plugin.Tax.FixedOrByCountryStateZip;

/// 
/// Fixed or by country & state & zip rate tax provider
/// 
public class FixedOrByCountryStateZipTaxProvider : BasePlugin, ITaxProvider
{
    #region Fields

    protected readonly FixedOrByCountryStateZipTaxSettings _countryStateZipSettings;
    protected readonly ICountryStateZipService _taxRateService;
    protected readonly IGenericAttributeService _genericAttributeService;
    protected readonly IHttpContextAccessor _httpContextAccessor;
    protected readonly ILocalizationService _localizationService;
    protected readonly IOrderTotalCalculationService _orderTotalCalculationService;
    protected readonly IPaymentService _paymentService;
    protected readonly ISettingService _settingService;
    protected readonly IStaticCacheManager _staticCacheManager;
    protected readonly ITaxCategoryService _taxCategoryService;
    protected readonly ITaxService _taxService;
    protected readonly IWebHelper _webHelper;
    protected readonly TaxSettings _taxSettings;

    #endregion

    #region Ctor

    public FixedOrByCountryStateZipTaxProvider(FixedOrByCountryStateZipTaxSettings countryStateZipSettings,
        ICountryStateZipService taxRateService,
        IGenericAttributeService genericAttributeService,
        IHttpContextAccessor httpContextAccessor,
        ILocalizationService localizationService,
        IOrderTotalCalculationService orderTotalCalculationService,
        IPaymentService paymentService,
        ISettingService settingService,
        IStaticCacheManager staticCacheManager,
        ITaxCategoryService taxCategoryService,
        ITaxService taxService,
        IWebHelper webHelper,
        TaxSettings taxSettings)
    {
        _countryStateZipSettings = countryStateZipSettings;
        _taxRateService = taxRateService;
        _genericAttributeService = genericAttributeService;
        _httpContextAccessor = httpContextAccessor;
        _localizationService = localizationService;
        _orderTotalCalculationService = orderTotalCalculationService;
        _paymentService = paymentService;
        _settingService = settingService;
        _staticCacheManager = staticCacheManager;
        _taxCategoryService = taxCategoryService;
        _taxService = taxService;
        _webHelper = webHelper;
        _taxSettings = taxSettings;
    }

    #endregion

    #region Methods

    /// 
    /// Gets tax rate
    /// 
    /// Tax rate request
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the ax
    /// 
    public async Task GetTaxRateAsync(TaxRateRequest taxRateRequest)
    {
        var result = new TaxRateResult();

        //the tax rate calculation by fixed rate
        if (!_countryStateZipSettings.CountryStateZipEnabled)
        {
            result.TaxRate = await _settingService.GetSettingByKeyAsync(string.Format(FixedOrByCountryStateZipDefaults.FIXED_RATE_SETTINGS_KEY, taxRateRequest.TaxCategoryId));
            return result;
        }

        //the tax rate calculation by country & state & zip 
        if (taxRateRequest.Address == null)
        {
            result.Errors.Add("Address is not set");
            return result;
        }

        //first, load all tax rate records (cached) - loaded only once
        var cacheKey = _staticCacheManager.PrepareKeyForDefaultCache(ModelCacheEventConsumer.ALL_TAX_RATES_MODEL_KEY);
        var allTaxRates = await _staticCacheManager.GetAsync(cacheKey, async () => (await _taxRateService.GetAllTaxRatesAsync()).Select(taxRate => new TaxRate
        {
            Id = taxRate.Id,
            StoreId = taxRate.StoreId,
            TaxCategoryId = taxRate.TaxCategoryId,
            CountryId = taxRate.CountryId,
            StateProvinceId = taxRate.StateProvinceId,
            Zip = taxRate.Zip,
            Percentage = taxRate.Percentage
        }).ToList());

        var storeId = taxRateRequest.CurrentStoreId;
        var taxCategoryId = taxRateRequest.TaxCategoryId;
        var countryId = taxRateRequest.Address.CountryId;
        var stateProvinceId = taxRateRequest.Address.StateProvinceId;
        var zip = taxRateRequest.Address.ZipPostalCode?.Trim() ?? string.Empty;

        var existingRates = allTaxRates.Where(taxRate => taxRate.CountryId == countryId && taxRate.TaxCategoryId == taxCategoryId);

        //filter by store
        var matchedByStore = existingRates.Where(taxRate => storeId == taxRate.StoreId || taxRate.StoreId == 0);

        //filter by state/province
        var matchedByStateProvince = matchedByStore.Where(taxRate => stateProvinceId == taxRate.StateProvinceId || taxRate.StateProvinceId == 0);

        //filter by zip
        var matchedByZip = matchedByStateProvince.Where(taxRate => string.IsNullOrWhiteSpace(taxRate.Zip) || taxRate.Zip.Equals(zip, StringComparison.InvariantCultureIgnoreCase));

        //sort from particular to general, more particular cases will be the first
        var foundRecords = matchedByZip.OrderBy(r => r.StoreId == 0).ThenBy(r => r.StateProvinceId == 0).ThenBy(r => string.IsNullOrEmpty(r.Zip));

        var foundRecord = foundRecords.FirstOrDefault();

        if (foundRecord != null)
            result.TaxRate = foundRecord.Percentage;

        return result;
    }

    /// 
    /// Gets tax total
    /// 
    /// Tax total request
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the ax total
    /// 
    public async Task 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();
        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.TryGetValue(taxRate, out var value))
                    taxRates.Add(taxRate, taxValue);
                else
                    taxRates[taxRate] = value + 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.TryGetValue(taxRate, out var value))
                        taxRates.Add(taxRate, shippingTax);
                    else
                        taxRates[taxRate] = value + 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(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.TryGetValue(taxRate, out var value))
                    taxRates.Add(taxRate, paymentMethodAdditionalFeeTax);
                else
                    taxRates[taxRate] = value + 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;
    }

    /// 
    /// Gets a configuration page URL
    /// 
    public override string GetConfigurationPageUrl()
    {
        return $"{_webHelper.GetStoreLocation()}Admin/FixedOrByCountryStateZip/Configure";
    }

    /// 
    /// Install plugin
    /// 
    /// A task that represents the asynchronous operation
    public override async Task InstallAsync()
    {
        //settings
        await _settingService.SaveSettingAsync(new FixedOrByCountryStateZipTaxSettings());

        //locales
        await _localizationService.AddOrUpdateLocaleResourceAsync(new Dictionary
        {
            ["Plugins.Tax.FixedOrByCountryStateZip.Fixed"] = "Fixed rate",
            ["Plugins.Tax.FixedOrByCountryStateZip.Tax.Categories.Manage"] = "Manage tax categories",
            ["Plugins.Tax.FixedOrByCountryStateZip.TaxCategoriesCanNotLoaded"] = "No tax categories can be loaded. You may manage tax categories by this link",
            ["Plugins.Tax.FixedOrByCountryStateZip.TaxByCountryStateZip"] = "By Country",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.TaxCategoryName"] = "Tax category",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.Rate"] = "Rate",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.Store"] = "Store",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.Store.Hint"] = "If an asterisk is selected, then this shipping rate will apply to all stores.",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.Country"] = "Country",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.Country.Hint"] = "The country.",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.StateProvince"] = "State / province",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.StateProvince.Hint"] = "If an asterisk is selected, then this tax rate will apply to all customers from the given country, regardless of the state.",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.Zip"] = "Zip",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.Zip.Hint"] = "Zip / postal code. If zip is empty, then this tax rate will apply to all customers from the given country or state, regardless of the zip code.",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.TaxCategory"] = "Tax category",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.TaxCategory.Hint"] = "The tax category.",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.Percentage"] = "Percentage",
            ["Plugins.Tax.FixedOrByCountryStateZip.Fields.Percentage.Hint"] = "The tax rate.",
            ["Plugins.Tax.FixedOrByCountryStateZip.AddRecord"] = "Add tax rate",
            ["Plugins.Tax.FixedOrByCountryStateZip.AddRecordTitle"] = "New tax rate",
            ["Plugins.Tax.FixedOrByCountryStateZip.SwitchRate"] = @"
                    

You are going to change the way the tax rate is calculated. This will cause the tax rate to be calculated based on the settings specified on the configuration page.

Any current tax rate settings will be saved, but will not be active until you return to this tax calculation method.

", }); await base.InstallAsync(); } /// /// Uninstall plugin /// /// A task that represents the asynchronous operation public override async Task UninstallAsync() { //settings await _settingService.DeleteSettingAsync(); //fixed rates var fixedRates = await (await _taxCategoryService.GetAllTaxCategoriesAsync()) .SelectAwait(async taxCategory => await _settingService.GetSettingAsync(string.Format(FixedOrByCountryStateZipDefaults.FIXED_RATE_SETTINGS_KEY, taxCategory.Id))) .Where(setting => setting != null).ToListAsync(); await _settingService.DeleteSettingsAsync(fixedRates); //locales await _localizationService.DeleteLocaleResourcesAsync("Plugins.Tax.FixedOrByCountryStateZip"); await base.UninstallAsync(); } #endregion }