How to apply Magento coupon codes automatically

The Magento price rule system is very complex, and like most things Magento, it can do basically whatever you want it to do once you understand how — but until then, it’s just frustrating. One of the things that it can’t do is apply coupon codes automatically. There are many cases where you might want to do this, the simplest being setting up special pricing for a specific traffic channel.

For instance, if you send feeds to shopping price comparison networks, use this little trick to set up a hidden 5% price difference and you can easily position your products ahead of the competition while staying on-price. At first consideration, it would seem that you should be able to set this up using a catalog price rule rather than a shopping cart price rule, but unfortunately (as far as I’ve been able to tell), you can’t, at least for not-logged-in visitors.

To get Magento to apply coupon codes automatically, you will need to create a controller that looks for a coupon code in your query string, then forwards the visitor on to the requested product or category page. The URL to make this happen will end up looking like this:

http://www.liveoutthere.com/applycoupon/index/?url=buy/the_north_face.html&coupon_code=TNF10

Shouldn’t this be done with an event observer?

No. Personally, I see two good reasons for using a controller and redirecting, rather than just appending a query string variable to the destination page URL and catching it with an event observer: future-proofing, and cache-breaking. On the topic of future-proofing, a controller will allow you to hack in additional functionality more easily when you want to set up complicated discount strategies, it will allow you to do things like check for stock prior to delivering someone to an out-of-stock product page; and in my opinion it is easier to understand than diving right into event observers (although you’ll need one of those too – give me one more paragraph).

As far as cache-breaking, if you’re using a full-page cache like TinyBrick’s LightSpeed, doing things this way will make life easier for you when you have to figure out how to serve special content for discounted products and categories – because the LightSpeed logic happens before everything else in the Magento system, it is *way* easier to deal with these special cases prior to page load than trying to use event observers and complicated cache hole punches. Trust me on this.

Now, before getting to the meat of this how-to, there are many great Magento event observer tutorials, and I suggest you spend some time familiarizing yourself with event observers before proceeding if you haven’t already. They are certainly one of the most powerful aspects of Magento.

Creating a Magento extension to apply coupon codes automatically

The first thing you need is an extension in your /app/code/local directory – I’ve named mine ApplyCoupon. The purpose of this post is not to explain Magento extension development, so I’m just going to give you a few GitHub gists and a screenshot and leave it at that.

First, you need to save the coupon code in session. Because of the way Magento works, we can’t actually apply a discount to anything unless we have a quote, and we can’t have a quote until we have a product in the cart… so this is a bit of a catch-22, and we will need an event observer to resolve it.

Before you worry about that, the class in your IndexController.php needs to contain this method:

public function indexAction() {
$coupon_code = $this->getRequest()->getParam('coupon_code');
if ($coupon_code != '') {
Mage::getSingleton("checkout/session")->setData("coupon_code",$coupon_code);
Mage::getSingleton('checkout/cart')->getQuote()->setCouponCode($coupon_code)->save();
Mage::getSingleton('core/session')->addSuccess($this->__('Coupon was automatically applied'));
}
else {
Mage::getSingleton("checkout/session")->setData("coupon_code","");
$cart = Mage::getSingleton('checkout/cart');
foreach( Mage::getSingleton('checkout/session')->getQuote()->getItemsCollection() as $item ) {
$cart->removeItem( $item->getId() );
}
$cart->save();
}

if ($this->getRequest()->getParam('url')) {
//using raw header instead of _redirect because _redirect appends a /
header('HTTP/1.1 301 Moved Permanently');
$gclid = this->getRequest()->getParam('gclid');
$url = $this->getRequest()->getParam('url');
header('Location: /' . $url . '?gclid=' . $gclid);
die();
} else {
$this->_redirect("/");
}
}

Note the gclid query string parameter we’re appending to the URL on line 21 – this is really important. If you don’t append it, you will lose your auto-tagged campaign URLs in Google Analytics. Learn from me and don’t make that mistake!

Now, you need an event observer attached to checkout_cart_product_add_after that fires when your shopper adds something to their cart and applies the discount. The class in your Observer.php file will contain one method, applyCouponEvent, to grab the coupon code out of session and apply it to the current quote:

