Magento Howto: Whole-cart Percentage Discount

Wednesday, January 17th, 2018

Introduction

One of the most common requests we receive on e-commerce projects where Magento 1.9 is being used as a store is to add a percentage discount that works on the entire cart WITH a cap on the overall discount amount. This article will explain how to add this to your Magento installation. The same technique could be used to create any discount type with a cap.

1. Add a column for discount_max to salesrule:

On your Magento database, run the following SQL. This will create the field for maximum discount amount.

ALTER TABLE SalesRule ADD COLUMN discount_max DECIMAL(12,4);

2. Add a new simple_action to Mage_SalesRule_Model_Rule_Model_Abstract (/app/code/core/Mage/SalesRule/Model/Rule.php)

This will allow the system to distinguish your per-cart-percentage rule from other types. Add:

const CART_PERCENT_ACTION = 'cart_percent';

after:

const CART_FIXED_ACTION = 'cart_fixed';

3. Add a case to the switch in Mage_Sales_Model_Quote_Item_Abstract->process() (/app/code/core/Mage/SalesRule/Model/Validator.php)

This is the code that actually processes this rule type for each item.

case Mage_SalesRule_Model_Rule::CART_PERCENT_ACTION:
    if (empty($this->_rulesItemTotals[$rule->getId()])) {
        Mage::throwException(Mage::helper('salesrule')->__('Item totals are not set for rule.'));
    }
    //Get the running cart total discount
    $cartDiscountAmount = $this->_rulesItemTotals[$rule->getId()]['cart_total_discount'];
    //percentage multiplier for maths
    $_rulePct = $rulePercent/100;
    // prevent applying whole cart discount for every shipping order, but only for first order
    if ($quote->getIsMultiShipping()) {
        $usedForAddressId = $this->getCartFixedRuleUsedForAddress($rule->getId());
        if ($usedForAddressId && $usedForAddressId != $address->getId()) {
            break;
        } else {
            $this->setCartFixedRuleUsedForAddress($rule->getId(), $address->getId());
        }
    }
    $cartRules = $address->getCartFixedRules();
    if (!isset($cartRules[$rule->getId()])) {
        $cartRules[$rule->getId()] = $rule->getDiscountAmount();
    }

    //Have we exceeded the per-cart limit?
    if ($cartDiscountAmount < $rule->getDiscountMax()) {
        //If not, what's our maximum remaining
        $maxAllowed = $rule->getDiscountMax()-$cartDiscountAmount;
        //This item's discount is the smaller of the two
        $discountAmount = $quote->getStore()->convertPrice(min($maxAllowed, (($baseItemPrice * $qty)*$_rulePct)));
        $quoteAmount = $quote->getStore()->convertPrice($discountAmount);
        $this->_rulesItemTotals[$rule->getId()]['cart_total_discount']+=$discountAmount;
        $cartRules[$rule->getId()] = $discountAmount;
    }
    $address->setCartFixedRules($cartRules);
    break;

4. Update Mage_Sales_Model_Quote_Address->initTotals for the new simple_action (/app/code/core/Mage/SalesRule/Model/Validator.php)

We need to make sure that running totals are created for this new rule type, so change:

if (Mage_SalesRule_Model_Rule::CART_FIXED_ACTION == $rule->getSimpleAction()

to:

if ((Mage_SalesRule_Model_Rule::CART_FIXED_ACTION == $rule->getSimpleAction()||Mage_SalesRule_Model_Rule::CART_PERCENT_ACTION == $rule->getSimpleAction())

We’ll need to initialize our running total when the rule begins processing, so after:

$validItemsCount = 0;

add:

$cartDiscountTotalForThisRule = 0;

and after:

'items_price' => $ruleTotalItemsPrice,

add

'cart_total_discount' => $cartDiscountTotalForThisRule,

Next, we move on to updates for the management interface.(You did want to be able to edit this, right?)

1. In Mage_Adminhtml_Block_Promo_Quote_Edit_Tab_Actions (/app/code/core/Mage/Adminhtml/Block/Promo/Quote/Edit/Tab/Actions.php)

First, we’ll need to add an option to the Discount Type dropdown on the SalesRule editor, so add:

Mage_SalesRule_Model_Rule::CART_PERCENT_ACTION => Mage::helper('salesrule')->__('Fixed percentage discount for whole cart'),

after:

Mage_SalesRule_Model_Rule::CART_FIXED_ACTION => Mage::helper('salesrule')->__('Fixed amount discount for whole cart'),

2. Add new simple_action to Mage_SalesRule_Model_Rule (already done if working in /code instead of /include)

If you are working in code instead of /include, this is already done. If not, add:

const CART_PERCENT_ACTION = 'cart_percent';

after:

const CART_FIXED_ACTION = 'cart_fixed';

3. Add Discount_Max to Management interface tab in Mage_Adminhtml_Block_Promo_Quote_Edit_Tab_Actions (/app/code/core/Mage/Adminhtml/Block/Promo/Quote/Edit/Tab/Actions.php)

This will need to be added to prepareForm(), which is pretty straightforward. Add this:

$fieldset->addField('discount_max', 'text', array(
        	    'name' => 'discount_max',
	            'label' => Mage::helper('salesrule')->__('Maximum Cart-wide Discount Amount'),
        	));
	        $model->setDiscountMax($model->getDiscountMax()*1);

after:

$model->setDiscountQty($model->getDiscountQty()*1);

And with that, you are done. Flush your cache, add your new SalesRule in the management interface, and enjoy!