Adding shipping costs to the cart automatically in Magento

Advertisements

I’ve come across a handful of sites now that either do (or want to) automatically add a default shipping charge to the cart when it’s created or updated. As I had a bit of trouble finding a method for this, I thought I’d post my exact method here for usefulness and future reference.

In order to get this working, you need a module which hooks into the event ‘sales_quote_save_before’. We’ll use the namespace BTS.

First, create a module definition file; in app/etc/modules, create the file BTS_Checkout.xml:


	
		
			true
			local
		
	

Now we create our namespace. Create the following directory hierarchy:

app/code/local/BTS
app/code/local/BTS/Checkout
app/code/local/BTS/Checkout/etc
app/code/local/BTS/Checkout/Model

Under “app/code/local/BTS/Checkout/etc”, create a file “config.xml” with the following contents:



	
		
			0.0.1
		
	
	
		
			
				BTS_Checkout_Model
			
		
	
	
		
			
				
					
						singleton
						bts_checkout/observer
						addShipping
					
				
			
		
	

Now under “app/code/local/BTS/Checkout/Model”, create a file “Observer.php” with the following contents:

class BTS_Checkout_Model_Observer {
	
	private $_shippingCode = 'royalmail';
	private $_country = 'GB';
	
	public function addShipping($params = null) {
		if (Mage::registry('checkout_addShipping')) {
			Mage::unregister('checkout_addShipping');
			return;
		}
		Mage::register('checkout_addShipping',true);
		
		$cart = Mage::getSingleton('checkout/cart');
		$quote = $cart->getQuote();
		
		if ($quote->getCouponCode() != '') {
			$c = Mage::getResourceModel('salesrule/rule_collection');
			$c->getSelect()->where("code=?", $quote->getCouponCode());
			foreach ($c->getItems() as $item) { $coupon = $item; }
		
			if ($coupon->getSimpleFreeShipping() > 0) {
				$quote->getShippingAddress()->setShippingMethod($this->_shippingCode)->save();
				return true;
			}
		}

		try {
			$method = $quote->getShippingAddress()->getShippingMethod();
			if ($method) return; // don't overwrite what's already there if we have one set already
			
			if ($quote->getShippingAddress()->getCountryId() == '') {
				$quote->getShippingAddress()->setCountryId($this->_country);
			}
			
			$quote->getShippingAddress()->setCollectShippingRates(true);
			$quote->getShippingAddress()->collectShippingRates();
			
			$rates = $quote->getShippingAddress()->getAllShippingRates();
			$allowed_rates = array();
			foreach ($rates as $rate) {
				array_push($allowed_rates,$rate->getCode());
			}

			if (!in_array($this->_shippingCode,$allowed_rates) && count($allowed_rates) > 0) {
				$shippingCode = $allowed_rates[0];
			}

            if (!empty($shippingCode)) {
                $address = Mage::getSingleton('checkout/session')->getQuote()->getShippingAddress();
                if ($address->getCountryId() == '') $address->setCountryId($this->_country);
                if ($address->getCity() == '') $address->setCity('');
                if ($address->getPostcode() == '') $address->setPostcode('');
                if ($address->getRegionId() == '') $address->setRegionId('');
                if ($address->getRegion() == '') $address->setRegion('');
                $address->setShippingMethod($this->_shippingCode)->setCollectShippingRates(true);
                Mage::getSingleton('checkout/session')->getQuote()->save();
            } else {
                $address = Mage::getSingleton('checkout/session')->getQuote()->getShippingAddress();
                if ($address->getCountryId() == '') $address->setCountryId($this->_country);
                if ($address->getCity() == '') $address->setCity('');
                if ($address->getPostcode() == '') $address->setPostcode('');
                if ($address->getRegionId() == '') $address->setRegionId('');
                if ($address->getRegion() == '') $address->setRegion('');
                $address->setShippingMethod($this->_shippingCode)->setCollectShippingRates(true);
                Mage::getSingleton('checkout/session')->getQuote()->save();
            }

            Mage::getSingleton('checkout/session')->resetCheckout();

		}
		catch (Mage_Core_Exception $e) {
			Mage::getSingleton('checkout/session')->addError($e->getMessage());
		}
		catch (Exception $e) {
			Mage::getSingleton('checkout/session')->addException($e, Mage::helper('checkout')->__('Load customer quote error'));
		}
                
                
	}
	
