<p>There is a way to do this, obviously, it is worth noting that the code below is written to be easy to understand and could be improved. Also, it is worth noting that there are plenty of things to think about when implementing something like this.</p>

<p>Take careful consideration about how this will affect all parts of the e-commerce experience, whether that is carts, orders, adding to cart in the front end and cart management in the control panel.</p>

<p>The following code will remove a tax adjustment based on the SKU of the line item and the name of the tax adjustment, your criteria may obviously vary.</p>

<pre class="lang-php prettyprint-override"><code>use craft\commerce\elements\Order;
use craft\commerce\Plugin;
use craft\events\ModelEvent;
// ...

Event::on(Order::class,
    Order::EVENT_AFTER_SAVE,
    function(ModelEvent $event) {
        $lineItems = $event-&gt;sender-&gt;getLineItems();
        $adjustments = $event-&gt;sender-&gt;getAdjustments();

        // No need to do anything if this is an order or there aren't any line items or adjustments
        if (empty($lineItems) || empty($adjustments) || $event-&gt;sender-&gt;isCompleted) {
            return;
        }

        // Figure out which line items we want to remove tax from
        $removeTaxFromLineItemIds = [];
        foreach ($lineItems as $lineItem) {
            if ($lineItem-&gt;getSku() == 'MY-EXAMPLE-SKU') {
                $removeTaxFromLineItemIds[] = $lineItem-&gt;id;
            }
        }

        if (empty($removeTaxFromLineItemIds) ) {
            return;
        }

        // Loop through adjustments and remove the tax adjustments for the line items we determined above
        $taxNameToRemove = 'My Tax';
        foreach ($adjustments as $key =&gt; $adjustment) {
            if (in_array($adjustment-&gt;lineItemId, $removeTaxFromLineItemIds) &amp;&amp; $adjustment-&gt;type === Tax::ADJUSTMENT_TYPE &amp;&amp; $adjustment-&gt;name == $taxNameToRemove) {
                unset($adjustments[$key]);
                Plugin::getInstance()-&gt;getOrderAdjustments()-&gt;deleteOrderAdjustmentByAdjustmentId($adjustment-&gt;id);
            }
        }

        $event-&gt;sender-&gt;setAdjustments($adjustments);
    }
);
</code></pre>

<p>This code can be used inside a custom plugin or module.</p>

<p>Hope this helps.</p>

CC BY-SA 4.0
2020-06-12T17:22:56.107Z
Thanks for the great example. I'm curious why this happens in `EVENT_AFTER_SAVE`; when this set the adjustments at the end `$event->sender->setAdjustments($adjustments);` will that not be saved though? It seems like it's being set but not saved.

2nd question: will the line `Plugin::getInstance()->getOrderAdjustments()->deleteOrderAdjustmentByAdjustmentId($adjustment->id);` delete the adjuster permanently or just from the order in question?  It seems like it will remove it completely but I just want to remove it for the order being saved.
Ryan
CC BY-SA 4.0
2020-06-14T06:25:30.817Z
It is done in `EVENT_AFTER_SAVE` to make sure we have given the system the chance to do everything it needs to. We using the `setAdjustments` to make sure they are set on the in memory version of the cart/order so that if something else is using the order they have the correct snapshot. I am then not saving it again to avoid infinite loops. `deleteOrderAdjustmentByAdjustmentId` only deletes that specific adjustment for that specific order and not across the whole system as you mention in your solution.
nfourtythree
CC BY-SA 4.0
2020-06-12T12:30:42.480Z
2020-06-14T06:14:11.077Z
2020-06-14T06:14:11.077Z
nfourtythree
<p>@nfourtythree's answer was correct, but just for a 2nd example, here's what I ended up going with:</p>
<pre><code>use craft\commerce\Plugin;
use craft\commerce\elements\Order;
use craft\commerce\models\OrderAdjustment;
use craft\events\ModelEvent;
use yii\base\Event;

