Magento Custom Shipping Part 1: Build a Custom Shipping Module

I am developing an ecommerce site in Magento, when I ran into a wall with the built-in shipping system. To be fair, Magento's shipping is incredibly robust, with a lot of add-on plugins to provide any missing requirements. However, like many small businesses, my client has some very unique needs and business logic, which means there is no off-the-shelf solution for their particular scenario. In this case, they needed a custom shipping solution. The product is delivered in bulk by semi truck, and different local delivery areas have different pricing structures. Naturally, hooking into Fedex or UPS shipping systems would not fit the bill.

Read on for a tutorial on how to build a custom shipping module.

Before we get started, there are a few key strings you'll need to define. I'll do my best to call out in the code where they should be used

  • Namespace - this is usually the name of the developer, and must start with a capital letter. I will be using AMC
  • Module Name - this is the name of custom module you are building. I will be using Shipping
  • Group Alias - This groups some XML config options making them easier to access in PHP. I will be using amcshipping
  • Method Name - LocalDelivery



Setup file structure for your new module

The first step is to create a custom module. Add the following folder structure to your Magento installation (replace AMC with your own unique namespace):

  • app/code/local/AMC/
  • app/code/local/AMC/Shipping/
  • app/code/local/AMC/Shipping/etc/
  • app/code/local/AMC/Shipping/Model/
  • app/code/local/AMC/Shipping/Model/Carrier/

It is important that all folder and file names are case-sensitive, so make sure things stay consistent.

Add module specification file

Next, you need to create the Module Specification File, which tells Magento that your module exists and what it is dependent on. This file should be your namespace plus the module name. So in my case, it is AMC_Shipping.xml (Note that if you were to take the folder name structure and replace the / with _ you get this file.).

app/etc/modules/AMC_Shipping.xml

 
<?xml version="1.0"?>
<config>
    <modules>
        <AMC_Shipping>
            <active>true</active>
            <codePool>local</codePool>
            <depends>
              <Mage_Shipping/>
            </depends>
        </AMC_Shipping>
    </modules>
</config>
 

This XML file defines the existence of the AMC_Shipping module by that unique name string. Also notice that I added a tag in the <depends> section that makes my module dependent on the built-in Mage_Shippng module.

Add a module configuration file

This XML file will define the various parameters and options for your new module. It should go in:

app/code/local/AMC/Shipping/etc/config.xml

 
<?xml version="1.0"?>
<config>
  <modules>
    <AMC_Shipping>
      <version>0.1.0</version>
    </AMC_Shipping>
  </modules>
  <global>
    <models>
      <amcshipping>
        <class>AMC_Shipping_Model</class>
      </amcshipping>
    </models>
    <resources>
      <amcshipping_setup>
        <setup>
          <module>AMC_Shipping</module>
        </setup>
        <connection>
          <use>core_setup</use>
        </connection>
      </amcshipping_setup>
    </resources>
  </global>
  <default>
    <carriers>
      <amcshipping>
        <active>1</active>
        <allowed_methods>delivery</allowed_methods>
        <methods>delivery</methods>
        <sallowspecific>0</sallowspecific>
        <model>AMC_Shipping_Model_Carrier_LocalDelivery</model>
        <name>AMC Custom Shipping</name>
        <title>Local Delivery</title>
        <specificerrmsg>This shipping method is currently unavailable. If
          you would like to ship using this shipping method, please contact
          us.
                </specificerrmsg>
        <handling_type>F</handling_type>
      </amcshipping>
    </carriers>
  </default>
</config>
 

This file defines a few things including the version, the display name of the module, and also a shipping carrier with the id "customrate" which uses the PHP class Carrier_Customrate.

Add an admin configuration file

Like the config.xml, we also need a file that defines the options available to the system administrator:

