The Magento_Tax module provides the calculations needed to compute the consumption tax on goods and services.
The Magento_Tax module includes the following:

  • configuration of the tax rates and rules to apply
  • configuration of tax classes that apply to: ** taxation on products ** taxation on shipping charges ** taxation on gift options (example: gift wrapping)
  • specification whether the consumption tax is “sales & use” (typically product prices are loaded without any tax) or “VAT” (typically product prices are loaded including tax)
  • specification of whether the tax total line can be toggled to display the tax details/subtotals
  • display of prices (presented with tax, without tax, or both with and without)
  • display of prices (presented with tax, without tax, or both with and without)

Magento handles taxes as price adjustments and has 3 generic types of taxes:

  • Tax
  • Fixed Product Tax
  • Tax for Fixed Product Tax

Applying and rendering taxes is complicated. A product can have more than one price shown and taxes may or may not apply to all of them.

Tax module Class Magento\Tax\Model\Calculation\AbstractCalculator and that method calculate() is main point where magento check whether price is with tax or without tax. We can put our custom logic/trick within that tax calculation code flow.

Class: Magento\Tax\Model\Calculation\AbstractCalculator

Method: calculate()

public function calculate(QuoteDetailsItemInterface $item, $quantity, $round = true)
{
    if ($item->getIsTaxIncluded()) {
        return $this->calculateWithTaxInPrice($item, $quantity, $round);
    } else {
        return $this->calculateWithTaxNotInPrice($item, $quantity, $round);
    }
}

Summary of code flow (Stack)

  1. Magento\Tax\Model\Calculation\AbstractCalculator::calculate()
  2. Magento\Tax\Api\Data\QuoteDetailsItemInterface::getIsTaxIncluded()
  3. Magento\Tax\Model\Sales\Quote\ItemDetails::getIsTaxIncluded()
  4. Magento\Tax\Model\Sales\Quote\ItemDetails::KEY_IS_TAX_INCLUDED
  5. Magento\Catalog\Helper\Data::getTaxPrice()
  6. Magento\Tax\Model\Config::priceIncludesTax()
  7. Magento\Tax\Model\Config::CONFIG_XML_PATH_PRICE_INCLUDES_TAX

Detail Code Flow

Magento\Tax\Model\Calculation\AbstractCalculator::calculate()

public function calculate(QuoteDetailsItemInterface $item, $quantity, $round = true)
{
    if ($item->getIsTaxIncluded()) {
        return $this->calculateWithTaxInPrice($item, $quantity, $round);
    } else {
        return $this->calculateWithTaxNotInPrice($item, $quantity, $round);
    }
}

Magento\Tax\Api\Data\QuoteDetailsItemInterface::getIsTaxIncluded()

public function getIsTaxIncluded();

Magento\Tax\Model\Sales\Quote\ItemDetails::getIsTaxIncluded()

public function getIsTaxIncluded()
{
    return $this->getData(self::KEY_IS_TAX_INCLUDED);
}

Magento\Tax\Model\Sales\Quote\ItemDetails::KEY_IS_TAX_INCLUDED

const KEY_IS_TAX_INCLUDED      = 'is_tax_included';

Magento\Catalog\Helper\Data::getTaxPrice()