Event::on(
    Order::class,
    Order::EVENT_AFTER_SAVE,
    function(ModelEvent $event) {

        /* @var Order $order */
        $order = $event-&gt;sender;

        // abort if this is a cart because changes to adjustments wouldn't be applied otherwise
        if (! $order-&gt;isCompleted) {
            return;
        }

        // get adjustments and abort if there aren't any
        $adjustments = $order-&gt;getAdjustments();
        if (empty($adjustments)) {
            return;
        }
        
        $changesWereMade = false;

        foreach ($adjustments as $key =&gt; $adjustment) {
            /* @var OrderAdjustment $adjustment */
            if ($someExampleCondition) {
                Plugin::getInstance()-&gt;getOrderAdjustments()-&gt;deleteOrderAdjustmentByAdjustmentId($adjustment-&gt;id);
                unset($adjustments[$key]);
                $changesWereMade = true;
            }
        }

        if ($changesWereMade) {
            $order-&gt;setAdjustments($adjustments);
        }

    });
</code></pre>
<p><code>$someExampleCondition</code> should obviously be changed to your needs. My condition is to filter out taxes that weren't relevant to the country the order was being placed in, since Commerce doesn't yet offer a multi-store solution.</p>

CC BY-SA 4.0
2020-06-14T06:30:37.013Z
**1) The method deleteOrderAdjustmentByAdjustmentId($adjustment->id) would delete a specific tax adjuster from the system completely when I just want it removed from the cart/order.**

This is incorrect, it only deletes the specific adjustment for that specific cart/order not across the whole system
nfourtythree
CC BY-SA 4.0
2020-06-14T06:32:47.733Z
**Carts' adjustments are dynamically pulling in the applicable tax adjusters in the system at that time, whereas an order's adjustments are recorded, so you can actually delete those off the order. Therefore, the method setAdjustments($adjustments) does nothing to a cart's adjustments, but it does actually affect an order's.** This is also not quite true, when a cart is saved its adjustments are also saved in the DB in the `commerce_orderadjustments` table. The only real difference between a cart and and order is `isCompleted`
nfourtythree
CC BY-SA 4.0
2020-06-14T06:38:32.957Z
Your solution will only affect completed orders which isn't advisable. This would mean that someone could not specifically add the tax to a line item that meeting the criteria when editing an order in the control panel. Your solution doesn't solve the initial question as the tax adjuster isn't removed from the cart. Which means if the adjustment is adding a value (e.g. $5) the customer would still have to pay it and then you code would remove that value after that order is saved when it is completed. I would revisit my answer, please ping me on discord if you would like to discuss it
nfourtythree
CC BY-SA 4.0
2020-06-21T16:56:07.840Z
@nfourtythree's you're right, and thanks for the patience and help! Under a bit of a deadline so I jumped to conclusions :/
Ryan
CC BY-SA 4.0
2020-06-13T16:57:09.373Z
2020-06-21T16:55:03.287Z
2020-06-21T16:55:03.287Z
Ryan
<p>I need to remove adjusters from each line item if a certain condition is not met. I see that in the CP you can manually remove them by editing the line item but hunting down this code in Commerce's codebase has proven difficult.
<a href="https://i.sstatic.net/c1kSe.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/c1kSe.png" alt="remove adjuster"></a></p>

<p>Here's some of my code. I'm intending to keep certain adjusters from appearing for all line items before they save. How can I do this?</p>

<pre><code>use yii\base\Event;
use craft\commerce\services\LineItems;
use craft\commerce\events\LineItemEvent;
use craft\commerce\models\OrderAdjustment;
use craft\commerce\models\LineItem;

Event::on(
    LineItems::class,
    LineItems::EVENT_BEFORE_SAVE_LINE_ITEM,
    function(LineItemEvent $event) {

        $lineItem = $event-&gt;lineItem;
        foreach ($lineItem-&gt;getAdjustments() as $adjustment) {
            /* @var OrderAdjustment $adjustment */

            if ($someExampleCondition) {
                // remove adjustment
            }

        }

    });
</code></pre>

<p>But when I inspect an <code>OrderAdjustment</code> model or <code>LineItem</code> model, I don't see any methods about removing or setting adjusters, only getting them.</p>

