In this post we will demonstrate how to create Magento 2 module with a shipping method. If you are already familiar with Magento 1, all examples will be very clear. Creating shipping method is pretty easy, so let’s go with an example.
First of all, you will need to create a Magento module. You should create directory structure like in a screen-shot below:
After creating a module, you should enable module with a shell script:
php -f /bin/magento module:enable Inchoo_Shipping
Also, you can check is module enabled or not with a command (this script will print a list of all enabled modules):
php -f /bin/magento module:status
Let’s start with a class which handles shipping method. First of all, shipping method should be defined in file config.xml, like in a screen-shot below. Without it, it can’t work. The main node in xml is “default” and child of node “carriers” should have the same name as property $_code in shipping class “InchooShippingModelCarrierExample“.
<?xml version="1.0"?>
<config xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xsi_noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
<default>
<carriers>
<example>
<active>1</active>
<sallowspecific>0</sallowspecific>
<model>InchooShippingModelCarrierExample</model>
<name>Inchoo Example Shipping</name>
<price>15.00</price>
<title>Inchoo Example</title>
<type>I</type>
<specificerrmsg>This shipping method is not available. To use this shipping method, please contact us.</specificerrmsg>
</example>
</carriers>
</default>
</config>
In our config.xml you will notice XML node “model” which define php class “InchooShippingModelCarrierExample“. This model class is charged for shipping method. In this class should be implemented all logic for shipping calculation.
Also every shipping method should have config options in admin. You can add shipping method options through system.xml file. Example is below:
<?xml version="1.0"?>
<config xmlns_xsi="http://www.w3.org/2001/XMLSchema-instance" xsi_noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
<system>
<section id="carriers" translate="label" type="text" sortOrder="320" showInDefault="1" showInWebsite="1" showInStore="1">
<group id="example" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Inchoo Example</label>
<field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Enabled</label>
<source_model>MagentoConfigModelConfigSourceYesno</source_model>
</field>
<field id="name" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Method Name</label>
</field>
<field id="price" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Price</label>
<validate>validate-number validate-zero-or-greater</validate>
</field>
<field id="handling_type" translate="label" type="select" sortOrder="7" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Calculate Handling Fee</label>
<source_model>MagentoShippingModelSourceHandlingType</source_model>
</field>
<field id="handling_fee" translate="label" type="text" sortOrder="8" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Handling Fee</label>
<validate>validate-number validate-zero-or-greater</validate>
</field>
<field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Sort Order</label>
</field>
<field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Title</label>
</field>
<field id="sallowspecific" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Ship to Applicable Countries</label>
<frontend_class>shipping-applicable-country</frontend_class>
<source_model>MagentoShippingModelConfigSourceAllspecificcountries</source_model>
</field>
<field id="specificcountry" translate="label" type="multiselect" sortOrder="91" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Ship to Specific Countries</label>
<source_model>MagentoDirectoryModelConfigSourceCountry</source_model>
<can_be_empty>1</can_be_empty>
</field>
<field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1" showInStore="0">
<label>Show Method if Not Applicable</label>
<source_model>MagentoConfigModelConfigSourceYesno</source_model>
</field>
<field id="specificerrmsg" translate="label" type="textarea" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Displayed Error Message</label>
</field>
</group>
</section>
</system>
</config>
Shipping class should look like the example below:
<?php
namespace InchooShippingModelCarrier;
use MagentoQuoteModelQuoteAddressRateRequest;
use MagentoShippingModelRateResult;
class Example extends MagentoShippingModelCarrierAbstractCarrier implements
MagentoShippingModelCarrierCarrierInterface
{
/**
* @var string
*/
protected $_code = 'example';
/**
* @param MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig
* @param MagentoQuoteModelQuoteAddressRateResultErrorFactory $rateErrorFactory
* @param PsrLogLoggerInterface $logger
* @param MagentoShippingModelRateResultFactory $rateResultFactory
* @param MagentoQuoteModelQuoteAddressRateResultMethodFactory $rateMethodFactory
* @param array $data
*/
public function __construct(
MagentoFrameworkAppConfigScopeConfigInterface $scopeConfig,
MagentoQuoteModelQuoteAddressRateResultErrorFactory $rateErrorFactory,
PsrLogLoggerInterface $logger,
MagentoShippingModelRateResultFactory $rateResultFactory,
MagentoQuoteModelQuoteAddressRateResultMethodFactory $rateMethodFactory,
array $data = []
) {
$this->_rateResultFactory = $rateResultFactory;
$this->_rateMethodFactory = $rateMethodFactory;
parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);
}
/**
* @return array
*/
public function getAllowedMethods()
{
return ['example' => $this->getConfigData('name')];
}
/**
* @param RateRequest $request
* @return bool|Result
*/
public function collectRates(RateRequest $request)
{
if (!$this->getConfigFlag('active')) {
return false;
}
/** @var MagentoShippingModelRateResult $result */
$result = $this->_rateResultFactory->create();
/** @var MagentoQuoteModelQuoteAddressRateResultMethod $method */
$method = $this->_rateMethodFactory->create();
$method->setCarrier('example');
$method->setCarrierTitle($this->getConfigData('title'));
$method->setMethod('example');
$method->setMethodTitle($this->getConfigData('name'));
/*you can fetch shipping price from different sources over some APIs, we used price from config.xml - xml node price*/
$amount = $this->getConfigData('price');
$method->setPrice($amount);
$method->setCost($amount);
$result->append($method);
return $result;
}
}
In order to properly write php class for shipping method, you should respect some Magento 2 rules. Every Magento 2 shipping class should extend “MagentoShippingModelCarrierAbstractCarrier” and implement “MagentoShippingModelCarrierCarrierInterface“.
In shipping model you need to create at least two php methods: “getAllowedMethods” and “collectRates“. This methods are required by abstract class and interface. Also, you should define property $_code with value. In our case, that is “example“. It’s related to config.xml and node structure.
Php method “collectRates” accepts parameter “$request” which is instance of class “MagentoQuoteModelQuoteAddressRateRequest“. This class contains all information about items in cart/quote, weight, shipping address and so on. In this method you can implement all logic for shipping calculation. From this method you can call other services for shipping price calculation but it depends about your integration.
You can see more info in screen-shot below.
If you implemented everything as I wrote, you will be able to see shipping method on checkout.
This is very simple example I hope that it will be useful for you 🙂
However, if you’re having questions or need help regarding Magento development, we would be happy to help you out by creating a detailed custom report based on our technical audit. Feel free to get in touch!