4

I'm curious what the best way to handle quantity discounts would be on a product/variant level. For example:

The base price of a variant is $3 each, but if you buy 10-19 they're $2 each, and if you buy more than 20 they're $1 each.

A different variant of the same product could have a base price of $10 each, but if you buy 20-49 they're $8 each, and if you buy over 50 they're $6 each.

Thoughts?

EDIT 2 (added file paths)

Based on the comments by Luke and Andris below, I've implemented an adjuster to handle this. I created a table field with the handle quantityPricing with 3 columns: minQuantity, maxQuantity, price. I then applied this field to the variants on a product.

This way a user can create as many prices for quantity ranges as they want. I then create the adjuster, checking the line items via the following:

craft/plugins/myplugin/Adjusters/Myplugin_Adjustername.php

<?php

namespace Commerce\Adjusters;
use Craft\Commerce_LineItemModel;
use Craft\Commerce_OrderAdjustmentModel;
use Craft\Commerce_OrderModel;

class Myplugin_Adjustername implements Commerce_AdjusterInterface {
public function adjust(Commerce_OrderModel &$order, array $lineItems = [])
{

    if (empty($lineItems))
    {
        return [];
    }

    $updated  = [];
    $amount   = 0;
    $adjuster = new Commerce_OrderAdjustmentModel();

    $adjuster->type        = 'Discount';
    $adjuster->name        = 'Quantity Pricing';
    $adjuster->description = 'The overall price of the cart has been adjsuted based on the number of items you are purchasing.';
    $adjuster->orderId     = $order->id;

    foreach ($lineItems as $lineItem)
    {
        $purchasable = $lineItem->purchasable;
        if (!empty($purchasable->quantityPricing))
        {
            $updated[]     = $lineItem->id;
            $updatedAmount = 0;
            foreach ($purchasable->quantityPricing as $qp)
            {
                if (
                    ($lineItem->qty >= $qp['minQuantity'] && $lineItem->qty <= $qp['maxQuantity'])
                 ||
                    ($lineItem->qty >= $qp['minQuantity'] && empty($qp['maxQuantity']))
                )
                {
                    $lineItem->discount = ($qp['price'] - $lineItem->price) * $lineItem->qty;
                    $updatedAmount      = $lineItem->discount;
                }
            }
            $amount += $updatedAmount;
        }
    }

    $adjuster->amount      = $amount;
    $adjuster->optionsJson = ['lineItemsAffected' => $updated];

    $adjuster->included = false;

    if (empty($updated))
    {
        return[];
    }

    return [$adjuster];
}
}

I was also seemingly able to do this using onPopulateLineItem but based on the advice below changed it. My code for that looked like this:

    craft()->on('commerce_lineItems.onPopulateLineItem', function($event){

            $purchasable = $event->params['purchasable'];
            $lineItem    = $event->params['lineItem'];

            // This method checks for quantity pricing and updates 
            // line items costs depending on what it finds.
            if (!empty($purchasable->quantityPricing))
            {
                foreach ($purchasable->quantityPricing as $qp)
                {
                    if (
                        ($lineItem->qty >= $qp['minQuantity'] && $lineItem->qty <= $qp['maxQuantity'])
                     ||
                        ($lineItem->qty >= $qp['minQuantity'] && empty($qp['maxQuantity']))
                    )
                    {
                        $lineItem->price = (float) $qp['price'];
                    }
                }
            }
        });

As mentioned in the comments below, I'm not exactly sure why an Adjuster is the better route to go, because both seem to work.

Please don't hesitate to mention if I did something ridiculously stupid somewhere. It certainly wouldn't be the first time.

3
  • Hi peter, that's exactly what I would need too. But I am very new to adjusters and plugins. Could you tell me exactly where to put this code and how the file is named? Of course only if you have the time...
    – outline4
    Commented Mar 3, 2018 at 15:28
  • Hey @outline4 - Sorry for the delay! I updated my example with a full example of the Adjuster file plus the exact path to it from a hypothetical plugin.
    – Peter Tell
    Commented Mar 23, 2018 at 21:08
  • Also, don't forget to register the adjuster: craftcms.stackexchange.com/questions/20327/…
    – Peter Tell
    Commented Mar 23, 2018 at 21:13

1 Answer 1

3

Have you looked into building an adjuster for this purpose? You can find the documentation here

4
  • Hey Andris, thanks for the reply. I've built custom adjusters in the past, so it definitely wouldn't be an issue to get one incorporated. I'm just curious if that is the best way to go or something like using an onPopulateLineItem event. Maybe 6 of one, half dozen of another?
    – Peter Tell
    Commented Sep 11, 2017 at 13:07
  • Definitely recommend your own discount adjuster if you have advanced logic for reducing the price of line items. Make sure the adjuster class you return runs after the built in discount system to ensure it runs before tax and shipping adjusters. Commented Sep 12, 2017 at 2:26
  • Cool thanks Luke (and Andris) -- could you elaborate on why you would recommend the adjuster route vs onPopuplateLineItem?
    – Peter Tell
    Commented Sep 12, 2017 at 14:56
  • @PeterTell onPopulateLineItem could also work fine, but you wouldn't be storing the discount calculation or description in an adjustment in the cart. You also could have other line items containing the same product, and onPopulateLineItem only cares about the qty of a single line item - an adjuster could look at the whole cart and see 2 line items both with the same product in them and add a single adjustment. Commented Sep 13, 2017 at 6:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.