The functionality behind Magento shopping cart price rules is within the Mage_SalesRule
module.
Shopping cart price rules can be configured within Magento in the admin under Promotions -> Shopping Cart Price Rules
. These rules differ from the catalog rules as they apply to the quote, whereas catalog rules apply to the catalog information about the product entities.
Magento uses a total model to calculate the discount by shopping cart rules and shows it in the cart total. This discount
total is calculated after the subtotal and shipping and before the grand total, as represented in the Mage_SalesRule
module’s config.xml
file.
<?xml version="1.0"?> <config> <global> <sales> <quote> <totals> <freeshipping> <class>salesrule/quote_freeshipping</class> <after>subtotal</after> <before>tax_subtotal,shipping</before> </freeshipping> <discount> <class>salesrule/quote_discount</class> <after>subtotal,shipping</after> <before>grand_total</before> </discount> </totals> </quote> </sales> </global> </config>
You’ll also notice that there is a freeshipping
total also defined here. This is because within shopping cart price rules you can choose to enable free shipping should a customer match a rule.
The discount total has its own model, as represented by the class
nodes, within Mage_SalesRule/Model/Quote/Discount.php
.
public function collect(Mage_Sales_Model_Quote_Address $address) { parent::collect($address); $quote = $address->getQuote(); $store = Mage::app()->getStore($quote->getStoreId()); $this->_calculator->reset($address); $items = $this->_getAddressItems($address); if (!count($items)) { return $this; } $eventArgs = array( 'website_id' => $store->getWebsiteId(), 'customer_group_id' => $quote->getCustomerGroupId(), 'coupon_code' => $quote->getCouponCode(), ); $this->_calculator->init($store->getWebsiteId(), $quote->getCustomerGroupId(), $quote->getCouponCode()); $this->_calculator->initTotals($items, $address); $address->setDiscountDescription(array()); $items = $this->_calculator->sortItemsByPriority($items); foreach ($items as $item) { if ($item->getNoDiscount()) { $item->setDiscountAmount(0); $item->setBaseDiscountAmount(0); } else { /** * Child item discount we calculate for parent */ if ($item->getParentItemId()) { continue; } $eventArgs['item'] = $item; Mage::dispatchEvent('sales_quote_address_discount_item', $eventArgs); if ($item->getHasChildren() && $item->isChildrenCalculated()) { foreach ($item->getChildren() as $child) { $this->_calculator->process($child); $eventArgs['item'] = $child; Mage::dispatchEvent('sales_quote_address_discount_item', $eventArgs); $this->_aggregateItemDiscount($child); } } else { $this->_calculator->process($item); $this->_aggregateItemDiscount($item); } } } /** * process weee amount */ if (Mage::helper('weee')->isEnabled() && Mage::helper('weee')->isDiscounted($store)) { $this->_calculator->processWeeeAmount($address, $items); } /** * Process shipping amount discount */ $address->setShippingDiscountAmount(0); $address->setBaseShippingDiscountAmount(0); if ($address->getShippingAmount()) { $this->_calculator->processShippingAmount($address); $this->_addAmount(-$address->getShippingDiscountAmount()); $this->_addBaseAmount(-$address->getBaseShippingDiscountAmount()); } $this->_calculator->prepareDescription($address); return $this; }
The process()
method belongs in a Validator.php model file that actually checks the ‘action’ of the shopping cart price rule(s) and applies the discount.
<?php class Mage_SalesRule_Model_Validator extends Mage_Core_Model_Abstract { public function process() { .... foreach ($this->_getRules() as $rule) { .... switch ($rule->getSimpleAction()) { case Mage_SalesRule_Model_Rule::TO_PERCENT_ACTION: $rulePercent = max(0, 100-$rule->getDiscountAmount()); //no break; case Mage_SalesRule_Model_Rule::BY_PERCENT_ACTION: $step = $rule->getDiscountStep(); if ($step) { $qty = floor($qty/$step)*$step; } $_rulePct = $rulePercent/100; $discountAmount = ($qty * $itemPrice - $item->getDiscountAmount()) * $_rulePct; $baseDiscountAmount = ($qty * $baseItemPrice - $item->getBaseDiscountAmount()) * $_rulePct; //get discount for original price $originalDiscountAmount = ($qty * $itemOriginalPrice - $item->getDiscountAmount()) * $_rulePct; $baseOriginalDiscountAmount = ($qty * $baseItemOriginalPrice - $item->getDiscountAmount()) * $_rulePct; if (!$rule->getDiscountQty() || $rule->getDiscountQty()>$qty) { $discountPercent = min(100, $item->getDiscountPercent()+$rulePercent); $item->setDiscountPercent($discountPercent); } break; case Mage_SalesRule_Model_Rule::TO_FIXED_ACTION: $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount()); $discountAmount = $qty * ($itemPrice-$quoteAmount); $baseDiscountAmount = $qty * ($baseItemPrice-$rule->getDiscountAmount()); //get discount for original price $originalDiscountAmount = $qty * ($itemOriginalPrice-$quoteAmount); $baseOriginalDiscountAmount = $qty * ($baseItemOriginalPrice-$rule->getDiscountAmount()); break; case Mage_SalesRule_Model_Rule::BY_FIXED_ACTION: $step = $rule->getDiscountStep(); if ($step) { $qty = floor($qty/$step)*$step; } $quoteAmount = $quote->getStore()->convertPrice($rule->getDiscountAmount()); $discountAmount = $qty * $quoteAmount; $baseDiscountAmount = $qty * $rule->getDiscountAmount(); break; case Mage_SalesRule_Model_Rule::CART_FIXED_ACTION: .... } .... } .... } .... }
The model that is responsible for generating coupon codes is the Mage_SalesRule_Model_Coupon_Codegenerator
model.
<?php class Mage_SalesRule_Model_Coupon_Codegenerator extends Varien_Object implements Mage_SalesRule_Model_Coupon_CodegeneratorInterface { /** * Retrieve generated code * * @return string */ public function generateCode() { $alphabet = ($this->getAlphabet() ? $this->getAlphabet() : 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'); $lengthMin = ($this->getLengthMin() ? $this->getLengthMin() : 16); $lengthMax = ($this->getLengthMax() ? $this->getLengthMax() : 32); $length = ($this->getLength() ? $this->getLength() : rand($lengthMin, $lengthMax)); $result = ''; $indexMax = strlen($alphabet) - 1; for ($i = 0; $i < $length; $i++) { $index = rand(0, $indexMax); $result .= $alphabet{$index}; } return $result; } .... }
When the sales_order_place_after
event is dispatched in the Mage_Sales_Model_Order
class, an observer within the Mage_SalesRule
module increments the times_used
column that tracks the rule’s and customer’s discount usage.
public function sales_order_afterPlace($observer) { $order = $observer->getEvent()->getOrder(); if (!$order) { return $this; } // lookup rule ids $ruleIds = explode(',', $order->getAppliedRuleIds()); $ruleIds = array_unique($ruleIds); $ruleCustomer = null; $customerId = $order->getCustomerId(); // use each rule (and apply to customer, if applicable) if ($order->getDiscountAmount() != 0) { foreach ($ruleIds as $ruleId) { if (!$ruleId) { continue; } $rule = Mage::getModel('salesrule/rule'); $rule->load($ruleId); if ($rule->getId()) { $rule->setTimesUsed($rule->getTimesUsed() + 1); $rule->save(); if ($customerId) { $ruleCustomer = Mage::getModel('salesrule/rule_customer'); $ruleCustomer->loadByCustomerRule($customerId, $ruleId); if ($ruleCustomer->getId()) { $ruleCustomer->setTimesUsed($ruleCustomer->getTimesUsed() + 1); } else { $ruleCustomer ->setCustomerId($customerId) ->setRuleId($ruleId) ->setTimesUsed(1); } $ruleCustomer->save(); } } } $coupon = Mage::getModel('salesrule/coupon'); /** @var Mage_SalesRule_Model_Coupon */ $coupon->load($order->getCouponCode(), 'code'); if ($coupon->getId()) { $coupon->setTimesUsed($coupon->getTimesUsed() + 1); $coupon->save(); if ($customerId) { $couponUsage = Mage::getResourceModel('salesrule/coupon_usage'); $couponUsage->updateCustomerCouponTimesUsed($customerId, $coupon->getId()); } } } }
Key things to remember about shopping cart price rules:
Magento stores the shopping cart price rule data in the following database tables.
salesrule
– Contains generic information about the rule, including serialised conditions and actions datasalesrule_coupon
– Contains information about the coupon codes used for a rulesalesrule_coupon_usage
– Contains information about the usage of coupons when orders are placedsalesrule_customer
– Contains information about the usage of a rule for a customer such as the times_used
columnsalesrule_customer_group
– Contains information about customer groups assigned to a rulesalesrule_label
– Contains information about the label used to name the rulesalesrule_product_attribute
– Contains information such as the attribute ID used in the rule conditionssalesrule_website
– Contains the website ID(s) that the rule is assigned toNote: This article is based on Magento Community/Open Source version 1.9.