Sunday, November 13, 2011

Creating a Custom Magento Shipping Method


Explaining The Shipping Architecture

Before making our own module, we’ll look at how Magento’s existing shipping rate functionality operates. If you are already familiar with this, then you should skip to the next section.
For this walkthrough we’ll use the “Flatrate” shipping method which is available by default with Magento. This method requires two files (excluding inherited classes and so on), these are:
  • app/code/core/Mage/Shipping/Model/Carrier/Flatrate.php
  • app/code/core/Mage/Shipping/etc/config.xml

Open Flatrate.php and take a look - there are four elements of the class which are essential to the operation of this rate.
app/code/core/Mage/Shipping/Model/Carrier/Flatrate.php
protected $_code = 'flatrate';
protected $_isFixed = true;
 
public function collectRates() {}
public function getAllowedMethods() {}

The first important element is the $_code property. This is a code which must be unique to your shipping method. It’s important to note this code is specified in other places, so you need to be consistent with its naming (more on this later). The next property, $_isFixed, is telling Magento that this shipping method is a fixed, one time fee - rather than a recurring payment, which is the alternative option here.
The second file is config.xml; this file is for all Magento shipping methods and as such, it is quite large. However, we are only interested in one part of it:
app/code/core/Mage/Shipping/etc/config.xml
<carriers>
    <flatrate>
        <active>1</active>
        <sallowspecific>0</sallowspecific>
        <model>shipping/carrier_flatrate</model>
        <name>Fixed</name>
        <price>5.00</price>
        <title>Flat Rate</title>
        <type>I</type>
        <specificerrmsg>This shipping method is currently unavailable...(etc)</specificerrmsg>
        <handling_type>F</handling_type>
    </flatrate>
</carriers>

Notice how the node is called <flatrate> to match the $_code property in the Flatrate.php class we saw earlier. Most of the nodes above are easy to understand, but some will benefit from a little more explanation:
sallowspecific set to 1 to limit the countries the rate is applicable to
title the courier name
name the rate name
type an option specific to flatrate, whether the charge is per item or order
specifierrmsg a message to show if the method does not apply to this country
handling_type whether the handling charge is fixed, per item or a percentage

The <model> node in config.xml, as shown above, tells Magento which shipping class to use; in our example, that is shipping/carrier_flatrate. Magento converts shipping/carrier_flatrate to Mage_Shipping_Model_Carrier_Flatrate (found in app/code/core/Mage/Shipping/Model/Carrier/Flatrate.php). The class within Magento responsible for loading the correct rate is Mage_Shipping_Model_Shipping. It is a good idea to take a look at this class and familiarise yourself with how Magento handles this.
We can also take a deeper look at the flatrate classfile. This class extends Mage_Shipping_Model_Carrier_Abstract and implements Mage_Shipping_Model_Carrier_Interface. In fact since all shipping methods must do the same, we can work out which methods require implementation, and which are already there. In general we will be interested in implementing two methods:
  • collectRates()
  • getAllowedMethods()

All other implementation is provided in Mage_Shipping_Model_Carrier_Abstract, so these two methods are all we need to create our own shipping model.

Creating Your Own Rate

This example shows how to create a simple shipping rate, which can provide the basis of a more complicated rate should you need to adapt this for your own purposes. The first step is to make our module skeleton by creating a directory structure along these lines:
app/code/local/Foobar/
app/code/local/Foobar/Shipping/
app/code/local/Foobar/Shipping/etc/
app/code/local/Foobar/Shipping/Model/

Then we need to add a module activation file to app/etc/modules/Foobar_Shipping.xml with the following contents:
app/etc/modules/Foobar_Shipping.xml
<?xml version="1.0"?>
<config>
    <modules>
        <Foobar_Shipping>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
                <Mage_Shipping/>
            </depends>
        </Foobar_Shipping>
    </modules>
</config>

Next we make the config file; here’s the one I used when creating this example:
app/code/local/Foobar/Shipping/etc/config.xml
<?xml version="1.0" ?>
<config>
    <modules>
        <Foobar_Shipping>
            <version>0.1.0</version>
        </Foobar_Shipping>
    </modules>
    <global>
        <models>
            <foobar_shipping>
                <class>Foobar_Shipping_Model</class>
            </foobar_shipping>
        </models>
    </global>
    <default>
        <carriers>
            <foobar_customrate>
                <active>1</active>
                <model>foobar_shipping/carrier_customrate</model>
                <title>Foobar Shipping</title>
                <name>Default Rate</name>
            </foobar_customrate>
        </carriers>
    </default>
</config>

Finally we add our custom shipping class.
app/code/local/Foobar/Shipping/Model/Carrier/Customrate.php
<?php
 
class Foobar_Shipping_Model_Carrier_Customrate
    extends Mage_Shipping_Model_Carrier_Abstract
    implements Mage_Shipping_Model_Carrier_Interface
{
    protected $_code = 'foobar_customrate';
    protected $_isFixed = true;
 
    public function collectRates(Mage_Shipping_Model_Rate_Request $request)
    {
 
        if (!$this->getConfigFlag('active')) {
            return false;
        }
 
        $result = Mage::getModel('shipping/rate_result');
 
        $method = Mage::getModel('shipping/rate_result_method');
        $method->setCarrier('foobar_customrate');
        $method->setCarrierTitle($this->getConfigData('title'));
        $method->setMethod('foobar_customrate');
        $method->setMethodTitle($this->getConfigData('name'));
        $method->setPrice(5);
        $method->setCost(2);
 
        $result->append($method);
 
        return $result;
    }
 
    public function getAllowedMethods()
    {
        return array('foobar_customrate' => $this->getConfigData('name'));
    }
}

After clearing your cache, go and ahead and add a product to your cart and go to checkout. You should see our shipping method sitting there with a charge (of 5 units of whatever currency your Magento is set up to use). Congratulations - you have a basic shipping module available for use!

Behind the Scenes

When we hit the checkout, Magento uses the collectRates() method in our class; via this method you can offer shipping rates (or not) by storing an object in $result, which is Mage_Shipping_Model_Rate_Result. Basically, you add instances of Mage_Shipping_Model_Rate_Result_Method ($method in our example) to this object via the append() method. The rate result object holds all the values such as carrier and method name, as well as the all-important price and cost. In this way it becomes easy to offer multiple rates via the collectRates() method, simply by creating more rate results and calling append() for each one, in this way:
<?php
 
$result = Mage::getModel('shipping/rate_result');
 
$method = Mage::getModel('shipping/rate_result_method');
$method->setCarrier('foobar_customrate');
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod('foobar_customrate_one');
$method->setMethodTitle('Rate 1');
$method->setPrice(5);
$method->setCost(2);
 
$result->append($method);
 
$method = Mage::getModel('shipping/rate_result_method');
$method->setCarrier('foobar_customrate');
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod('foobar_customrate_two');
$method->setMethodTitle('Rate 2');
$method->setPrice(5);
$method->setCost(2);
 
$result->append($method);
After using the code above, you will see two shipping methods available for use. Methods are grouped by carrier, and the Carrier Title and Method Title are displayed on the frontend. If your shipping method uses multiple rates then it makes little sense to use the config to specify the names attribute, which is why they are hardcoded here. In this case you are free to remove the <name> node from your config.xml, as it is not used anywhere else.

No comments:

Post a Comment