Advanced Shipping Method Module

In this tutorial, we are going to see how to create a shipping method with form fields.

For example, you want to show user a list of your stores to choose from in your shipping method, or want to collect some information from used based on the shipping method. This type of feature is not supported in magneto by default, so we would to edit many files. This tutorial is in continuation of a previous tutorial written, so please go through it before reading this one. The basic files need for creating a shipping method is not explained here since its already done in the previous tutorial. Attached is the module used in this tutorial
[dm]22[/dm]
Here are screen shots of the end result of the module

Magento Store Pickup

Magento Store Pickup

Magento Store Pickup

Magento Store Pickup

Magento Store Pickup

Magento Store Pickup

Now let’s see the steps required to achieve this. Also the name of the module used is Excellence_Pickup.

Step1: Create Basic Shipping Method

This step is basically what we have already seen in the previous tutorial, so you need to add the necessary code in config.xml, system.xml and the Carrier Model. But in our case we are going to make some changes to the carrier model, so lets see the code

<?php

class Excellence_Pickup_Model_Carrier_Pickup extends Mage_Shipping_Model_Carrier_Abstract
implements Mage_Shipping_Model_Carrier_Interface {

	protected $_code = 'pickup';

	public function getFormBlock(){
		return 'pickup/pickup';
	}

	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');
		$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);
		return $result;
	}
	public function getAllowedMethods()
	{
		return array('excellence'=>$this->getConfigData('name'));
	}
}

as you can see i have added a new function called getFormBlock() which we will use later.

Step2

We need to make changes to the shipping html, so that we are able to display our form. For this we need to edit the checkout\onepage\shipping_method\available.phtml file. So we will override this pthml file. In your module’s layout file pickup.xml put in this code

<checkout_onepage_index>
    	<reference name='head'>
    		<reference name="head">
	            <action method="addItem"><type>js</type><name>pickup/jquery-1.6.4.min.js</name></action>
	            <action method="addItem"><type>js</type><name>pickup/noconflict.js</name></action>
        	</reference>
    	</reference>
    	<reference name='checkout.onepage.shipping_method.available'>
    		<action method='setTemplate'><template>pickup/checkout/onepage/shipping_method/available.phtml</template></action>
    	</reference>
    </checkout_onepage_index>
    <checkout_onepage_shippingmethod>
    	<reference name='root'>
    		<action method='setTemplate'><template>pickup/checkout/onepage/shipping_method/available.phtml</template></action>
    	</reference>
    </checkout_onepage_shippingmethod>

Here we have changed the template path of the shipping_method/avaiable.phtml file to our module. Also we have added jquery to the checkout page.
Next the we need to add code to the available.phtml file to show our form. We will place this after the <label> tag get’s finished.

<?php
                        	$carrier = Mage::getModel('shipping/config')->getCarrierInstance($code);
                        	if($carrier->getFormBlock()){
                        		$block = $this->getLayout()->createBlock($carrier->getFormBlock());
                        		$block->setMethodCode($code);
                        		$block->setRate($_rate);
                        		$block->setMethodInstance($carrier);
                        		echo $block->_toHtml();
                        	} 
                        ?>

Here we are creating an block object, of the getFormBlock() which we defined in our carrier class. We have set code, rate object and carrier object in the block which we will use inside the block template. To see the exact placement of this code, download the module and look at the available.phtml file.

Step3

We will now create the form block, the class for our form block was ‘pickup/pickup’.

<?php
class Excellence_Pickup_Block_Pickup extends Mage_Checkout_Block_Onepage_Shipping_Method_Available
{
	public function __construct(){
		$this->setTemplate('pickup/pickup.phtml');		
	}
}

and the code that will go inside the template would be

<?php 
	$_code=$this->getMethodCode();
	$carrier = $this->getMethodInstance();
	$pickupData = $this->getQuote()->getPickupData();
	$_rate = $this->getRate();
	if(!isset($pickupData['store']))
	{
		$pickupData['store'] = -1;
	}
	if(!isset($pickupData['name']))
	{
		$pickupData['name'] = '';
	}
