Programmatically create, apply and delete Shopping Cart Price Rule in Magento

cart

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.


21 comments

  1. Hi, is there a way that I can add a customer email into the Shopping cart price rule Customer list?

    Thanks

  2. So… as with many blogs… WHERE do we put this code into?

    which file name? all in the same location ,or different?

  3. Hi Branko, I want to implement Custom Coupon Shopping price rule, I am new to magento, how i can implement this, i want to meet condition as,, as customer can buy as many as products he wants, but max discount for cart i can give is 20,000 how i can implement this , and if discount for product collects less than 20000 than it must provide me discount amount as it calculated. ex. if customer incured discount of 15000,it must give 15000 discount. and if it crosses discount of 20000 than it must give 20000 only Please help me,
    Thanks in advance

  4. Hi,
    I find your post very interesting but since I’m just a rookie at coding Magento I don’t really know how to use this to do what I want.
    My task is to make an incentivizing box on checkout page. On that box there should be text saying something like this: “Add £34.25 for a 20% discount!”. Discount applies when total is 200. (I have my rule like this: subtotal equals or greater than 165.2892, since I can’t find total under conditions I use subtotal and have in mind 21% VAT)
    Right now I’m doing this:

    getQuote()->getTotals();
    $grandtotal = $totals[“grand_total”]->getValue();
    $add = 200 – $grandtotal;
    if(isset($totals[‘discount’]) && $totals[‘discount’]->getValue()) {
    echo “Your cart qualified for 20% discout”;
    } else {
    echo “Add $add for a 20% discount!”;
    }
    ?>

    Now sometimes I might want to change percentage or the subtotal condition value. So how do I get these variables in my code? I believe there should be some method similar to what you used.
    $model = Mage::getModel(‘salesrule/rule’)
    ->getCollection()
    ->addFieldToFilter(‘name’, array(‘eq’=>sprintf(‘AUTO_GENERATION CUSTOMER_%s – 30%% Summer discount’, Mage::getSingleton(‘customer/session’)->getCustomerId())))
    ->getFirstItem();

    I would really appreciate your help. Thanx

    Matt

  5. Hi Branko , Greeting.

    Can ypu please guide me , how we can import order using CSV file.

    Kind regard,
    Selim H

  6. When i try to apply coupon

    Mage::getSingleton(‘checkout/cart’)
    ->getQuote()
    ->setCouponCode(strlen($couponCode) ? $couponCode : ”)
    ->collectTotals()
    ->save();

    apply perfectly but print couponcode in my template. i am trying to find why i am not please if you have any idea

  7. I have one question for the when i am custom module quotation to cart then how to apply coupon code on that cart.

  8. Hi,
    You wrote:

    “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/

    Can you explain how I can do this please?
    I am trying to create coupons programatically, and I need to code the specific conditions for the coupon I am creating, so I could really do with knowing how you get to see the post data from submitting the completed form in the admin system.
    I hope you can help.

  9. Can someone please tell me if this will solve the configurable products issue that mage has with the shopping cart rules?

    I need to apply rules to configurable products and am trying to do so by SKU but it only works on the parent sku.

    Any help would be much appreciated!

  10. when i was add condition for category like category is 10 and save rule its give me error message like this An error occurred while saving the rule data. Please review the log and try again.

    can you please how should i solve this issue.

  11. this has been a great help to me. Can you please tell me how to cancel a coupon code forcefully

  12. Where I will see this being really useful to apply it programmatical facilities and encourage repeat business.Thanks for help to Magento Developers.

  13. Hi Branko

    Thanks for the quick reply.
    I’m a very new bee to magento. I can explain the situation.

    Its a single store we configured. no concept of multiple stores.

    To login I used a custom module as customer is using their corresponding id to login.

    Each customer is a retailer who can have multiple stores each store will have individual ID with which customer login’s and places the order.

    example store1 and store2 are two customers but same owner. so when login as store1 and placed some order I will mail him the details. next login as store2 and placed some order then we will mail order information. but both store1 and store2 order details should go to same E-mail.

    So I want to allow the duplicate E-mail for store1 and store2 at the time of registration.

    I’m not sure about this how it effects to the magento.
    Please give me your thoughts on this.

    Thanks & regards
    Dhilip

  14. Really I have no doubt you are a professional ecommerce programmer and programmatically create, apply and delete Shopping Cart Price Rule in Magento. I like your lovely work. Thanks! 🙂

  15. @dhilip

    I dont see how same email can be used by different customers? Please explain, what/why/how??? Is it possible that you are confusing global/website customers? You can have multiple websites where one email can be used on all or just a subset of websites. This would give effect that you have multiple emails in database, since customer would need to create customer account for each website it wants to “use”. Thanks.

  16. Hello Inchoo.net

    Thanks for Nice tutorials in support for Magento Developers.
    Tutorials are excellent to use and improve the skills.

    I have a requirement but no where I found the solution, so I’m posting it here, Please support me…

    My requirement, I want to allow customers with duplicate E-mail Id’s.
    Means same email id can be used by more customers. I’m using a custom module for login with user name.

    Its a very urgent requirement and I’m a very new bee to zend and magento as well, I believe I can expect this article soon.

  17. Lovely work Branko.
    Where I can see this being really useful (CE) is to apply it programmatically to facilitate and encourage repeat business.

    For example, for each order placed where the customer creates an account automatically create a coupon code for X % to be used with their next purchase and send this as a transactional email in addition to the regular store emails, this way the message can be assigned its own content and subject making more likely to be noticed.

    This may be outside the scope of this blog but it won’t hurt to ask – how would we go about implementing this idea taking what you have already done?
    Thanks,
    Jeff

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.