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.