public function applyCouponEvent($observer){
$coupon_code = trim(Mage::getSingleton("checkout/session")->getData("coupon_code"));
if ($coupon_code != ''){
Mage::getSingleton('checkout/cart')->getQuote()->setCouponCode($coupon_code)->save();
}
}

And here are the bits you’ll need in your config.xml:

<frontend>
<events>
<checkout_cart_product_add_after>
<observers>
<ApplyCoupon_apply>
<type>singleton</type>
<class>ApplyCoupon/Observer</class>
<method>applyCouponEvent</method>
</ApplyCoupon_apply>
</observers>
</checkout_cart_product_add_after>
</events>
<routers>
<applycoupon>
<use>standard</use>
<args>
<module>You_ApplyCoupon</module>
<frontName>applycoupon</frontName>
</args>
</applycoupon>
</routers>
</frontend>
view raw config.xml This Gist brought to you by GitHub.

At this point this will all actually work – you will be able to load your controller action, add something to your cart, and see the discount applied. Unfortunately, like most things Magento, this won’t cut it. Your visitors still have no indication that there has been a discount applied until they add a product to cart.

Just a few more steps…

You can add this code to your template/catalog/product/view.phtml and list.phtml template files to get the discounted price as if the discount code had already been applied:

$productPrice = $this->getPriceHtml( $_product, true );
$coupon_code = trim(Mage::getSingleton("checkout/session")->getData("coupon_code"));

if ($coupon_code != '') {
$db = Mage::getSingleton('core/resource')->getConnection('core_read');
$sql = "SELECT discount_amount, conditions_serialized
FROM salesrule_coupon AS a INNER JOIN salesrule AS b ON a.rule_id = b.rule_id
WHERE simple_action = 'by_percent' AND a.code = ?";
$sql = $db->quoteInto($sql, $coupon_code);
$rows = $db->fetchAll($sql);
if (isset($rows[0])) {
$discount = ($rows[0]['discount_amount'] / 100);

/*** This assumes that if there is a shopping cart price rule condition,
* it is a condition with only one parameter - that the product belongs
* to a particular attribute set.
***/
$conditions = unserialize($rows[0]['conditions_serialized']);
$conditions = $conditions['conditions'][0]['conditions'][0];
if (is_array($conditions) && $conditions['value'] == $_product->getAttributeSetId()) {
// someone want to write me a regex for this nasty piece of work?
$discountedPrice = str_replace(' ','',strip_tags($productPrice));
$discountedPrice = str_replace("\n",'',str_replace('$','', $discountedPrice));
$discountedPrice = round($discountedPrice - ($discountedPrice * $discount),2);
$productPrice = "<div class='price-box'><span class='regular-price'>
<span class='price'>$" . $discountedPrice . "</span>
</span></div>";
$sale_price = true;
}
}
}
view raw product.phtml This Gist brought to you by GitHub.

Use the $sale_price flag to turn on sale badges, red-tag creative, etc., and echo $productPrice where you would normally. There are probably a few purists out there who would object to this approach, and I’d love to hear from you if you can think of a better way to do this. This was the best I could come up with.

The condition check on line 15 will allow you to display the discounted price, only if the product in question belongs to the attribute set specified by the condition in the shopping cart price rule — even though the coupon code will not apply if you have these conditions set up, you still need the template to be aware of the conditions so that you display the correct pricing. That’s a mouthful!

In LiveOutThere.com’s case, we add all of our The North Face products to their own attribute set, so we can show 10% discounts on just The North Face products when you come through our paid search campaign.

If you are using a full-page cache, you will also need to create a hole punch for the value of $productPrice, but that’s a topic for another post.

That’s it from me. Questions?

If there is enough interest, I would be happy to write another post about how to approach even more complex shopping cart price rules that involve giveaways and gift cards – a great example of something we’ve done at LiveOutThere.com is run a promotion where we gave our shoppers a free $75 gift card (we use Unirgy’s uGiftCert) if they spent $250 or more. This was a big hit with our customers and is a great promotional strategy that you should be using too.

About Drew

I do a few different things well enough to be dangerous, and since 2001, I have applied my expertise to clients in the advertising, retail, philanthropic, travel/tourism, real estate, and healthcare industries.

Leave a Reply

*