	public function getQuote() {
        if (empty($this->_quote)) {
            $this->_quote = Mage::getSingleton('checkout/session')->getQuote();
        }
        return $this->_quote;
    }
	
}

Note at the top of this file 2 private variables. Set $_country to the default ISO code for your shipping method, and set $_shippingCode to the default/preferred shipping method’s code internally to Magento (fedex/ups/usps etc..). In this instance it’s ‘royalmail’, as I’m also using Meanbee’s Royal Mail module to auto-calculate shipping based on destination country and total weight of cart.

Various other choices could be (I’ll fill this out as and when I experiment with other methods in this code):

  • flatrate_flatrate: Selected flatrate shipping method

Save all this then add an item to your cart. A shipping method should appear in the cart’s cost breakdown at the bottom with the relevant shipping costs included.

Updated September 2011: I’ve updated this article based on feedback relating to the code not working correctly, or preventing coupons from being applied.

Updated January 2012: Thanks to Toby Lerone for pointing out the error with the XML causing the cart to not update on first load (after the method got added to the cart in the first instance). Changing the event from sales_quote_save_before to checkout_cart_save_before apparently solves that problem (I’ll remove the “apparently” from that statement when I can independently verify it). I’ve updated the code above though to take this into account.

Updated August 2017: Thanks for Sam for confirming that the above still works in Magento 1.9.

Join the Conversation