public function getTaxPrice(
    $product,
    $price,
    $includingTax = null,
    $shippingAddress = null,
    $billingAddress = null,
    $ctc = null,
    $store = null,
    $priceIncludesTax = null,
    $roundPrice = true
) {
    if (!$price) {
        return $price;
    }
    $store = $this->_storeManager->getStore($store);
    if ($this->_taxConfig->needPriceConversion($store)) {
        if ($priceIncludesTax === null) {
            $priceIncludesTax = $this->_taxConfig->priceIncludesTax($store);
        }
        $shippingAddressDataObject = null;
        if ($shippingAddress === null) {
            $shippingAddressDataObject =
                $this->convertDefaultTaxAddress($this->_customerSession->getDefaultTaxShippingAddress());
        } elseif ($shippingAddress instanceof \Magento\Customer\Model\Address\AbstractAddress) {
            $shippingAddressDataObject = $shippingAddress->getDataModel();
        }
        $billingAddressDataObject = null;
        if ($billingAddress === null) {
            $billingAddressDataObject =
                $this->convertDefaultTaxAddress($this->_customerSession->getDefaultTaxBillingAddress());
        } elseif ($billingAddress instanceof \Magento\Customer\Model\Address\AbstractAddress) {
            $billingAddressDataObject = $billingAddress->getDataModel();
        }
        $taxClassKey = $this->_taxClassKeyFactory->create();
        $taxClassKey->setType(TaxClassKeyInterface::TYPE_ID)
            ->setValue($product->getTaxClassId());
        if ($ctc === null && $this->_customerSession->getCustomerGroupId() != null) {
            $ctc = $this->customerGroupRepository->getById($this->_customerSession->getCustomerGroupId())
                ->getTaxClassId();
        }
        $customerTaxClassKey = $this->_taxClassKeyFactory->create();
        $customerTaxClassKey->setType(TaxClassKeyInterface::TYPE_ID)
            ->setValue($ctc);
        $item = $this->_quoteDetailsItemFactory->create();
        $item->setQuantity(1)
            ->setCode($product->getSku())
            ->setShortDescription($product->getShortDescription())
            ->setTaxClassKey($taxClassKey)
            ->setIsTaxIncluded($priceIncludesTax)
            ->setType('product')
            ->setUnitPrice($price);
        $quoteDetails = $this->_quoteDetailsFactory->create();
        $quoteDetails->setShippingAddress($shippingAddressDataObject)
            ->setBillingAddress($billingAddressDataObject)
            ->setCustomerTaxClassKey($customerTaxClassKey)
            ->setItems([$item])
            ->setCustomerId($this->_customerSession->getCustomerId());
        $storeId = null;
        if ($store) {
            $storeId = $store->getId();
        }
        $taxDetails = $this->_taxCalculationService->calculateTax($quoteDetails, $storeId, $roundPrice);
        $items = $taxDetails->getItems();
        $taxDetailsItem = array_shift($items);
        if ($includingTax !== null) {
            if ($includingTax) {
                $price = $taxDetailsItem->getPriceInclTax();
            } else {
                $price = $taxDetailsItem->getPrice();
            }
        } else {
            switch ($this->_taxConfig->getPriceDisplayType($store)) {
                case Config::DISPLAY_TYPE_EXCLUDING_TAX:
                case Config::DISPLAY_TYPE_BOTH:
                    $price = $taxDetailsItem->getPrice();
                    break;
                case Config::DISPLAY_TYPE_INCLUDING_TAX:
                    $price = $taxDetailsItem->getPriceInclTax();
                    break;
                default:
                    break;
            }
        }
    }
    if ($roundPrice) {
        return $this->priceCurrency->round($price);
    } else {
        return $price;
    }
}

Magento\Tax\Model\Config::priceIncludesTax()

public function shippingPriceIncludesTax($store = null)
{
    if ($this->_shippingPriceIncludeTax === null) {
        $this->_shippingPriceIncludeTax = (bool)$this->_scopeConfig->getValue(
            self::CONFIG_XML_PATH_SHIPPING_INCLUDES_TAX,
            \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
            $store
        );
    }
    return $this->_shippingPriceIncludeTax;
}

Magento\Tax\Model\Config::CONFIG_XML_PATH_PRICE_INCLUDES_TAX

const CONFIG_XML_PATH_PRICE_INCLUDES_TAX = 'tax/calculation/price_includes_tax';

Magento/Tax/etc/adminhtml/system.xml


    
    This sets whether catalog prices entered from Magento Admin include tax.
    Magento\Tax\Model\Config\Price\IncludePrice
    Magento\Tax\Model\System\Config\Source\PriceType

Another complicated area in tax is Tax Exemption. It’s difficult to manage exemptions for a specific customer. With above knowledge you can do the following custom trick:

  1. Zero tax for some specific product from calculate() method
    ex:

    public function calculate(QuoteDetailsItemInterface $item, $quantity, $round = true)
    { 
    
    //you can apply custom trick with $item for specific product here.
    // do something . . . .
    
        if ($item->getIsTaxIncluded()) {
            return $this->calculateWithTaxInPrice($item, $quantity, $round);
        } else {
            return $this->calculateWithTaxNotInPrice($item, $quantity, $round);
        }
    }
    
  2. Return custom price from getTaxPrice() method
  3. Reduce tax on product based on some customer attribute