0

I am looking for a way to remove VAT from a product when it's added to the cart, for cases where the customer is entitled to VAT relief. Up to now, I have tried the following suggestion mentioned here which uses an observer approach on the sales_quote_collect_totals_before
However, even though I can change things like the Qty, final price etc, the issue is this doesn't seem to remove the VAT for prices set to Including Tax so by the time the order has gone through, it will still show as 20% being applied.

Is there a way around this do you know, so that I can remove the VAT from price which include Tax?

Cheers everyone!

5
  • Have you tried using the customer groups and removing VAT for that group?
    – Chris
    Jul 6 at 10:03
  • Hi @Chris would that not make the customer group a permanent change to the customer in question? I'm only looking to apply the VAT removal within a specific product category you see, not for all products etc.
    – SNAFU
    Jul 6 at 10:29
  • In that case, what about creating a new Tax Class for that product only, then assign regular Retail Customer group to it so they get charged VAT, but do not assign the TaxExempt customer group, so they dont get charged.
    – Chris
    Jul 6 at 10:42
  • @Chris the only problem is, this needs to be done programmatically, i.e. the customer would click the Add to cart button on the PDP page for the specific item. If this product qualifies for VAT relief then customer will fill in a form and provide eligibility details etc. Once the form is submitted, the VAT should then be removed from the item when added to the cart.
    – SNAFU
    Jul 6 at 10:57
  • we do have a module. Its pretty complex to be implemented as you will have to reverse calculate tax amount and then apply deduction. Also its recommended to keep Product price excluded of taxes and taxes added via use of tax class definition and associated rates. Jul 6 at 18:03
0

Ok I figured this one out and posting in case I forget in future, but also to assist with anyone else who was looking for a solution to this problem :) So I basically resolved this using a custom totals collector, as follows:

Step 1

Create app/code/Vendor/Module/etc/sales.xml file and reference a new collector class as follows for example:

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Sales:etc/sales.xsd">
    <section name="quote">
        <group name="totals">
            <item name="tax_subtotal" instance="Vendor\Module\Model\Sales\Total\Quote\Subtotal" sort_order="600"/>
        </group>
    </section>
</config>

Step 2

Create your custom class PHP file app/code/Vendor/Module/Model/Sales/Total/Quote/Subtotal.php with something like the following:

<?php

namespace Vendor\Module\Model\Sales\Total\Quote;

use Magento\Customer\Api\Data\AddressInterfaceFactory as CustomerAddressFactory;
use Magento\Customer\Api\Data\RegionInterfaceFactory as CustomerAddressRegionFactory;
use Magento\Tax\Helper\Data as TaxHelper;
use Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory;
use Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory;
use Magento\Tax\Model\Sales\Total\Quote\Tax;
use Magento\Tax\Model\Config;
use Magento\Tax\Api\TaxCalculationInterface;
use Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory;
use Magento\Framework\Serialize\Serializer\Json;

class Subtotal extends Tax
{
    public function __construct(
        Config $taxConfig,
        TaxCalculationInterface $taxCalculationService,
        QuoteDetailsInterfaceFactory $quoteDetailsDataObjectFactory,
        QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory,
        TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory,
        CustomerAddressFactory $customerAddressFactory,
        CustomerAddressRegionFactory $customerAddressRegionFactory,
        TaxHelper $taxData,
        Json $serializer
    ) {

        parent::__construct(
            $taxConfig,
            $taxCalculationService,
            $quoteDetailsDataObjectFactory,
            $quoteDetailsItemDataObjectFactory,
            $taxClassKeyDataObjectFactory,
            $customerAddressFactory,
            $customerAddressRegionFactory,
            $taxData,
            $serializer
        );
    }

    /**
     * Calculate tax on product items. The result will be used to determine shipping
     * and discount later.
     *
     * @param \Magento\Quote\Model\Quote $quote
     * @param ShippingAssignmentInterface $shippingAssignment
     * @param Address\Total $total
     * @return $this
     */
    public function collect(
        \Magento\Quote\Model\Quote $quote,
        \Magento\Quote\Api\Data\ShippingAssignmentInterface $shippingAssignment,
        \Magento\Quote\Model\Quote\Address\Total $total
    ) {
        // return if no items in quote
        if (!$quote->getItems()) {
            return $this;
        }

        // set some default values
        $subtotal = 0;
        $tax = 0;
        $quoteId = $quote->getId();

        foreach ($quote->getItems() as $quoteItem) {
            $productId = $quoteItem->getProductId();

            // if current item requires VAT relief
              // update row totals for item (remove VAT)
              $this->setRowExclTax($quoteItem);
                
              // add to cart subtotal
              $subtotal += $quoteItem->getBaseRowTotalInclTax();

              // add to cart tax
              $tax += $quoteItem->getBaseTaxAmount();
        }

        // Update the cart totals for subtotal, total Inc/Ex tax etc 
        $this->updateCartTotals($total, $subtotal, $tax);      

        return $this;
    }

   // Sets the item row calculations and removes Tax
    private function setRowExclTax($quoteItem) 
    {
        // calculate the subtotal for each item row
        $rowTotal = ($quoteItem->getPrice() * $quoteItem->getQty());

        // Calculate item row totals
        $quoteItem->setPrice($quoteItem->getPrice());
        $quoteItem->setBasePrice($quoteItem->getBasePrice());
        $quoteItem->setPriceInclTax($quoteItem->getPrice());
        $quoteItem->setBasePriceInclTax($quoteItem->getBasePrice());
        $quoteItem->setRowTotal($rowTotal);
        $quoteItem->setBaseRowTotal($rowTotal);
        $quoteItem->setRowTotalInclTax($rowTotal);
        $quoteItem->setBaseRowTotalInclTax($rowTotal);

        // Remove Tax
        $quoteItem->setTaxAmount(0);
        $quoteItem->setBaseTaxAmount(0);
        $quoteItem->setTaxPercent(0);
    }

    // updates totals such as subtotal, price Incl/Excl Tax etc. 
    private function updateCartTotals($total, $subtotal, $tax)
    {
        $total->setTotalAmount('subtotal', $subtotal);
        $total->setBaseTotalAmount('subtotal', $subtotal);
        $total->setSubtotalInclTax($subtotal);
        $total->setBaseSubtotalInclTax($subtotal);

        // Remove Tax 
        $total->setTotalAmount('tax', $tax);
        $total->setBaseTotalAmount('tax', $tax);

        // Set the grand total Inc Tax total
        $total->setGrandTotal($subtotal);
    }
}

And that was pretty much it :) everything now works as planned when removing VAT from specific items. Hopefully this will help someone else when they need it.

You can see

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.