62 Comments

  1. thanks for this tutorial man! I’ve been looking for this all night, now it’s morning! but it was all worth it!
    Can I contact you regarding Magento and stuff?

    thanks again,
    Cristian

  2. the solution doesnt show me any shipping costs in cart 🙁 i dont know why. can anybody help? how i can find out the shippingCode for tablerates?

  3. It probably doesn’t work because the shipping method code and country code defined at the top of the Observer.php attempt to instantiate a shipping method which fails to pass validation and apply to the cart successfully (i.e; instantiate a shipping method that would otherwise not be available during the checkout process).

    If you simply have table rates, then you’ll need to apply another method to do this (not listed here) to loop through a list of available rates, validate them then apply whichever one passes. It’s a lot more complex operation.

  4. Hi,

    Many thanks for the great post, saved me a lot of trouble, but I have a small problem if you have any ideas what it might be: the delivery cost in the totals block of the cart appears only if you refresh the page, not from the first time when you add a product to the cart.

    Any ideas will be greately appreciated 🙂

    Thank you.

  5. I’ve come across this one myself. The problem is, when I wrote them snippets in this post, I couldn’t replicate it, and it appeared in the cart the first load every time. But I have seen this, and can’t quite understand why. The code hooks into the order model just before it saves, so when the order’s pulled back by the cart, it pulls it back from the database – *after* it’s saved. But for some reason it seems to ignore it, or bizarrely save it after the cart loads. I really can’t work it out.

    I have a question though. When you add a product to your cart, does it take your straight to the shopping cart, or do you have it take you back to where you came from (i.e; the product view page)?

    Dan

  6. Maybe I am just really tired, but this isn’t working for me. I have followed the steps but when I add the item to the cart, instead of seeing the shipping amount in the cart- I see the Observer.php code in the header. I’m on v. 1.5 community

    Any ideas?

    Thanks!

  7. Erm, let me just take this opportunity to exclaim: Wtf?! That just..shouldn’t happen. It suggests some kind of encoding issue perhaps. Are you sure the opening <?php tag at the top of the file is in tact, with no odd characters (or any characters at all) before it? I can't say I've ever come across this before, let alone in Magento. Bad file encoding, a malformed opening php tag, or no tag at all, perhaps. That’s all I can think of!

    Hope this helps.

    Dan

  8. I’m an idiot lol. I had an extra character in my opening tag. That’s what I get for trying to do this on no sleep. Thanks for the tutorial! You sir are awesome 🙂

  9. @Dan,

    Sorry for the delay, no, it takes me straight to the cart page, not back to the product view page. I didn’t figure this one yet, still trying different scenarios maybe I can come up with something useful…

  10. I have implemented this, it works well apart from it seems to have had a knock on effect in preventing coupon codes from being added.

    If I remove this module from the code/local file then a coupon will add fine, but when i add it back in then the coupon constantly returns ‘Cannot apply the coupon code.’

    Anyone got any ideas?

  11. Hi,

    Michael; Use “flatrate_flatrate” in the $_shippingCode variable. I’ve updated the post with this information, and I’ll put other methods that I use and find that work well in the same list.

    Johnboy; I’ve resolved the problem you were having with the coupons. I think the database layouts/structure has changed since I posted this originally. The “coupon_code” in the select statement was causing it to fall over with an “unknown column”. If you swap out the coupon code if statement with the one updated above (or just -repaste the code from above into Observer.php), your coupon woes should vanish. This only broke I think because I got a bit lazy with the coupon checking for free shipping by talking almost directly with the database than via Magento’s API. Maybe I’ll re-write it completely one day, but for now, this works just as well 🙂

    Hope this helps.

    Dan

  12. Why’d you make the if statement “if (!empty($shippingCode)) {“?
    It makes no sense, you just duplicate the code in the if/else statement.

  13. There was a reason for that, in an early iteration of that code. I think it was to preserve the shipping method of the quote if the customer had been through checkout, and gone back to the cart. The else{} part of the statement used to do something else. I guess I changed it at some point (probably before publishing), and never noticed that the code between the 2 conditionals is actually now the same!

    Well spotted 🙂

  14. you’ll need to help out a bit here by telling me what the error message you’re receiving is. otherwise i have no clue how to start helping you.

  15. when i click on add to cart.. itz showing an error page saying “there is some error processing your request”

  16. Work a treat for me. thanks.

    I have another concern besides this, Hope you would help.
    I am new to Magento, and I mostly stuck in finding the right functions to do some operations, the module structures etc. Is there some online resource where I can learn Magento Module Development.
    And another thing I am looking for is Class Reference Guide of magento classes.
    Can you provide me these few things ?

  17. Hi, thank a lot for your code.
    So, what if I am using flat rate?
    I also have Webshopapps Matrixrate but can’t find out what the name code for this is? any clue?

    thanks again for the contribution

  18. Works great. How ever can it add shipping costs to the sidebar cart aswell? And can it also handle “free shipping over XX”?

  19. NOT UPDATING FIRST TIME
    ———————–
    @Dan or anyone else having problems with the cart totals not updating the first time, I have the solution!

    Change the observer from “sales_quote_save_before” to “checkout_cart_save_before”.

    This observer is only called on the cart page.

    If you need it to calculate costings on emails and other sections of the site then use observer “sales_quote_item_collection_products_after_load”.

    Free Shipping
    =============
    @Lars get in touch and I have now also modified to include two delivery options, one of which is free delivery!

    Cheers

  20. Hi! I’m very interested in Lars’ idea too. Specially the shipping cost in sidebar cart and including the free shipping.

    Also would like to know if table-rates is possible to use and if, what code to use. I tried ‘tablerates’ and ‘table_rates’ but doesn’t seem to work…

    thank you!

  21. Hello, I can’t post code in the comment box, but by all means send me your email. Or the site owner contact me to get the code….?

    Cheers

  22. Hi Toby, i’am interested in the 2 delivery options with free delivery to. Could you post how you did it?

    @Simon, the shipping code for table rates is “tablerate_bestway”. Easy way to find out the shipping codes is to inspect the shipping element in the checkout and look for the id, thats the shipping code.
    Tried it myself with table rates, it works.

  23. Hi, nothing happened
    Is there a way to know if the module has been correctly inserted or if it is there? because I went to System/Configuration/Advanced but cannot see the module there. I’ve had some problems with other modules, that I had to put in core files because it wouldn’t work in local…

  24. Sorry, was looking at wrong place. It’s there, but can’t see changes in sidebar cart. Maybe my custom theme (it’s based on modern)?

  25. Hello Simon. You need to ensure that the template/tax/shipping.phtml template is being included in your sidebar template. This outputs the shipping estimate. Cheers

  26. Hello,

    Great help and its almost perfect for me but I have a problem in getting it working with WebShopApps Shipping Matrix rates extension when adding a configurable product to the cart, sometimes it will be added sometimes not. I have changed the id to ‘matrixrate_matrixrate_7’. Simple products are fine it’s just the configurable ones. Has anyone else tried this or can any one offer an alternative solution for me.
    I need to have table rates for: destination > weight with free shipping over a certain amount of UK as well as free collect from store. Unfortunately I can’t seem to get the free shipping to work (with the standard table rates and a promotion code) with the collect from store because as soon as the value of the free shipping is reached you cant select the collection option?
    Thanks

  27. While the module is being shown as active in my backend, nothing is shown in my cart. I copied everything as described but no luck.
    I’m on Magento 1.5.1.0

  28. Ok I think I know what my problem was. I have tablerates configured as my top priority method. Even though I am also using flatrate for some products, entering “flatrate_flatrate” wouldn’t work. I changed it to “tablerate_bestway” and it is working! The only thing I have found out sofar is that, freeshipping will only be displayed after I refresh the cart. I have a cartrule that applies freeshipping when ordering for more than 100€, so I am not using the freeeship method – maybe this is the problem?

  29. hi, i have using shippingTablerate extension, is this code possible to apply? and id possible, what $_shippingCode im going to put this variable.

    Thanks in advance!

  30. Hey,

    why saving the quote inside the observer? We are right before saving the quote anyway, since we are hooking in with “sales_quote_save_before”…

    The additional save will just call the observer again.

    It seems that leaving out the extra save also takes care of the “no shipping cost shown in the cart”-Bug. I had that one, too.

  31. Addition: the code is not using the event “sales_quote_save_before”, but “checkout_cart_save_before”. Anyway, Mage_Checkout_Model_Cart::save() is calling Mage_Sales_Model_Quote::save().

  32. Hi,
    I created this module and it shows “enable” in Configuration -> Advance. But nothing happened to my cart. NO shipping display no Error.
    I am using Magento 1.5. Please somebody help.

    Thanks

  33. Did anyone figure out the first load problem? I tried Lars’ solution to no avail. I’m using Magento v1.69.

    And many thanks to the poster! You saved my week!

  34. Hi my friends, i have a questions for you, i’m working with magento 1.5.1 but my pay module is made it ofr magento 1.4, and when i’m going to pay i lost the values of the cart, total , name product, etc… A friend told me that magento 1.5 work with diferent variales….. My question is, how i can to get total ands product name from the cart in magento 1.5.1?

    thanx!

  35. @Guido;
    It’s entirely possible that your payment module isn’t 1.5 compatible. Magento drastically changed the way the order process works (and order data is stored in the database) in 1.4.2.0. As a result, most payment modules broke, and vendors were working frantically to fix the problems that the significant changes to Magento’s core created. There’s no easy way around this, especially now that you’ve updated, the only way you’ll fix the issue is if you upgrade the payment module’s code to something 1.4.2.0+ compatible.
    Sorry 🙁
    Dan

  36. Friends, i got the answer, the table is sales_flat_order_item, and you can access it with order_id , this id you can get it this way : Mage::getSingleton(‘checkout/session’)->getLastRealOrderId();

    Dan, thank you so much you explain help me a lot..

    Regards, Guido!

  37. Great article and its work like charm.
    YOu save my lot of time to make this.
    Thanks you very much.

Leave a comment

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