?>
<ul class="form-list" id="shipping_form_<?php echo $_rate->getCode() ?>" style="display:none;">
    <li>
        <label for="<?php echo $_code ?>_store" class="required"><em>*</em><?php echo $this->__('Choose Store Location:') ?></label>
        <span class="input-box">
            <select class="required-entry" name="shipping_pickup[store]">
            	<option value='' <?php if($pickupData['store'] == ''){ echo "selected=selected";} ?>><?php echo $this->__('Select Store..');?></option>
            	<option value='Store1' <?php if($pickupData['store'] == 'Store1'){ echo "selected=selected";} ?>><?php echo $this->__('Store1');?></option>
            	<option value='Store2' <?php if($pickupData['store'] == 'Store2'){ echo "selected=selected";} ?>><?php echo $this->__('Store2');?></option>
            	<option value='Store3' <?php if($pickupData['store'] == 'Store3'){ echo "selected=selected";} ?>><?php echo $this->__('Store3');?></option>
            	<option value='Store4' <?php if($pickupData['store'] == 'Store4'){ echo "selected=selected";} ?>><?php echo $this->__('Store4');?></option>
            </select>
        </span>
        <label for="<?php echo $_code ?>_store" class="required"><em>*</em><?php echo $this->__("Pickup Person's Name:") ?></label>
        <span class="input-box">
        	<input type='text' name='shipping_pickup[name]' class='required-entry input-text' value='<?php echo $pickupData['name']?>' />
        </span>
    </li>
</ul>

Here as you can see i have added two form fields, one is a drop down and other is a text field. Also by default the style has been set to display:none so this form will not show up, so we need to add the javascript code to make the shipping method form hide/show on click on the shipping methods.

Step4

Here is the javascript code that we need to add to the available.phtml file

jQuery(document).ready(function(){
	hideShippingAll();
	jQuery('input[type="radio"][name="shipping_method"]').click(function(){
			hideShippingAll();
			var code = jQuery(this).val();
			if(jQuery(this).is(':checked')){
				showShipping(code);
			}
	});
	jQuery('input[type="radio"][name="shipping_method"]').each(function(){
		var code = jQuery(this).val();
		if(jQuery(this).is(":checked")){
			showShipping(code);
		}		
	});
});
function showShipping(code){
	if(jQuery('#'+'shipping_form_'+code).length != 0){
		jQuery('#'+'shipping_form_'+code).show();
		jQuery(this).find('.required-entry').attr('disabled','false');
	}
}
function hideShippingAll(){
	jQuery('input[type="radio"][name="shipping_method"]').each(function(){
		var code = jQuery(this).val();
		jQuery('#'+'shipping_form_'+code).hide();
		jQuery(this).find('.required-entry').attr('disabled','true');	
	});
}
Step5

Now we need to save these fields form to the database, when ever an order is placed all these fields needs to be saved to the database. For this we will have to create a new table called ‘order_shipping_pickup’.