CC BY-SA 4.0
2020-06-10T19:54:09.960Z
Did you already try modifying the array passed in `$event->types`? https://docs.craftcms.com/commerce/v3/adjusters.html#replace-an-adjuster
Matt Stein
CC BY-SA 4.0
2020-06-10T20:11:30.483Z
I did and when I dd `$event->types` it's not very helpful: 
`[
    0 => 'craft\\commerce\\adjusters\\Shipping'
    1 => 'craft\\commerce\\adjusters\\Discount'
    2 => 'craft\\commerce\\adjusters\\Tax'
]`
I need to loop through all the Tax adjusters and remove the ones that don't apply.
Ryan
CC BY-SA 4.0
2020-06-10T20:20:32.333Z
It’d probably be worth using Xdebug to see what’s in each one, but why not isolate the tax adjuster(s) with `if ($type instanceof craft\commerce\adjusters\Tax) {` and then use your own logic to drop it from the set when it doesn’t apply? Basically the inverse of the example I linked above.
Matt Stein
CC BY-SA 4.0
2020-06-10T20:30:57.807Z
Err ... because you’re working with the line item and not the order. Which is not the same thing, sorry!
Matt Stein
<plugin-development><plugin-craftcommerce><events><adjusters><tax>
Commerce: Remove tax adjuster from line item in plugin
# Commerce: Remove tax adjuster from line item in plugin

(Asked by: username_0 on 2020-06-09)

I need to remove adjusters from each line item if a certain condition is not met. I see that in the CP you can manually remove them by editing the line item but hunting down this code in Commerce's codebase has proven difficult.

Here's some of my code. I'm intending to keep certain adjusters from appearing for all line items before they save. How can I do this?
use yii\base\Event;
use craft\commerce\services\LineItems;
use craft\commerce\events\LineItemEvent;
use craft\commerce\models\OrderAdjustment;
use craft\commerce\models\LineItem;

Event::on(
    LineItems::class,
    LineItems::EVENT_BEFORE_SAVE_LINE_ITEM,
    function(LineItemEvent $event) {

        $lineItem = $event->lineItem;
        foreach ($lineItem->getAdjustments() as $adjustment) {
            /* @var OrderAdjustment $adjustment */

            if ($someExampleCondition) {
                // remove adjustment
            }

        }

    });

But when I inspect an OrderAdjustment model or LineItem model, I don't see any methods about removing or setting adjusters, only getting them.

**Tags:** plugin-development, plugin-craftcommerce, events, adjusters, tax
> **Comments:**
> **username_1**: Did you already try modifying the array passed in `$event->types`? https://docs.craftcms.com/commerce/v3/adjusters.html#replace-an-adjuster  
> **username_0**: I did and when I dd `$event->types` it's not very helpful:  `[     0 => 'craft\\commerce\\adjusters\\Shipping'     1 => 'craft\\commerce\\adjusters\\Discount'     2 => 'craft\\commerce\\adjusters\\Tax' ]` I need to loop through all the Tax adjusters and remove the ones that don't apply.  
> **username_1**: It’d probably be worth using Xdebug to see what’s in each one, but why not isolate the tax adjuster(s) with `if ($type instanceof craft\commerce\adjusters\Tax) {` and then use your own logic to drop it from the set when it doesn’t apply? Basically the inverse of the example I linked above.  
> **username_1**: Err ... because you’re working with the line item and not the order. Which is not the same thing, sorry!  

---

## 2 answers

### Answer by username_2 on 2020-06-12. Score: 3 | Accepted answer

There is a way to do this, obviously, it is worth noting that the code below is written to be easy to understand and could be improved. Also, it is worth noting that there are plenty of things to think about when implementing something like this.
Take careful consideration about how this will affect all parts of the e-commerce experience, whether that is carts, orders, adding to cart in the front end and cart management in the control panel.
The following code will remove a tax adjustment based on the SKU of the line item and the name of the tax adjustment, your criteria may obviously vary.
use craft\commerce\elements\Order;
use craft\commerce\Plugin;
use craft\events\ModelEvent;
// ...

Event::on(Order::class,
    Order::EVENT_AFTER_SAVE,
    function(ModelEvent $event) {
        $lineItems = $event->sender->getLineItems();
        $adjustments = $event->sender->getAdjustments();

        // No need to do anything if this is an order or there aren't any line items or adjustments
        if (empty($lineItems) || empty($adjustments) || $event->sender->isCompleted) {
            return;
        }

        // Figure out which line items we want to remove tax from
        $removeTaxFromLineItemIds = [];
        foreach ($lineItems as $lineItem) {
            if ($lineItem->getSku() == 'MY-EXAMPLE-SKU') {
                $removeTaxFromLineItemIds[] = $lineItem->id;
            }
        }

        if (empty($removeTaxFromLineItemIds) ) {
            return;
        }

        // Loop through adjustments and remove the tax adjustments for the line items we determined above
        $taxNameToRemove = 'My Tax';
        foreach ($adjustments as $key => $adjustment) {
            if (in_array($adjustment->lineItemId, $removeTaxFromLineItemIds) && $adjustment->type === Tax::ADJUSTMENT_TYPE && $adjustment->name == $taxNameToRemove) {
                unset($adjustments[$key]);
                Plugin::getInstance()->getOrderAdjustments()->deleteOrderAdjustmentByAdjustmentId($adjustment->id);
            }
        }

        $event->sender->setAdjustments($adjustments);
    }
);