app/local/AMC/Shipping/etc/system.xml

 
<?xml version="1.0"?>
<config>
  <sections>
    <carriers>
      <groups>
        <amcshipping translate="label" module="shipping">
          <label>AMC Custom Shipping</label>
          <frontend_type>text</frontend_type>
          <sort_order>13</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>
            <sort_order translate="label">
              <label>Sort order</label>
              <frontend_type>text</frontend_type>
              <sort_order>100</sort_order>
              <show_in_default>1</show_in_default>
              <show_in_website>1</show_in_website>
              <show_in_store>1</show_in_store>
            </sort_order>
            <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>
            <methodtitle translate="label">
              <label>Method 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>
            </methodtitle>
            <handling translate="label">
              <label>Handling fee</label>
              <frontend_type>text</frontend_type>
              <sort_order>12</sort_order>
              <show_in_default>1</show_in_default>
              <show_in_website>1</show_in_website>
              <show_in_store>1</show_in_store>
            </handling>
            <handling_type translate="label">
              <label>Calculate Handling Fee</label>
              <frontend_type>select</frontend_type>
              <source_model>shipping/source_handlingType</source_model>
              <sort_order>10</sort_order>
              <show_in_default>1</show_in_default>
              <show_in_website>1</show_in_website>
              <show_in_store>0</show_in_store>
            </handling_type>
 
            <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>1</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>1</show_in_store>
            </specificcountry>
            <specificerrmsg translate="label">
              <label>Displayed Error Message</label>
              <frontend_type>textarea</frontend_type>
              <sort_order>80</sort_order>
              <show_in_default>1</show_in_default>
              <show_in_website>1</show_in_website>
              <show_in_store>1</show_in_store>
            </specificerrmsg>
          </fields>
        </amcshipping>
      </groups>
    </carriers>
  </sections>
</config>
 

Add a class to define the shipping carrier business logic

Now we create a PHP class that will allow us to build our custom logic for however our shipping method needs to be calculated. We add another file:

app/code/local/AMC/Shipping/Model/LocalDelivery.php

 
<?php
class AMC_Shipping_Model_Carrier_LocalDelivery extends Mage_Shipping_Model_Carrier_Abstract
{
    /* Use group alias */
    protected $_code = 'amcshipping';
 
    public function collectRates(Mage_Shipping_Model_Rate_Request $request)
    {
        // skip if not enabled
        if (!Mage::getStoreConfig('carriers/'.$this->_code.'/active'))
            return false;
 
        $result = Mage::getModel('shipping/rate_result');
        $handling = 0;
        if(Mage::getStoreConfig('carriers/'.$this->_code.'/handling') >0)
            $handling = Mage::getStoreConfig('carriers/'.$this->_code.'/handling');
        if(Mage::getStoreConfig('carriers/'.$this->_code.'/handling_type') == 'P' && $request->getPackageValue() > 0)
            $handling = $request->getPackageValue()*$handling;
 
        $method = Mage::getModel('shipping/rate_result_method');
        $method->setCarrier($this->_code);
        $method->setCarrierTitle(Mage::getStoreConfig('carriers/'.$this->_code.'/title'));
        /* Use method name */
        $method->setMethod('delivery');
        $method->setMethodTitle(Mage::getStoreConfig('carriers/'.$this->_code.'/methodtitle'));
        $method->setCost($handling);
        $method->setPrice($handling);
        $result->append($method);
        return $result; 
    }
}
 

Again, make sure to replace AMC with your namespace. Notice the function and filename match the XML tag we created in the config.xml file. You can add multiple carriers this way relatively easily by simply adding a XML node and PHP class for each one.

This PHP class does a few things. First, it extends the built-in Mage Shipping classes for Carriers, and it provides two important functions, collectRates() and getAllowedMethods. These override the default behaviors from the built-in classes so that we can return different results. We currently are getting the shipping price from a configuration option. We'll change that in a later tutorial.

If you were to now clear your caches and proceed to the checkout, you would see a new shipping carrier available called "AMC Shipping". Within that carrier, you would have one shipping method, with a $5 price (or 5 of whatever your currency unit is).

Add comment


Security code
Refresh

img_8155.jpg