Default Magento installation has this great feature called “Promotions”. There are two types of promotions: Catalog Price Rules, Shopping Cart Price Rules. In this article I will show you how to programmatically create, apply and delete Shopping Cart Price Rule.
There are several reasons why you might do this, for example: You want to simulate some sort of “rewards” functionality towards customers like applying a discount to them on certain actions. Unless you are using Magento EE (which actually comes with Reward Points functionality), then you need to code it or purchase some of the 3rd party extensions. I personally dislike using 3rd party extensions unless really necessary and unless they cover at least 90-95% or desired functionality. Otherwise you will waste your time on coding around the extension you just purchased or obtained for free. Lets see how we can do this functionality on our own. One way of coding it would be to create and apply Shopping Cart Price Rules programmatically in certain cases, then delete the Shopping Cart Price Rule once it is used.
First we start with the code for creating Shopping Cart Price Rule. We will start by first creating a rule manually then studying the backend process in order to replicate it programmatically. We will go into the Magento admin, under “Promotions > Shopping Cart Price Rules” and clicking on the “Add New Rule” button in the upper right corner. In this example we want to create a rule that will be 1 usage per coupon + 1 usage per customer, this will limit the rule ot be used only by one customer, one time. Once we fill in all of the required fields and submit the data this is what gets submitted via POST to the url like http://shop.net/index.php/admin/promo_quote/save/
form_key => YYhUwcLXX6sezYPY
product_ids =>
name => CUSTOMER_1 - 30% Summer discount
description =>
is_active => 1
website_ids[] => 1
customer_group_ids[] => 3
coupon_type => 2
coupon_code => leIYc4g1dbCCOlT1
uses_per_coupon => 1
uses_per_customer => 1
from_date =>
to_date =>
sort_order =>
is_rss => 1
rule[conditions][1][type] => salesrule/rule_condition_combine
rule[conditions][1][aggregator] => all
rule[conditions][1][value] => 1
rule[conditions][1][new_child] =>
simple_action => by_percent
discount_amount => 30
discount_qty => 0
discount_step =>
apply_to_shipping => 0
simple_free_shipping => 0
stop_rules_processing => 0
rule[actions][1][type] => salesrule/rule_condition_product_combine
rule[actions][1][aggregator] => all
rule[actions][1][value] => 1
rule[actions][1][new_child] =>
store_labels[0] =>
store_labels[1] =>
Now if we study the code behind the http://shop.net/index.php/admin/promo_quote/save/ which is actualy the Mage_Adminhtml_Promo_QuoteController -> saveAction() we can see how rule is created. It basically comes down to the:
$data = array(
'product_ids' => null,
'name' => sprintf('AUTO_GENERATION CUSTOMER_%s - 30%% Summer discount', Mage::getSingleton('customer/session')->getCustomerId()),
'description' => null,
'is_active' => 1,
'website_ids' => array(1),
'customer_group_ids' => array(1),
'coupon_type' => 2,
'coupon_code' => Mage::helper('core')->getRandomString(16),
'uses_per_coupon' => 1,
'uses_per_customer' => 1,
'from_date' => null,
'to_date' => null,
'sort_order' => null,
'is_rss' => 1,
'rule' => array(
'conditions' => array(
array(
'type' => 'salesrule/rule_condition_combine',
'aggregator' => 'all',
'value' => 1,
'new_child' => null
)
)
),
'simple_action' => 'by_percent',
'discount_amount' => 30,
'discount_qty' => 0,
'discount_step' => null,
'apply_to_shipping' => 0,
'simple_free_shipping' => 0,
'stop_rules_processing' => 0,
'rule' => array(
'actions' => array(
array(
'type' => 'salesrule/rule_condition_product_combine',
'aggregator' => 'all',
'value' => 1,
'new_child' => null
)
)
),
'store_labels' => array('30% Summer discount')
);
$model = Mage::getModel('salesrule/rule');
$data = $this->_filterDates($data, array('from_date', 'to_date'));
$validateResult = $model->validateData(new Varien_Object($data));
if ($validateResult == true) {
if (isset($data['simple_action']) && $data['simple_action'] == 'by_percent'
&& isset($data['discount_amount'])) {
$data['discount_amount'] = min(100, $data['discount_amount']);
}
if (isset($data['rule']['conditions'])) {
$data['conditions'] = $data['rule']['conditions'];
}
if (isset($data['rule']['actions'])) {
$data['actions'] = $data['rule']['actions'];
}
unset($data['rule']);
$model->loadPost($data);
$model->save();
}
The above covers the “create” part, now let’s move on to the “apply”. The idea is that we apply the coupon programmatically before the customer executes the checkout. If we where the customer and go under the page like http://shop.net/checkout/cart/ to apply the coupon, then the form would submit the data to the http://shop.net/checkout/cart/couponPost/ controller action.
Submited data is really simple:
remove => 0
coupon_code => da4K53cGuhNoTc10
Now let’s take a look at code behind http://shop.net/checkout/cart/couponPost/ controller action. However, we will go one step further, we will first lookup our previously created rule by a specific name pattern then apply that rule. Once you filter out the “extra” data check, etc. you are left with something like (the code that does the “apply Shopping Cart Price Rule”):
/* START This part is my extra, just to load our coupon for this specific customer */
$model = Mage::getModel('salesrule/rule')
->getCollection()
->addFieldToFilter('name', array('eq'=>sprintf('AUTO_GENERATION CUSTOMER_%s - 30%% Summer discount', Mage::getSingleton('customer/session')->getCustomerId())))
->getFirstItem();
$couponCode = $model->getCouponCode();
/* END This part is my extra, just to load our coupon for this specific customer */
Mage::getSingleton('checkout/cart')
->getQuote()
->getShippingAddress()
->setCollectShippingRates(true);
Mage::getSingleton('checkout/cart')
->getQuote()
->setCouponCode(strlen($couponCode) ? $couponCode : '')
->collectTotals()
->save();
Now all we need to do is to move this code into appropriate place, like event observer. For example imagine we sell some sort of downloadable or virtual products like MS Office, and we would like to give 60% discount for updates when the new version of product is out while remain full price for full product purchase. We could do this by presenting the user with the “update” link under his My Account. Update link next to appropriate product under My Account would then simply take the user to the appropriate product on the shop.
For example, update link could look like this http://shop.net/htc-touch-diamond.html?action=update. Then based on the “action=update” GET param we would simply show the user a message “You already have the older version of this product purchased. We will give you 60% discount on this product if you purchase it, as we consider it to be an update.”. I’m simplifying things here, but hopefully you get my point. Then if the user decides to add the product to the cart, we simple programmatically apply the 60% discount rule on that specific product id/sku (previously checking if user really purchased “that product” before, or similar one that would apply to be considered as update).
And finally once the customer checkout is done, we can delete the rule with a simple code snippet like:
$model = Mage::getModel('salesrule/rule')
->getCollection()
->addFieldToFilter('name', array('eq'=>sprintf('AUTO_GENERATION CUSTOMER_%s - 30%% Summer discount', Mage::getSingleton('customer/session')->getCustomerId())))
->getFirstItem();
$model->delete();
The proper place to place that code snippet would probably be observer for “sales_order_place_after” event. The idea to delete the rule is simply for the cleanup purposes. Since our rule was created such that it can be used only one time no harm can done on possibly reusing it over and over. We can safely delete the rule since all required information is saved on the order so we can later easily know what rule was used on what order under the sales_flat_order.coupon_code and sales_flat_order.coupon_rule_name.
Hope the article was helpful. My idea was to show you how to get around with the Shopping Cart Price Rules, and not so much to give a precise example.
Cheers.