Answers.Body: <p>Generally speaking, no. Adjusters should always be computed from <em>the current state of the order</em>—the information required to match an Adjuster should be attached directly to an order.</p>
  <p>Your custom Adjuster is registered globally, then applied to Orders that match criteria you've defined in its <code>adjust()</code> method. That method returns an array of Adjustment models, which are then attached to the order.</p>
  <p>In this case, what I'd recommend doing is something like this, in your controller:</p>
  <ol>
  <li>Looks up the Entry based on a URL param (<code>?code=abc</code>);</li>
  <li>Load the current user's Cart;</li>
  <li>Set that Entry on the Order via a custom Entry field: <code>$order-&gt;setFieldValue('myDiscountEntryField', [$discountEntry]);</code> (noting that the <code>value</code> is an array, due to the way Relational data is saved!)</li>
  <li>Save the Cart/Order: <code>Craft::$app-&gt;getElements()-&gt;saveElement($order);</code></li>
  </ol>
  <p>At this point, Commerce automatically offers each registered <code>Adjuster</code> an opportunity to apply adjustments to the Order.</p>
  <p>In your Adjuster, use the attached Discount Entry data to return an Adjustment:</p>
  <pre class="lang-php prettyprint-override"><code>class MyAdjuster implements AdjusterInterface
  {
    public function adjust(craft\commerce\elements\Order $order): array
    {
      $discountEntry = $order-&gt;myDiscountEntryField-&gt;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 &quot;no adjustments applied.&quot;
        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-&gt;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;
    }
  }
  </code></pre>
  <blockquote>
  <p>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!</p>
  </blockquote>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-11-20T19:38:16.370Z
Answers.LastActivityDate: 2020-11-20T23:48:27.717Z
Answers.LastEditDate: 2020-11-20T23:48:27.717Z
Answers.UserDisplayName: August Miller
Body: <p>I have a controller which when called (<code>actions/some-module/discount-link/apply?code=abc</code>) associates <code>abc</code> with an entry and based on some stuff in that entry populates the cart with some specific products. This works well.</p>
  <p>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.</p>
  <p>The last part of of my controller:</p>
  <pre><code>foreach ($cart-&gt;getLineItems() as $item) {
      $adjustment = new OrderAdjustment;
      $adjustment-&gt;type = 'discount';
      $adjustment-&gt;name = '$2 off';
      $adjustment-&gt;description = '$2 off everything in the store';
      $adjustment-&gt;sourceSnapshot = ['data' =&gt; 'value'];
      $adjustment-&gt;amount = -2;
      $adjustment-&gt;setOrder(Order::findOne($cart));
      $adjustment-&gt;setLineItem($item);
  
      plugin::getInstance()-&gt;getOrderAdjustments()-&gt;saveOrderAdjustment($adjustment, true);
  }
  
  Craft::$app-&gt;elements-&gt;saveElement($cart);
  </code></pre>
  <p>Is the entire thing wrong? Or am I not saving the adjustments to the cart correctly?</p>
  
Tags: <plugin-development><plugin-craftcommerce>
Title: Can I apply adjustments (discounts) through a controller?
ThreadText: # Can I apply adjustments (discounts) through a controller?
  
  (Asked by: username_0 on 2020-11-20)
  
  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?
  
  **Tags:** plugin-development, plugin-craftcommerce
  
  
  ---
  
  ## 1 answers
  
  ### Answer by username_1 on 2020-11-20. Score: 3 | Accepted answer
  
  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:
  
  ```php
  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!
  
  
  ---
  
  