This code can be used inside a custom plugin or module.
Hope this helps.

> **Comments:**
> **username_0**: Thanks for the great example. I'm curious why this happens in `EVENT_AFTER_SAVE`; when this set the adjustments at the end `$event->sender->setAdjustments($adjustments);` will that not be saved though? It seems like it's being set but not saved.  2nd question: will the line `Plugin::getInstance()->getOrderAdjustments()->deleteOrderAdjustmentByAdjustmentId($adjustment->id);` delete the adjuster permanently or just from the order in question?  It seems like it will remove it completely but I just want to remove it for the order being saved.  
> **username_2**: It is done in `EVENT_AFTER_SAVE` to make sure we have given the system the chance to do everything it needs to. We using the `setAdjustments` to make sure they are set on the in memory version of the cart/order so that if something else is using the order they have the correct snapshot. I am then not saving it again to avoid infinite loops. `deleteOrderAdjustmentByAdjustmentId` only deletes that specific adjustment for that specific order and not across the whole system as you mention in your solution.  

---

### Answer by username_0 on 2020-06-13. Score: 2

@nfourtythree's answer was correct, but just for a 2nd example, here's what I ended up going with:
use craft\commerce\Plugin;
use craft\commerce\elements\Order;
use craft\commerce\models\OrderAdjustment;
use craft\events\ModelEvent;
use yii\base\Event;

Event::on(
    Order::class,
    Order::EVENT_AFTER_SAVE,
    function(ModelEvent $event) {

        /* @var Order $order */
        $order = $event->sender;

        // abort if this is a cart because changes to adjustments wouldn't be applied otherwise
        if (! $order->isCompleted) {
            return;
        }

        // get adjustments and abort if there aren't any
        $adjustments = $order->getAdjustments();
        if (empty($adjustments)) {
            return;
        }
        
        $changesWereMade = false;

        foreach ($adjustments as $key => $adjustment) {
            /* @var OrderAdjustment $adjustment */
            if ($someExampleCondition) {
                Plugin::getInstance()->getOrderAdjustments()->deleteOrderAdjustmentByAdjustmentId($adjustment->id);
                unset($adjustments[$key]);
                $changesWereMade = true;
            }
        }

        if ($changesWereMade) {
            $order->setAdjustments($adjustments);
        }

    });

$someExampleCondition should obviously be changed to your needs. My condition is to filter out taxes that weren't relevant to the country the order was being placed in, since Commerce doesn't yet offer a multi-store solution.

> **Comments:**
> **username_2**: **1) The method deleteOrderAdjustmentByAdjustmentId($adjustment->id) would delete a specific tax adjuster from the system completely when I just want it removed from the cart/order.**  This is incorrect, it only deletes the specific adjustment for that specific cart/order not across the whole system  
> **username_2**: **Carts' adjustments are dynamically pulling in the applicable tax adjusters in the system at that time, whereas an order's adjustments are recorded, so you can actually delete those off the order. Therefore, the method setAdjustments($adjustments) does nothing to a cart's adjustments, but it does actually affect an order's.** This is also not quite true, when a cart is saved its adjustments are also saved in the DB in the `commerce_orderadjustments` table. The only real difference between a cart and and order is `isCompleted`  
> **username_2**: Your solution will only affect completed orders which isn't advisable. This would mean that someone could not specifically add the tax to a line item that meeting the criteria when editing an order in the control panel. Your solution doesn't solve the initial question as the tax adjuster isn't removed from the cart. Which means if the adjustment is adding a value (e.g. $5) the customer would still have to pay it and then you code would remove that value after that order is saved when it is completed. I would revisit my answer, please ping me on discord if you would like to discuss it  
> **username_0**: @nfourtythree's you're right, and thanks for the patience and help! Under a bit of a deadline so I jumped to conclusions :/  

---

