1

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. remove adjuster

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.

  • Did you already try modifying the array passed in $event->types? docs.craftcms.com/commerce/v3/… – Matt Stein Jun 10 at 19:54
  • 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 Jun 10 at 20:11
  • 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 Jun 10 at 20:20
  • Err ... because you’re working with the line item and not the order. Which is not the same thing, sorry! – Matt Stein Jun 10 at 20:30
3

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.

| improve this answer | |
  • 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 Jun 12 at 17:22
  • 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 Jun 14 at 6:25
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.

| improve this answer | |
  • 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 Jun 14 at 6:30
  • 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 Jun 14 at 6:32
  • 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 Jun 14 at 6:38
  • @nfourtythree's you're right, and thanks for the patience and help! Under a bit of a deadline so I jumped to conclusions :/ – Ryan Jun 21 at 16:56

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.