Blog

Magento 2 Create Shipping Method

Magento 2 is a rich eCommerce platform and it also supports few shipping methods in the checkout process. However, they are not enough to make you comfortable. In order to be proportional with your development in the future, the customization of shipping methods is really crucial. Therefore, Magento 2 Create Shipping Method is built to make all easier.

With the simple explanation, it is accessible to follow step-by-step and complete the creation of new shipping methods.

All generated shipping methods are stored in Magento Admin Panel.

Please go to Stores > Settings > Configuration > Sales > Delivery Methods to find and enable it on the storefront. But hold on, access the file /Model/Carries/Generatedshippingmethod.php in which you can set the specific shipping cost for each shipping method.

Namely to create the shipping method, please keep tracking on the following steps.

To add a new shipping carrier to the Magento checkout:

  1. Create a new module
  2. Add the carrier configuration
  3. Create the carrier model
  4. Enable the module

Step 1: Create a new module

The example module for use here is Thecoachsmb_CustomShipping.

1.1 Register Module

Source code of app/code/Thecoachsmb/CustomShipping/registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Thecoachsmb_CustomShipping',
    __DIR__
);

1.2 Create composer.json

Source code of app/code/Thecoachsmb/CustomShipping/composer.json

{
    "name": "thecoachsmb/custom-shipping",
    "description": "Custom shipping module",
    "require": {
        "php": "~7.2.0||~7.3.0",
        "magento/framework": "102.0.*",
        "magento/module-backend": "101.0.*",
        "magento/module-catalog": "103.0.*",
        "magento/module-config": "101.1.*",
        "magento/module-directory": "100.3.*",
        "magento/module-quote": "101.1.*",
        "magento/module-sales": "102.0.*",
        "magento/module-sales-rule": "101.1.*",
        "magento/module-shipping": "100.3.*",
        "magento/module-store": "101.0.*"
    },
    "type": "magento2-module",
    "license": [
        "OSL-3.0",
        "AFL-3.0"
    ],
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Thecoachsmb\\CustomShipping\\": ""
        }
    },
    "version": "1.0.0"
}

1.3 Declare the Module

Source code of app/code/Thecoachsmb/CustomShipping/etc/module.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Thecoachsmb_CustomShipping" >
        <sequence>
            <module name="Magento_Store"/>
            <module name="Magento_Sales"/>
            <module name="Magento_Quote"/>
            <module name="Magento_SalesRule"/>
        </sequence>
    </module>
</config>

Step 2: Add the module configuration

To add a module configuration use the following source code snippets.

2.1 Show Configuration in Stores Configuration

Source code  of app/code/Thecoachsmb/CustomShipping/etc/adminhtml/system.xml

The system.xml source code declares custom shipping module options:

  • Enabled
  • Title
  • Method Name
  • Shipping Cost
  • Ship to Applicable Countries
  • Ship to Specific Countries
  • Show Method if Not Applicable
  • Sort Order
<?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="customshipping" translate="label" type="text" sortOrder="0" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Thecoachsmb Custom Shipping Method</label>
                <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1">
                    <label>Enabled</label>
                    <source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
                </field>
                <field id="name" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Method Name</label>
                </field>
                <field id="price" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1">
                    <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" canRestore="1">
                    <label>Calculate Handling Fee</label>
                    <source_model>Magento\Shipping\Model\Source\HandlingType</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" canRestore="1">
                    <label>Title</label>
                </field>
                <field id="sallowspecific" translate="label" type="select" sortOrder="90" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1">
                    <label>Ship to Applicable Countries</label>
                    <frontend_class>shipping-applicable-country</frontend_class>
                    <source_model>Magento\Shipping\Model\Config\Source\Allspecificcountries</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>Magento\Directory\Model\Config\Source\Country</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>Magento\Config\Model\Config\Source\Yesno</source_model>
                    <frontend_class>shipping-skip-hide</frontend_class>
                </field>
                <field id="specificerrmsg" translate="label" type="textarea" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
                    <label>Displayed Error Message</label>
                </field>
            </group>
        </section>
    </system>
</config>

Source code of app/code/Thecoachsmb/CustomShipping/etc/config.xml

First of all, shipping method should be defined in file config.xml, like in a 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 Thecoachsmb\CustomShipping\Model\Carrier\Customshipping.

The config.xml file specifies default values for custom shipping module options and the shipping module model, Thecoachsmb\CustomShipping\Model\Carrier\Customshipping:

