1

I have a controller which when called (actions/some-module/discount-link/apply?code=abc) associates abc with an entry and based on some stuff in that entry populates the cart with some specific products. This works well.

What I want to do next is apply a discount to each line item that the cart is populated with. Setting up an adjuster as per the docs was no problem, but when I'm trying to add adjustments to the cart from within my controller I can't figure it out.

The last part of of my controller:

foreach ($cart->getLineItems() as $item) {
    $adjustment = new OrderAdjustment;
    $adjustment->type = 'discount';
    $adjustment->name = '$2 off';
    $adjustment->description = '$2 off everything in the store';
    $adjustment->sourceSnapshot = ['data' => 'value'];
    $adjustment->amount = -2;
    $adjustment->setOrder(Order::findOne($cart));
    $adjustment->setLineItem($item);

    plugin::getInstance()->getOrderAdjustments()->saveOrderAdjustment($adjustment, true);
}

Craft::$app->elements->saveElement($cart);

Is the entire thing wrong? Or am I not saving the adjustments to the cart correctly?

3

Generally speaking, no. Adjusters should always be computed from the current state of the order—the information required to match an Adjuster should be attached directly to an order.

Your custom Adjuster is registered globally, then applied to Orders that match criteria you've defined in its adjust() method. That method returns an array of Adjustment models, which are then attached to the order.

In this case, what I'd recommend doing is something like this, in your controller:

  1. Looks up the Entry based on a URL param (?code=abc);
  2. Load the current user's Cart;
  3. Set that Entry on the Order via a custom Entry field: $order->setFieldValue('myDiscountEntryField', [$discountEntry]); (noting that the value is an array, due to the way Relational data is saved!)
  4. Save the Cart/Order: Craft::$app->getElements()->saveElement($order);

At this point, Commerce automatically offers each registered Adjuster an opportunity to apply adjustments to the Order.

In your Adjuster, use the attached Discount Entry data to return an Adjustment:

class MyAdjuster implements AdjusterInterface
{
  public function adjust(craft\commerce\elements\Order $order): array
  {
    $discountEntry = $order->myDiscountEntryField->one();

    // We don't want to assume every order contains valid data, and if we can bail early, let's take the opportunity:
    if (!$discountEntry) {
      // The method signature still requires an array returned, but in this case and empty array is basically saying "no adjustments applied."
      return [];
    }

    // Then here, you'll create and return your Adjustment models (like you have in the original question)!
    $adjustments = [];


    // ... Loop over your items...
    foreach ($order->getLineItems() as $lineItem) {
      // ...and use `$adjustments[] = $adjustment` to push new ones into the array, or just `continue;` if you want to skip one.
    }

    // Return anything that was added:
    return $adjustments;
  }
}

If you apply Adjustments outside of this process (the automatic recalculation step, which happens when saving a Cart), you are not likely to have a consistent experience, because they won't be injected the next time a Cart is saved!

| improve this answer | |

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.