Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

A shipping module

A carrier in PrestaShop is a record in the back office under "Shipping → Carriers" with a name, a delivery time and a price table. It is usually created by hand, and no module is needed. A shipping module is written when the cost has to be calculated by code: to request a rate from a delivery service or to apply your own formula. Such a module extends CarrierModule.

The example is a separate acmeship module in the modules/acmeship folder.

The main class

<?php
// modules/acmeship/acmeship.php
if (!defined('_PS_VERSION_')) {
    exit;
}
 
class AcmeShip extends CarrierModule
{
    public $id_carrier;
 
    public function __construct()
    {
        $this->name = 'acmeship';
        $this->tab = 'shipping_logistics';
        $this->version = '1.0.0';
        $this->author = 'Acme';
 
        parent::__construct();
 
        $this->displayName = $this->trans('Acme courier', [], 'Modules.Acmeship.Admin');
    }
 
    public function getOrderShippingCost($cart, $shippingCost)
    {
        return 5.0;
    }
 
    public function getOrderShippingCostExternal($cart)
    {
        return $this->getOrderShippingCost($cart, 0);
    }
}

Two methods are mandatory. The engine calls them when it calculates shipping for a cart and takes the returned number as the cost without tax. Here shipping always costs 5 units of the shop currency. Before the call the engine writes the carrier id into the id_carrier property.

MethodWhen it is called
getOrderShippingCost($cart, $shippingCost)The carrier has price ranges; the second argument is the price from the table
getOrderShippingCostExternal($cart)Ranges are not used

Creating the carrier

The module alone does not show up in the shipping list: on install it must create a carrier tied to the module.

// modules/acmeship/acmeship.php
public function install(): bool
{
    if (!parent::install() || !$this->registerHook('actionCarrierUpdate')) {
        return false;
    }
 
    $carrier = new Carrier();
    $carrier->name = 'Acme Courier';
    $carrier->active = true;
    $carrier->is_module = true;
    $carrier->external_module_name = $this->name;
    $carrier->shipping_external = true;
    $carrier->need_range = true;
 
    foreach (Language::getLanguages(false) as $language) {
        $carrier->delay[(int) $language['id_lang']] = '2 days';
    }
 
    if (!$carrier->add()) {
        return false;
    }
 
    return Configuration::updateValue('ACMESHIP_CARRIER_ID', (int) $carrier->id);
}

Carrier is a core model. The is_module, external_module_name and shipping_external properties tie the carrier to the module: the cost will be asked from it. delay is the delivery time text in every language. The module remembers the id of the created carrier in the settings.

What else a carrier needs

For the option to be shown to a customer, the carrier is given zones, customer groups and ranges. The easiest way is to do it in the back office after installation: "Shipping → Carriers", the edit button of "Acme Courier".

WhatWhat for
ZonesWhich regions it delivers to
GroupsWhich customer groups may use it
Weight or price rangesMandatory with need_range, even if the code calculates the price

Modules made for distribution do this in install() with code — the core methods and models addZone(), setGroups(), RangeWeight, RangePrice.

How to see the result

php bin/console prestashop:module install acmeship

Set up zones and ranges, put a product in the cart and go to the shipping step: "Acme Courier" is in the list with the price 5 and the time "2 days".

Calculating from the cart

// modules/acmeship/acmeship.php
public function getOrderShippingCost($cart, $shippingCost)
{
    $weight = $cart->getTotalWeight();
 
    if ($weight > 30) {
        return false;
    }
 
    return 5.0 + $weight * 0.5;
}

The cost now depends on the cart weight. The value false means the carrier is not available for this cart — the option is not shown.

A changed carrier id

When the shop owner edits a carrier, PrestaShop does not change the record but creates a copy with a new id — so that old orders keep pointing to the previous terms. The module must update the saved id:

// modules/acmeship/acmeship.php
public function hookActionCarrierUpdate(array $params): void
{
    if ((int) $params['id_carrier'] === (int) Configuration::get('ACMESHIP_CARRIER_ID')) {
        Configuration::updateValue('ACMESHIP_CARRIER_ID', (int) $params['carrier']->id);
    }
}

id_carrier holds the old id, carrier holds the new object.

Common mistake

getOrderShippingCost() calls an external delivery service without a cache and without a time limit. The method is called on every page with a cart, and a slow response from the service slows down the whole shop. Save the received rate for the cart-and-address pair and set a short request timeout.

Thanks for reading!

Donate to the site — we work hard for you.