<?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>
            <customshipping>
                <active>1</active>
                <sallowspecific>0</sallowspecific>
                <model>Thecoachsmb\CustomShipping\Model\Carrier\Customshipping</model>
                <name>Thecoachsmb Custom Shipping Method</name>
                <price>10.00</price>
                <title>Thecoachsmb Custom Shipping Method</title>
                <specificerrmsg>This shipping method is not available. To use this shipping method, please contact us.</specificerrmsg>
                <handling_type>F</handling_type>
            </customshipping>
        </carriers>
    </default>
</config>
In our config.xml you will notice XML node “model” which define php class “Thecoachsmb\CustomShipping\Model\Carrier\Customshipping“. This model class is charged for shipping method. In this class should be implemented all logic for shipping calculation.

Step 3: Create the carrier model

In this example, the Thecoachsmb\CustomShipping\Model\Carrier\Customshipping class is a skeleton of a carrier model. You can extend it to fit your needs.

The carrier class implements the CarrierInterface interface and retrieves all available shipping methods in the getAllowedMethods function.

The collectRates function returns the \Magento\Shipping\Model\Rate\Result object if the carrier method is available on checkout. Otherwise, it returns false—the carrier method is not applicable to the shopping cart.

Source code of app/code/Thecoachsmb/CustomShipping/Model/Carrier/Customshipping.php

<?php
namespace Thecoachsmb\CustomShipping\Model\Carrier;

use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Shipping\Model\Rate\Result;

class Customshipping extends \Magento\Shipping\Model\Carrier\AbstractCarrier implements
    \Magento\Shipping\Model\Carrier\CarrierInterface
{
    /**
     * @var string
     */
    protected $_code = 'customshipping';

    /**
     * @var \Magento\Shipping\Model\Rate\ResultFactory
     */
    protected $_rateResultFactory;

    /**
     * @var \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory
     */
    protected $_rateMethodFactory;

    /**
     * Shipping constructor.
     *
     * @param \Magento\Framework\App\Config\ScopeConfigInterface          $scopeConfig
     * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory  $rateErrorFactory
     * @param \Psr\Log\LoggerInterface                                    $logger
     * @param \Magento\Shipping\Model\Rate\ResultFactory                  $rateResultFactory
     * @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory
     * @param array                                                       $data
     */
    public function __construct(
        \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
        \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory,
        \Psr\Log\LoggerInterface $logger,
        \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory,
        \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory,
        array $data = []
    ) {
        $this->_rateResultFactory = $rateResultFactory;
        $this->_rateMethodFactory = $rateMethodFactory;
        parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);
    }

    /**
     * get allowed methods
     * @return array
     */
    public function getAllowedMethods()
    {
        return [$this->_code => $this->getConfigData('name')];
    }

    /**
     * @return float
     */
    private function getShippingPrice()
    {
        $configPrice = $this->getConfigData('price');

        $shippingPrice = $this->getFinalPriceWithHandlingFee($configPrice);

        return $shippingPrice;
    }

    /**
     * @param RateRequest $request
     * @return bool|Result
     */
    public function collectRates(RateRequest $request)
    {
        if (!$this->getConfigFlag('active')) {
            return false;
        }

        /** @var \Magento\Shipping\Model\Rate\Result $result */
        $result = $this->_rateResultFactory->create();

        /** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */
        $method = $this->_rateMethodFactory->create();

        $method->setCarrier($this->_code);
        $method->setCarrierTitle($this->getConfigData('title'));

        $method->setMethod($this->_code);
        $method->setMethodTitle($this->getConfigData('name'));

        $amount = $this->getShippingPrice();

        $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 “\Magento\Shipping\Model\Carrier\AbstractCarrier” and implement “\Magento\Shipping\Model\Carrier\CarrierInterface“.

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 “customshipping“. It’s related to config.xml and node structure.

Php method “collectRates” accepts parameter “$request” which is instance of class “Magento\Quote\Model\Quote\Address\RateRequest“. 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.

Step 4: Enable the module

Run the commands below to register Thecoachsmb_CustomShipping module:

php bin/magento module:enable Thecoachsmb_CustomShipping
php bin/magento setup:upgrade && php bin/magento se:s:d -f && php bin/magento c:f

After successful running the commands, you will see the output as below:


Conclusion

In this article, we are learning  the process of creating custom shipping method. Follow the article, step by step. Please feel free to comment below for any queries or concerns.

 

Leave a Reply

Your email address will not be published. Required fields are marked *