<?php
$installer = $this;
$installer->startSetup();
$installer->run("
	CREATE TABLE IF NOT EXISTS {$this->getTable('order_shipping_pickup')} (
	  `id` int(11) unsigned NOT NULL auto_increment,
	  `order_id` int(11) NOT NULL,
	  `store` varchar(255) NOT NULL default '',
	  `name` varchar(255) NOT NULL default '',
	  PRIMARY KEY (`id`)
	) ENGINE=InnoDB DEFAULT CHARSET=utf8;
");
$installer->endSetup();

Next we add some event observer for when order is saved sales_model_service_quote_submit_after and when the continue button is pressed on the shipping method step checkout_controller_onepage_save_shipping_method. Below are all the events used

<global>
    	<events>
    		<checkout_controller_onepage_save_shipping_method>
    			<observers>
    				<checkout_controller_onepage_save_shipping_method>
			            <type>model</type>
			            <class>pickup/observer</class>
			            <method>saveShippingMethod</method>
			        </checkout_controller_onepage_save_shipping_method>
    			</observers>
    		</checkout_controller_onepage_save_shipping_method>
    		<sales_model_service_quote_submit_after>
    			<observers>
    				<checkout_controller_onepage_save_shipping_method>
			            <type>model</type>
			            <class>pickup/observer</class>
			            <method>saveOrderAfter</method>
			        </checkout_controller_onepage_save_shipping_method>
    			</observers>
    		</sales_model_service_quote_submit_after>
    		<sales_order_load_after>
    			<observers>
    				<sales_order_load_after>
    					<type>model</type>
			            <class>pickup/observer</class>
			            <method>loadOrderAfter</method>
    				</sales_order_load_after>
    			</observers>
    		</sales_order_load_after>
    		<sales_quote_load_after>
    			<observers>
    				<sales_quote_load_after>
    					<type>model</type>
			            <class>pickup/observer</class>
			            <method>loadQuoteAfter</method>
    				</sales_quote_load_after>
    			</observers>
    		</sales_quote_load_after>
    	</events>
</global>

Next we need to create the observer class

<?php

class Excellence_Pickup_Model_Observer extends Varien_Object
{
	public function saveShippingMethod($evt){
		$request = $evt->getRequest();
		$quote = $evt->getQuote();
		$pickup = $request->getParam('shipping_pickup',false);
		$quote_id = $quote->getId();
		$data = array($quote_id => $pickup);
		if($pickup){
			Mage::getSingleton('checkout/session')->setPickup($data);
		}
		print_r($data);die;
	}
	public function saveOrderAfter($evt){
		$order = $evt->getOrder();
		$quote = $evt->getQuote();
		$quote_id = $quote->getId();
		$pickup = Mage::getSingleton('checkout/session')->getPickup();
		if(isset($pickup[$quote_id])){
			$data = $pickup[$quote_id];
			$data['order_id'] = $order->getId();
			$pickupModel = Mage::getModel('pickup/pickup');
			$pickupModel->setData($data);
			$pickupModel->save();
		}
	}
	public function loadOrderAfter($evt){
		$order = $evt->getOrder();
		if($order->getId()){
			$order_id = $order->getId();
			$pickupCollection = Mage::getModel('pickup/pickup')->getCollection();
			$pickupCollection->addFieldToFilter('order_id',$order_id);
			$pickup = $pickupCollection->getFirstItem();
			$order->setPickupObject($pickup);
		}
	}	
	public function loadQuoteAfter($evt)
	{
		$quote = $evt->getQuote();
		if($quote->getId()){
			$quote_id = $quote->getId();
			$pickup = Mage::getSingleton('checkout/session')->getPickup();
			if(isset($pickup[$quote_id])){
				$data = $pickup[$quote_id];
				$quote->setPickupData($data);
			}
		}
	}
}

As you can see in the observer class we have used the model Mage::getModel(‘pickup/pickup’). You will find details of this models code in the source code.

Step6

The last step is show the information of the form in the order email, order admin view, invoices etc. Doing this very easy because magento accesses shipping information from a central function getShippingDescription() in the order model. So will override the order model and put in our own code

<models>
       		<sales>
       			<rewrite>
        			<order>Excellence_Pickup_Model_Sales_Order</order>
       			</rewrite>
       		</sales>
</models>

and the code in the order class would be

<?php
class Excellence_Pickup_Model_Sales_Order extends Mage_Sales_Model_Order{
	public function getShippingDescription(){
		$desc = parent::getShippingDescription();
		$pickupObject = $this->getPickupObject();
		if($pickupObject){
			$desc .= '<br/><b>Store</b>: '.$pickupObject->getStore();
			$desc .= '<br/><b>Name</b>: '.$pickupObject->getName();
			$desc .= '<br/>';
		}
		return $desc;
	}
}

Here we have added the store name and the persons name to the shipping description.

These are all the steps to add a shipping method with form field. All fields are not explain in the tutorial, many basic files you can find in the module’s source code. Only important things have been explained in this tutorial.
  • Hello, 

    Thank you very much for this shipping method! I will test it first at all conditions..:)

    I am thinking about one other way, that means:
    When i will select (in checkout method), “pay as guest” ->next step, i have to fill the billing information -> and then jump to last step (order review).
    This schedule i want to be activated when is selected “pay as guest” only.  Is that possible?

    Thanks, 
    Zanias Pantelis

    • Manish Prakash

      Yes this is possible. But would require lot of coding, won’t be able to explain it in a tutorial.

  • I think that “inline translation” doesn’t work when the module is activated..

    p.s. one more.. It’s not appeared (the store and name information) in mail confirmation form! I need it too.. is that possible to add it?

    • Manish Prakash

      let me look into the email information

      • any news for email information?
        is that possible with multi-language?:)

        Thanks

  • xavi

    It Goes well until I try to place an order, then I get “Please specify a shipping method.” Can you fix that?

  • Lhardrex

    Hi Manish Prakash! Can i build this module with prototype? Using Jquery with Prototype makes my site slow, and here in Brazil this is a problem. 
    Thank you

    • Manish Prakash

      Yes I think you can do that. There is only very little javascript used In the module.

      But I am not an expert in prototype so won’t be able to convert the jquery code to prototype.

  • Great tutorial. 

  • Asmara_azriel

    where is that pickup.xml located? i am lost on that part.  i dont know where would i see that.

    • Manish Prakash

      Pickup.xml is in the new module created for the shipping method.

      Please download the module source code and then you will find all the files

      • Amitkumar Magento Developer

        Hi Manish

        Thanks for great post.

        May i have a download link of source code link as i faced issue to download.

  • is this module fixed? cause we have had a problem with email information…

  • Anonymous

    If you prefer using prototype then here is rewritten Javascript code:

    function attachShippingMethodFormHandling() {
    hideShippingAll();
    $$(‘input[type=”radio”][name=”shipping_method”]’).each(function(inputEl) {
    inputEl.observe(‘click’, function(event) {
    hideShippingAll();
    var el = event.element();
    var code = el.value;

    if (el.checked) {
    showShipping(code);
    }
    });
    });

    $$(‘input[type=”radio”][name=”shipping_method”]’).each(function(el) {
    var code = el.value;
    if(el.checked) {
    showShipping(code);
    }
    });
    }

    function showShipping(code) {
    if ($$(‘#shipping_form_’ + code).length != 0) {
    $$(‘#shipping_form_’ + code)[0].show();
    $$(‘#shipping_form_’ + code + ‘ .required-entry’).each(function(el) {
    el.removeAttribute(‘disabled’);
    });
    }
    }
    function hideShippingAll() {
    $$(‘input[type=”radio”][name=”shipping_method”]’).each(function(el) {
    var code = el.value;
    if ($$(‘#shipping_form_’ + code).length != 0) {
    $$(‘#shipping_form_’ + code)[0].hide();
    $$(‘#shipping_form_’ + code + ‘ .required-entry’).each(function(el) {
    el.setAttribute(‘disabled’, ‘disabled’);
    });
    }
    });
    }

    • Manish Prakash

      You have put in 2 java script codes, which is the correct one. The first or the second one?

      • Anonymous

        Hi

        Second one is correct.
        I accidentaly added incorrect code at first and if i tried to edit it, the indentation was lost.
        Avo

        Subject: [etechblog] Re: Advanced Shipping Method Module

        • Manish Prakash

          Ok, thanks for this code.

    • Anonymous

      Almost forgot. You also have to add the following code at the end of available.phtml template to get this Javascript to work.

      //

  • Anonymous

    If you want to use Prototype then here is rewritten Javascript code:

    function attachShippingMethodFormHandling() {
        hideShippingAll();
        $$(‘input[type=”radio”][name=”shipping_method”]’).each(function(inputEl) {
            inputEl.observe(‘click’, function(event) {
                hideShippingAll();
                var el = event.element();
                var code = el.value;
                
                if (el.checked) {
                    showShipping(code);
                }
            });
        });
        
        $$(‘input[type=”radio”][name=”shipping_method”]’).each(function(el) {
            var code = el.value;
            if(el.checked) {
                showShipping(code);
            }
        });
    }

    function showShipping(code) {
        if ($$(‘#shipping_form_’ + code).length != 0) {
            $$(‘#shipping_form_’ + code)[0].show();
            $$(‘#shipping_form_’ + code + ‘ .required-entry’).each(function(el) {
                el.removeAttribute(‘disabled’);
            });
        }
    }
    function hideShippingAll() {
        $$(‘input[type=”radio”][name=”shipping_method”]’).each(function(el) {
            var code = el.value;
            if ($$(‘#shipping_form_’ + code).length != 0) {
                $$(‘#shipping_form_’ + code)[0].hide();
                $$(‘#shipping_form_’ + code + ‘ .required-entry’).each(function(el) {
                    el.setAttribute(‘disabled’, ‘disabled’);
                });
            }
        });
    }

  • Anonymous

    Observers “saveShippingMethod()” seems a little odd. Can you review it? It prints out some data and then calls die().

  • Information about Store and Name Still doesn’t send via email.. it’s blank cell. Can we fix it?

  • Andres

    Hi, I ve installed the module and in the backend works fine, but in the front end when I select any of the shipping methods avaliable, including pick up in store, in the progress side bar says that no shipping method has been selected.what could be this.

  • mk_gk

    Hello, someone completely finished ah this method the name and the store is not displayed in the administrator, someone solved this problem?

  • Sergio Vasquez

    in the progress of the purchase, where it says “shipping info” does not show the selected shop, nor the name entered, any solution?

    • Jagesh

      Did you get answer?

  • As someone commented on:

    http://www.excellencemagentoblog.com/magento-create-custom-shipping-method

    this module blocks the checkout process showing the message “Please specify a shipping method”. Magento version 1.6.2.

    Removing this module and all works normally again.

    Seems a bug in your shipping methods code.

    Anyway thanks for sharing

  • Fadzly Othman

    Great Tutorial Manish. But I can’t select the new shipping module. It will show ‘
    Please specify a shipping method’  at the side. Please help.

    • Fadzly Othman

      I managed to select the new shipping method after commenting out the following :

                         model            pickup/observer            saveShippingMethod               

      I am using 1.6.1.0

  • Hi Manish Prakash
    Your Code id good but I founded some mistakes.
    The problem: when something try to checkout (As an user in frontend) it display an alert saying: “Please specify a shipping method” I have seen others users have commented in your blog but, I can see a solution, You can see it as this image:
    http://www.hostpruebas.com/user02/error.JPG

    Can you help me please I need to make a delivery Date in Magento. Just need to fix your example “Advanced Shipping Method Module”
    Sorry if dont understand some words (My English is so bad)

    X(

  • classical

    I want to develop this extention for Multishipping. But i can’t save data!
    How can i do with Observer?
    Thanks for your help!

  • Chukku’s

    I want to develop this extension in Multishipping. Can you please help me to do the store-pickup functioning in Multiple checkout.

    • Chukku’s

      Pls help me….I have also the same pblm. Will you pls help me guys…

  • I need help. I created module pickup.In one page checkout page, choose store pickup.I need to replace the shipping address with the address of the store pickup.

    Everyone can help me to implement solutions. Thanks all.

  • Anu

    Hi,

    Nice tutorial and working fine in magento 1.7.0.2. I have one doubt how to escape and other HTML tag in getShippingDescription function? Please guide this.

    Regards

    Anu

    • Jagesh

      Did you solve this?

      • vignesh

        Did you solve this issue?

  • Jagesh

    Shipping method has not been selected yet . please specify a shipping method error.Please guide this asap

  • Jagesh

    How to show store pickup store name in sales order email template?

  • Cer Gos

    Hi – your solution looks good, but is it possible to have customer complete checkout with no shipping? I would like customer to complete order without activating provided credit card and then I can look for best possible shipping (in Australia) and agree with customer before completing transaction.
    thanks

  • Allen

    Thanks for your tutorial. I have a problem of displaying shipping details. The “Store, and Name” only display at backend, but not in the new_order_placed email template. Even if I select other pickup method, these text still display at backend of the order. I guess this may have some problem. Can anyone help me ?

    $pickupObject = $this ->getPickupObject();
    if($pickupObject){
    $desc .= ‘Store: ‘.$pickupObject->getStore();
    $desc .= ‘Name: ‘.$pickupObject->getName();
    $desc .= ”;
    }

  • Pieter

    Great Manish, works good! We’d like to use the shipping cost based on the shipping cost attribute. The only issue we face is that shipping costs are added up when there are multiple items in a cart. What could we do to only charge one time shipping costs, only cost of th item with the highest shipping cost.

  • Cryms Tetta

    Hi Manish, i write again a non answered question: Is there any way to fetch the store and name information and displaing it during the checkout process? Many thanks. However, great great great post

  • vivek

    Hello Manish,
    I am new to magento any very confuse with the folder and file structure, can you please provide this tutorial with folder and file structure.

    Thanks

  • Nirav

    Hi Manish, Thanks for a great tutorial. But i can’t get store and name information in order confirmation mail while place an order from front-end. So is it possible to get those information in order mail? Also i got those information in order confirmation mail when clicked on “Send Email” from Magento Admin. I am using Magento 1.7.0.2. How can i fix this issue?

  • Ravi shukla

    Manish sir. you post great tutorial

  • vivek

    HI Manish,
    The method not showing in admin in shipping method, only showing in advance option, also not working with other custom shipping method extension.
    Please help
    Thanks

  • Anders

    Dear Manish

    Absolutely brilliant module I must say! Unfortunately it only work with Magento CE 1.8.1.0 but not the new Magento CE 1.9.01. Do you by any chance know what seems to be the problem?

    Kind regards Anders

  • akdn

    Dear Manish,
    It is aweosome, I would like to ask that will it work with 1.9 as well? would you able to share the complete module if you created?

    I look forward to hearing from you.

    Thanks

    akd

  • Jamie Pounder

    Just curious if you could let me know why it doesn’t work for 1.9 so I can fix it, thanks ;)!

  • Andrey

    Hello!

    Thanks for your code.

    I tried to use it in my shipping module but got the error: invalid method save() in saveOrderAfter method. As I understand save method is in the resource model but this model doesn’t run somehow. I edited config file and add the resource model class but the error remains.If I use Mage: getModelResource() – resource model load but if I use just getModel method the error remains. Look forward to advice, how to solve or debug this error (I am using 1.9)

  • santhosh

    Have you implemented this in magento 2? If yes, let me know, how can we do that ?