1

I made a module, which should add an amount to an item, based on the chosen option. For example, if user checks "gift wrapped" 5$ gets added, if "Gift wrapped with Card" is chosen, 10$ is added.

This is my code for the module:

 Event::on(
          LineItems::class,
          LineItems::EVENT_POPULATE_LINE_ITEM,
          function(LineItemEvent $event) {
              // @var LineItem $lineItem
               $lineItem = $event->lineItem;

              if(isset($lineItem->options['giftWrapped']) && $lineItem->options['giftWrapped'] == 'yes'){
                $lineItem->price = $lineItem->price + 10;
              }
          }
      );

But the result is this:

Screenshot

The original price is 14.75 and 10 should be added.

What is going wrong here? I tried different Events, next to EVENT_POPULATE_LINE_ITEM, but no luck.

I also tried putting it in a adjuster, but this does not work at all.

In General: what would be best practice? Putting it in a module, or making an adjuster?

<?php

namespace modules\terraverdemodule\adjusters;

use Craft;
use craft\base\Component;
use craft\commerce\base\AdjusterInterface;
use craft\commerce\elements\Order;
use craft\commerce\models\OrderAdjustment;
use craft\commerce\Plugin;
use craft\commerce\Model;
use craft\commerce\services\LineItems;
use craft\commerce\events\LineItemEvent;

use yii\base\Event;

class GiftWrapAdjuster extends Component implements AdjusterInterface
{
     public function adjust(Order $order): array
 {

     $adjustments = [];

     if(isset($lineItem->options['giftWrapped']) && $lineItem->options['giftWrapped'] == 'yes'){
          $lineItem->price = $lineItem->price + 10;
     }

   return $adjustments;
 }
}
1
  • I'm looking for a solution to the same issue. There's a few folks saying this should still work but it doesnt. It seems to be a change in Commerce 3 that changes how the cart total is calculated from what I can tell. I've asked for help on the official discord too. Aug 6 at 16:10
2

Your instinct to try the Adjuster API is spot-on. However, you've missed one critical aspect of how Adjusters work that may help explain why the changes you're making are unstable or invisible.

Modifying the price attribute of a LineItem is not a guarantor of any particular behavior. In fact, the price is often just the Variant's base price, memoized—not really the price that someone will pay (for a quantity of one). So, changing that property (even if you can be sure it'll be saved) may not be reflected in the various calculations that Commerce does on an Order.

To make consistent, persistent alterations to the price of individual LineItems (or an entire Order), you have to go through an Adjuster.

At its heart, and Adjuster is a special class that returns an array of OrderAdjustment models based on some arbitrary, user-defined logic. Usually, that involves looping over the items in an order, or accessing some properties of the Order itself to determine what individual Adjustment(s) is/are applied. You're nearly there!

In your case, an Adjustment has to be returned for every LineItem that has a certain value set in its options. You've already defined the logic for this, so let's adapt your Adjuster class to return OrderAdjustment models instead of just mutating the price directly:

// Your `use` statements will need to stay! Omitting for clarity.

class GiftWrapAdjuster extends Component implements AdjusterInterface
{
    const ADJUSTMENT_TYPE = 'discount';
    const GIFTWRAP_PRICE = 10.00;

    public function adjust(Order $order): array
    {
        // Create an array so we can push new Adjustments into it.
        $adjustments = [];

        if(isset($lineItem->options['giftWrapped']) && $lineItem->options['giftWrapped'] == 'yes'){
            // Ok, so we've identified that the option exists and needs an Adjustment applied to it. Let's stub the model:
            $adjustment = new OrderAdjustment([
                // Don't worry, you can have "positive" discounts, and this handle doesn't show up anywhere in the front-end.
                'type' => self::ADJUSTMENT_TYPE,
                'name' => 'Gift Wrap',
                'description' => 'You’ve requested gift-wrapping for this item.',
                // Set the price, multiplying by the quantity (or not, your choice!)
                'amount' => self::GIFTWRAP_PRICE * $lineItem->qty,
            ]);

            // Make sure the Adjuster knows what Order and LineItem it's supposed to adjust. This also helps calculate stuff in-memory, prior to saving (especially for new LineItems that don't yet have an ID!):
            $adjustment->setOrder($order);
            $adjustment->setLineItem($lineItem);

            // Perhaps most importantly! Push that adjustment into the array, so it gets returned:
            $adjustments[] = $adjustment;
        }

        // Return that array—it may be empty, if nothing matched!
        return $adjustments;
    }
}

Defining stable logic for when adjustments are applied allows the Adjustments themselves to be almost disposable—Commerce can trash them all, and recompute them from the state of the order at any time. Best of all, it has a kind of paper-trail for what changes are happening on the LineItem and Order level, rather than just a single number.

3
  • Thanks a lot! i tried your code, but it does not take any action. I have it inside a module, where i have 2 other adjusters. This should`nt be a problem, should it? Or do i have to make another module, because the event is a EVENT_REGISTER_DISCOUNT_ADJUSTERS, whereas the 2 others adjusters is have the Event: EVENT_REGISTER_ORDER_ADJUSTERS?
    – Henrik
    Aug 7 at 14:02
  • This is my Code inside the module: ``` Event::on( OrderAdjustments::class, OrderAdjustments::EVENT_REGISTER_DISCOUNT_ADJUSTERS, function(RegisterComponentTypesEvent $event) { $event->types[] = adjusters\GiftWrapAdjuster::class; }); ```
    – Henrik
    Aug 7 at 14:04
  • I'm not sure what the difference between these events is. I've only ever used EVENT_REGISTER_ORDER_ADJUSTERS. You can register multiple adjusters in one Event listener by calling $event->types[] = MyAdjuster::class; as many times as necessary—I'd take another look at the official Adjuster docs to make sure your files and events are set up correctly, or take a look at my example adjuster repo for a drop-in test case. Aug 7 at 23:43

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.