Magento Create Custom Shipping Method

shipping-mtd
In this blog post, we will see how to create a custom shipping method in magento.

Creating a custom shipping method is very easy, you just need to know all the useful functions to be used inside the shipping method class to put in various configuration options. We will see in this blog many different use cases and functions to use in shipping methods.
So let’s start of by creating the shipping method. I have used a module for this tutorial named Excellence_Ship. You can create your own module and change class names accordingly.
Attached is source code of the module shown in this blog
Error... Unable to load download template. Search single-download-template.tpl in your plugin folder!
These are the primary areas where the shipping method shows up

  • Admin Magento Admin - Custom Shipping Method
  • Frontend Magento Custom Shipping Method Checkout Page

To achieve this there are 3 things required

  • config.xml entries
  • system.xml entries
  • Shipping Module
  • So lets start of with the steps.

    Step1

    First thing when creating a shipping method is to decide the code for the shipping method. In the current module i am using the code for shipping method as “excellence”. Next we will create the system.xml file for admin entries.

    <?xml version="1.0" encoding="UTF-8"?>
    <config>
    	<sections>
    		<carriers>
    			<groups>
    				<excellence translate="label" module="ship">
    					<label>Excellence Shipping Module</label>
    					<frontend_type>text</frontend_type>
    					<sort_order>99</sort_order>
    					<show_in_default>1</show_in_default>
    					<show_in_website>1</show_in_website>
    					<show_in_store>1</show_in_store>
    					<fields>
    						<active translate="label">
    							<label>Enabled</label>
    							<frontend_type>select</frontend_type>
    							<source_model>adminhtml/system_config_source_yesno</source_model>
    							<sort_order>1</sort_order>
    							<show_in_default>1</show_in_default>
    							<show_in_website>1</show_in_website>
    							<show_in_store>1</show_in_store>
    						</active>
    						<title translate="label">
    							<label>Title</label>
    							<frontend_type>text</frontend_type>
    							<sort_order>2</sort_order>
    							<show_in_default>1</show_in_default>
    							<show_in_website>1</show_in_website>
    							<show_in_store>1</show_in_store>
    						</title>
    						<name translate="label">
    							<label>Method Name</label>
    							<frontend_type>text</frontend_type>
    							<sort_order>2</sort_order>
    							<show_in_default>1</show_in_default>
    							<show_in_website>1</show_in_website>
    							<show_in_store>1</show_in_store>
    						</name>
    						<price translate="label">
    							<label>Price</label>
    							<frontend_type>text</frontend_type>
    							<sort_order>3</sort_order>
    							<show_in_default>1</show_in_default>
    							<show_in_website>1</show_in_website>
    							<show_in_store>0</show_in_store>
    						</price>
    						<specificerrmsg translate="label">
    							<label>Displayed Error Message</label>
    							<frontend_type>textarea</frontend_type>
    							<sort_order>4</sort_order>
    							<show_in_default>1</show_in_default>
    							<show_in_website>1</show_in_website>
    							<show_in_store>1</show_in_store>
    						</specificerrmsg>
    						<sallowspecific translate="label">
                                <label>Ship to Applicable Countries</label>
                                <frontend_type>select</frontend_type>
                                <sort_order>90</sort_order>
                                <frontend_class>shipping-applicable-country</frontend_class>
                                <source_model>adminhtml/system_config_source_shipping_allspecificcountries</source_model>
                                <show_in_default>1</show_in_default>
                                <show_in_website>1</show_in_website>
                                <show_in_store>0</show_in_store>
                            </sallowspecific>
    						<specificcountry translate="label">
                                <label>Ship to Specific Countries</label>
                                <frontend_type>multiselect</frontend_type>
                                <sort_order>91</sort_order>
                                <source_model>adminhtml/system_config_source_country</source_model>
                                <show_in_default>1</show_in_default>
                                <show_in_website>1</show_in_website>
                                <show_in_store>0</show_in_store>
                                <can_be_empty>1</can_be_empty>
                            </specificcountry>
    					</fields>
    				</excellence>
    			</groups>
    		</carriers>
    	</sections>
    </config>
    

    So, let me explain this. first we always have to create our shipping method inside

    <sections>
    		<carriers>
    			<groups>
    

    As you can see the field have been created inside the ‘excellence’ tag which is our shipping method code. Next, we have created fields for active,title,name,price. There fields are required for every shipping method created. To know more details of system.xml entries please refer to this blog
    Next, i have included to more fields sallowspecific and specificcountry. This is required, when you want your shipping method to available for only few countries. To implement this restriction in the frontend (i.e suppose if user chooses India as shipping country our shipping doesnt show) we don’t have to do any more programming. Just included these two fields in the xml and country based restrictions start working automatically. Of course, we have to setup the country restriction in System Configuration in admin.
    Next, i have included specificerrmsg field. This field is used whenever there some error in our shipping method and we to show an error message to the user.

    Step2

    Next in our config.xml file of our module we add the below code directly inside the <config>

    <default>
    		<carriers>
    	      <excellence>
    	           <active>1</active>
    	           <model>ship/carrier_excellence</model>
    	           <title>Carrier Title</title>
    	           <name>Method Name</name>
    	           <price>5.00</price>
    	           <specificerrmsg>This shipping method is currently unavailable. If you would like to ship using this shipping method, please contact us.</specificerrmsg>
    	        </excellence>
    	     </carriers>
           </default>
    

    As we know the <default> tag is used to assign default values to our system.xml fields created. So here we specify default values for active,title,name,price etc. The important field to notice here is the <model> which contains the path of our shipping model. This field is very important, or else the shipping method won’t show on frontend. After this step is over go to Admin -> System -> Configuration -> Shipping Methods and you should see your shipping method there with all the default values pre-filled.

    Magento Admin Custom Shipping Method

    Magento Admin Custom Shipping Method

    Step3

    Now all we need to do is to create our shipping model class. The model class name is Excellence.php and is created Model/Carrier folder.

    <?php
    class Excellence_Ship_Model_Carrier_Excellence extends Mage_Shipping_Model_Carrier_Abstract
    implements Mage_Shipping_Model_Carrier_Interface {
    	protected $_code = 'excellence';
    
    	public function collectRates(Mage_Shipping_Model_Rate_Request $request)
    	{
    		if (!Mage::getStoreConfig('carriers/'.$this->_code.'/active')) {
    			return false;
    		}
    		
    
    		$handling = Mage::getStoreConfig('carriers/'.$this->_code.'/handling');
    		$result = Mage::getModel('shipping/rate_result');
    		$show = true;
    		if($show){ // This if condition is just to demonstrate how to return success and error in shipping methods
    
    			$method = Mage::getModel('shipping/rate_result_method');
    			$method->setCarrier($this->_code);
    			$method->setMethod($this->_code);
    			$method->setCarrierTitle($this->getConfigData('title'));
    			$method->setMethodTitle($this->getConfigData('name'));
    			$method->setPrice($this->getConfigData('price'));
    			$method->setCost($this->getConfigData('price'));
    			$result->append($method);
    
    		}else{
    			$error = Mage::getModel('shipping/rate_result_error');
    			$error->setCarrier($this->_code);
    			$error->setCarrierTitle($this->getConfigData('name'));
    			$error->setErrorMessage($this->getConfigData('specificerrmsg'));
    			$result->append($error);
    		}
    		return $result;
    	}
    	public function getAllowedMethods()
    	{
    		return array('excellence'=>$this->getConfigData('name'));
    	}
    }
    

    The above code is for our shipping model. The code is very basic, let go line by line

    public function collectRates(Mage_Shipping_Model_Rate_Request $request)
    

    This is the function which we need to implement. This function is called by magento for all the shipping methods to find out the shipping rates.

    if (!Mage::getStoreConfig('carriers/'.$this->_code.'/active')) {
      return false;
    }
    

    This line simply checks if the shipping method is enabled from admin.

    $result = Mage::getModel('shipping/rate_result');
    

    here we create the result object, which is always returned from the shipping method collectRate function.

    $method = Mage::getModel('shipping/rate_result_method');
    $method->setCarrier($this->_code);
    $method->setMethod($this->_code);
    $method->setCarrierTitle($this->getConfigData('title'));
    $method->setMethodTitle($this->getConfigData('name'));
    $method->setPrice($this->getConfigData('price'));
    $method->setCost($this->getConfigData('price'));
    $result->append($method);
    

    This code is used to return the shipping price. The code is quite clear.

    $error = Mage::getModel('shipping/rate_result_error');
    $error->setCarrier($this->_code);
    $error->setCarrierTitle($this->getConfigData('name'));
    $error->setErrorMessage($this->getConfigData('specificerrmsg'));
    $result->append($error);
    

    The above code is used to return error message.

    After this model is created, the shipping method should be working properly. It will show up on the checkout page and cart page.

    Different Cases and Function to use inside shipping method

    All the function written below are to be implemented in the collectRates function only. I will write down function you can use to put your condition and calculate price, the price needs to be set in the result variable as shown above.
    Shipping Price is Based On Destination Country,State and Zip Code

    //Case1: Price Depends on Country,State and Pin Code
    		echo $destCountry = $request->getDestCountryId().': Dest Country<br/>';
    		echo $destRegion = $request->getDestRegionId().': Dest Region<br/>';
    		echo $destRegionCode = $request->getDestRegionCode().': Dest Region Code<br/>';
    		print_r($destStreet = $request->getDestStreet()); echo ': Dest Street<br/>';
    		echo $destCity = $request->getDestCity().': Dest City<br/>';
    		echo $destPostcode = $request->getDestPostcode().': Dest Postcode<br/>';
    		echo $country_id = $request->getCountryId().': Package Source Country ID<br/>';
    		echo $region_id = $request->getRegionId().': Package Source Region ID<br/>';
    		echo $city = $request->getCity().': Package Source City<br/>';
    		echo $postcode = $request->getPostcode().': Package Source Post Code<br/>';
    

    Use the above function to put conditions and set price based on your business logic.
    Shipping Price is based on total order cost or weight

    //Case2: Price Depends on Total Order Value or Weight
    		echo $packageValue = $request->getPackageValue().': Dest Package Value<br/>';
    		echo $packageValueDiscout = $request->getPackageValueWithDiscount().': Dest Package Value After Discount<br/>';
    		echo $packageWeight = $request->getPackageWeight().': Package Weight<br/>';
    		echo $packageQty = $request->getPackageQty().': Package Quantity <br/>';
    		echo $packageCurrency = $request->getPackageCurrency().': Package Currency <br/>';
    

    Shipping Price Depends on Dimensions

    //Case3: Price Depends on order dimensions
    		echo $packageheight = $request->getPackageHeight() .': Package height <br/>';
    		echo $request->getPackageWeight().': Package Width <br/>';
    		echo $request->getPackageDepth().': Package Depth <br/>';
    

    Shipping Price Depends on Product Attributes
    As example, suppose we want to have shipping price different for each product. So for this, we create a product attribute called ‘shipping_price’ and on each product edit page in admin we assign the shipping price of the product. Now in our shipping method we need to access this shipping_price again and show the total shipping cost.

    //Case4: Price based on product attribute
    		if ($request->getAllItems()) {
    			foreach ($request->getAllItems() as $item) {
    				if ($item->getProduct()->isVirtual() || $item->getParentItem()) {
    					continue;
    				}
    
    				if ($item->getHasChildren() && $item->isShipSeparately()) {
    					foreach ($item->getChildren() as $child) {
    						if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) {
    							$product_id = $child->getProductId();
    							$productObj = Mage::getModel('catalog/product')->load($product_id);
    							$ship_price = $productObj->getData('shipping_price'); //our shipping attribute code
    							$price += (float)$ship_price;
    						}
    					}
    				} else {
    					$product_id = $item->getProductId();
    					$productObj = Mage::getModel('catalog/product')->load($product_id);
    					$ship_price = $productObj->getData('shipping_price'); //our shipping attribute code
    					$price += (float)$ship_price;
    				}
    			}
    		}
    

    Shipping Price Based on Configurable Product Option Selected
    Example, suppose on our website we have configurable product with Size as the drop down attribute. If user selected selects Size as Small the shipping cost is 15$, Medium is 20$ and Large is 25$.

    //Case5: Shipping option based configurable product option
    		if ($request->getAllItems()) {
    			foreach ($request->getAllItems() as $item) {
    				if ($item->getProduct()->isVirtual() || $item->getParentItem()) {
    					continue;
    				}
    				if ($item->getHasChildren() && $item->isShipSeparately()) {
    					foreach ($item->getChildren() as $child) {
    						if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) {
    							$product_id = $child->getProductId();
    							$value = $item->getOptionByCode('info_buyRequest')->getValue();
    							$params = unserialize($value);
    							$attributeObj = Mage::getModel('eav/config')->getAttribute(Mage_Catalog_Model_Product::ENTITY,'shirt_size'); // our configurable attribute
    							$attribute_id = $attributeObj->getAttributeId();
    							$attribute_selected = $params['super_attribute'][$attribute_id];
    
    							$label = '';
    							foreach($attributeObj->getSource()->getAllOptions(false) as $option){
    								if($option['value'] == $attribute_selected){
    									$label =  $option['label'];
    								}
    							}
    							if($label = 'Small'){
    								$price += 15;
    							} else if($label = 'Medium'){
    								$price += 20;
    							} else if($label = 'Large'){
    								$price += 22;
    							}
    						}
    					}
    				} else {
    					$product_id = $item->getProductId();
    					$value = $item->getOptionByCode('info_buyRequest')->getValue();
    					$params = unserialize($value);
    					$attributeObj = Mage::getModel('eav/config')->getAttribute(Mage_Catalog_Model_Product::ENTITY,'shirt_size'); // our configurable attribute
    					$attribute_id = $attributeObj->getAttributeId();
    					$attribute_selected = $params['super_attribute'][$attribute_id];
    
    					$label = '';
    					foreach($attributeObj->getSource()->getAllOptions(false) as $option){
    						if($option['value'] == $attribute_selected){
    							$label =  $option['label'];
    						}
    					}
    					if($label = 'Small'){
    						$price += 15;
    					} else if($label = 'Medium'){
    						$price += 20;
    					} else if($label = 'Large'){
    						$price += 22;
    					}
    				}
    			}
    		}
    

    Shipping Price based on Product Custom Options
    Example, suppose we have custom drop down options created for a simple product. The Custom Option is Delivery and values are Express,Normal. If user selects Express when adding product, the shipping cost is 50$ and for normal the cost is 10$.

    //Case6: Price based on custom options
    		if ($request->getAllItems()) {
    			foreach ($request->getAllItems() as $item) {
    				if ($item->getProduct()->isVirtual() || $item->getParentItem()) {
    					continue;
    				}
    				if ($item->getHasChildren() && $item->isShipSeparately()) {
    					foreach ($item->getChildren() as $child) {
    						if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) {
    							$product_id = $child->getProductId();
    							$value = $item->getOptionByCode('info_buyRequest')->getValue();
    							$params = unserialize($value);
    							$options_select = $params['options'];
    
    							$product = Mage::getModel('catalog/product')->load($product_id);
    							$options = $product->getOptions();
    							foreach ($options as $option) {
    								if ($option->getGroupByType() == Mage_Catalog_Model_Product_Option::OPTION_GROUP_SELECT) {
    									$option_id =  $option->getId();
    									foreach ($option->getValues() as $value) {
    										if($value->getId() == $options_select[$option_id]){
    											if($value->getTitle() == 'Express'){
    												$price += 50;
    											}else if($value->getTitle() == 'Normal'){
    												$price += 10;
    											}
    										}
    
    									}
    								}
    							}
    						}
    					}
    				} else {
    					$product_id = $item->getProductId();
    					$value = $item->getOptionByCode('info_buyRequest')->getValue();
    					$params = unserialize($value);
    					$options_select = $params['options'];
    
    					$product = Mage::getModel('catalog/product')->load($product_id);
    					$options = $product->getOptions();
    					foreach ($options as $option) {
    						if ($option->getGroupByType() == Mage_Catalog_Model_Product_Option::OPTION_GROUP_SELECT) {
    							$option_id =  $option->getId();
    							foreach ($option->getValues() as $value) {
    								if($value->getId() == $options_select[$option_id]){
    									if($value->getTitle() == 'Express'){
    										$price += 50;
    									}else if($value->getTitle() == 'Normal'){
    										$price += 10;
    									}
    								}
    
    							}
    						}
    					}
    				}
    			}
    		}
    
    Hopefully i have added all the information regarding shipping method. If there are any more scenarios on which you want code for shipping method let me know i will add it.