Shipping plugin development

Contents...

Shipping plugins are used to calculate the shipping cost in real time during the checkout in the online storefront. A plugin accepts basic data of the products being shipped (product list, total weight, total cost, etc.) and returns the shipping cost, calculated using the plugin's internal algorithm; e.g., as a percentage of the total order cost or by sending a request to the shipping service server via its API (if available). The specific implementation is individual for each shipping plugin and is defined by its developer.

In the Webasyst framework the basis for development of shipping plugins is provided by the system kernel. The framework offers a set of methods and a certain data exchange format for retrieving order details and returning the shipping cost calculation results. These basic methods also include methods for printing related documents, such as waybills, for example.

Take a look at the source code of the “Flat rate” shipping plugin as an example (GitHub repository).

Shipping plugins are supported at the framework's system level rather than at the application level. This allows various applications (not only Shop-Script 5) to rely on the same shipping plugins existing in the system.

When developing your own shipping plugin, we recommend using plugin FlatRate as a simple example. This plugin allows specifying a fixed shipping rate for all orders and does not contain complex algorithms; however, it is a convenient template, which you can use for development of a new shipping plugin.

The source code of shipping plugins resides inside the wa-plugins/shipping/ directory. Each plugin is stored in a separate subdirectory containing implementation of a class extending base class waShipping. The name of the subdirectory with plugin files is the plugin's identifier, which is used to form the correct name for the class and the file it is contained in. Below is provided an example of the correct class and file names for some plugin with identifier yourshipping:

wa-plugins/shipping/yourshipping/lib/yourshippingShipping.class.php

<?php
 
class yourshippingShipping extends waShipping 
{ 
... 
}

In addition to the main plugin class extending base class waShipping, each plugin also requires configuration files for correct functioning, which, like in applications and plugins, must be placed inside the lib/config/ subdirectory:

plugin.php: contains general plugin description (name, description, path to icon file, path to logo file, version number, and developer name);
settings.php: contains an associative array of parameters required for the plugin functioning (the exact list of settings is defined by the specific algorithm of each plugin). The contents of this array is implicitly included in the plugin class code and is accessible as class fields (e.g., $this->some_setting_id).

The development of a shipping plugin is very similar to that of an application plugin with the exception that, unlike the plugin development, creation of a shipping plugin often requires only implementation of one or several main methods of the base class, listed below.

Methods

In methods of the main plugin class you can access values of plugin settings specified in its configuration file lib/config/settings.php by using private class fields of the form $this->field_name. Instead of field_name specify the id of the settings field whose value you need to obtain.

Methods which must be implemented in every shipping plugin

calculate()

The main method of each shipping method which returns the shipping cost and the approximate shipping time. This method accepts no arguments. Method calculate() must return an associative array containing shipping options available for the current order, each array item representing an individual shipping option. Below is described the structure of a single shipping option element:

'some_variant_id' => array(
    'name' => '', //shipping variant name; e.g., “Ground”, “Air”, “Express Mail”, etc.
    'description' => '', //optional description of the shipping variant
    'est_delivery' => '', //optional string contaning information about the approximate delivery date and time
    'currency' => $this->currency, //ISO3 code of the currency of the calculated shipping cost value
    'rate_min' => $this->cost, //the lower limit if the shipping cost has been calculated approximately
    'rate_max' => $this->cost, //the higher limit if the shipping cost has been calculated approximately
    'rate' => $this->cost, //exact shipping cost value
),

If no shipping vost can be calculated, method calculate() may return an error message instead of an array, for example:

return 'No delivery to the specified address is possible';

calculate() is the main method of any shipping plugin. Development of most shipping plugins can be limited to writing the shipping cost calculation algorithm inside this main method only.

Method calculate() can return additional data attributes for shipping options. Those attributes can be used by plugin’s JavaScript code in app backend in order editing mode.

To add such data attributes, you need to define a sub-array with key 'custom_data' for each item of the array returned by calculate() method. The keys of that sub-array will be used as data attributes’ names for option HTML elements, and their values will be used as data attributes’ values.

Example
array(
    'variant_id_1' => array(
        'name' => '...',
        'est_delivery' => '...',
        'currency' => '...',
        'rate' => '...',
        'custom_data' => array(
            'some-attribute' => 'some data attribute value',
            'another-attribute' => 'another data attribute value',
            '...' => '...',
        ),
    ),
    ...
),

allowedCurrency()

Returns a string or an array with the list of ISO3 codes of the currencies for which the plugin can calculate shipping rates.

allowedWeightUnit()

Returns a string ID, or an array of IDs, of weight units supported by the shipping plugin.

Optional methods which may be implemented if required by plugin's functionality

tracking($tracking_id = null)

This method returns a string (HTML tags are allowed), which may contain information about the current state of the order delivery; e.g., a link to the delivery tracking page of the corresponding online service or the delivery status if this information is available via the shipping service API.

$tracking_id is an optional tracking id received from user. The result returned by this method may be displayed to a user in the order-viewing page.

getPrintForms(waOrder $order = null)

Returns the list of supported printable forms in the form of an associative array as shown below:

array(
    key1 => array(
        'name'        => '...',
        'description' => '...',
    ),
    key2 => array(
        'name'        => '...',
        'description' => '...',
    ),
    ...
)

key is an arbitrary unique (within this shipping plugin) identifier of a printable form;
name — form name;
description — form description.

displayPrintForm($id, waOrder $order, $params = array())

Prints a form by the specified identifier (one of array keys defined in method getPrintForms()). The contents and look of each printable document are defined by the individual template residing in plugin subdirectory templates/. The method must return HTML code of the generated printable form.

allowedAddress()

This method returns an array with the list of countries, regions or also other address parameters, for which the current plugin can calculate shipping rates. The contents of the returned array can be used to verify whether the current customer address is supported by this shipping plugin. The returjned array must have the following structure:

array(
    array(
        'country' => ...,
        'region'  => ...,
    )
)

Each array item may contain either a string (single value), or an array of strings (list of allowed country or regions codes). If you do not need to limit shipping to individual regions of the specified country, then omit the 'region' item.

requestedAddressFields()

Returns array of address form fields which must be requested from customer on the shipping option selection step. The method must return either false (no address fields must be requested), or array() (all fields must be requested), or an array with the structure similar to that shown in the example below:

return array(
     'field_id_1' => ...,
     'field_id_2' => ...,
     ...
 );

Instead of array keys field_id_* specify codes of address fields: 'country', 'region', 'city', 'zip', 'street', or any custom address field, if available. Array items may have the following values:

Configuration files

plugin.php

File plugin.php is plugin's main configuration file used to store basic information: plugin name, description, version, etc. as shown below:

<?php
    
return array(
    'name'        => ...,
    'description' => ...,
    'icon'        => ...,
    'logo'        => ...,
    'vendor'      => ...,
    'version'     => ...,
    'type'        => ...,
    'external'    => ..., //optional parameter
);

Field descriptions

settings.php

File settings.php is used for automatic generation of settings interface for a shipping plugin displayed in the backend of the app utilizing the plugin. Read a detailed description of the plugin settings file.

requirements.php

File requirements.php is used to specify additional system requirements applicable to the functioning of your shipping plugin (e.g., availability of extra PHP extensions or configuration parameters, or installed Webasyst apps). See a detailed description of the system requirements configuration file.

Using custom fields for checkout and order editing modes

A shipping plugin can offer a user its own custom fields, in addition to standard ones, which are defined by app settings. During checkout, a user can specify extra parameters in those custom fields to more precisely calculate the shipping cost. Those custom fields’ values can also be editing in app backend, if the app supports this functionality.

To offer a user custom fields, you need to add public method customFields() to plugin class. The method must return an array containing custom fields configuration data. See an example:

public function customFields(waOrder $order)
{
    if (!$this->getAdapter()->getAppProperties('custom_fields')) {
        //if an app does not support custom fields, do not show them either in backend or in frontend
        return array();
    }
   //checking current environment
    if (wa()->getEnv() === 'backend') {
        //checking if an app supports editing of custin fields’ values in backend
        if (!$this->getAdapter()->getAppProperties('custom_fields_backend')) {
            //do not show custom fields, if an app does not support them
            return array();
        }
    }

    //getting the values of previously completed custom fields
    $shipping_params = $order->shipping_params;
    return array(
        'field1' => array(
            'value'        => $default_field_value, //default value, if necessary
            'title'        => _wp('First field name'),
            'control_type' => waHtmlControl::INPUT,
        ),
        'field2' => array(
            'value'        => $default_field_value, //default value, if necessary
            'title'        => _wp('Second field name'),
            'control_type' => 'MyShippingPluginControl', //custom control ID, if applicable
        ),
        //...
    );
}

For 'control_type' parameters you may specify a custom control key, which can be any string of HTML or JavaScript code.

To implement a custom control, the following is required:

  1. Add a public method to your plugin class to return the desired control HTML code.
  2. At the beginning of customFields() method, add the following call:

    $this->registerControl('MyShippingPluginControl', array($this, 'myShippingPluginControl'));

    In this example, 'MyShippingPluginControl' as the first parameter of the registerControl() method is your custom control key, and 'myShippingPluginControl' is its implementation method name.

Custom control keys and their implementation methods can be named freely.

If a change of a custom field’s value must immediately update the calculated shipping cost, then there must be sub-array 'data' with 'affects-rate' => true element in that field’s configuration.

Example
array(
    'value'            => ifset($shipping_params['some_custom_field']),
    'title'            => _wp('Custom field name'),
    'control_type'     => waHtmlControl::SELECT,
    'description'      => '...',
    'options'          => array(...),
    'data'             => array(
        'affects-rate' => true,
    ),
)

On-the-fly updating of the shipping cost on changing the value of a custom field must be implemented by means of JavaScript logic, which must allow custom fields to interact with shipping plugin’s standard fields. That JavaScript logic must be included in a custom control using method registerControl().

To obtain the value of a custom field, call method getPackageProperty($field) of the system shipping class. The method accepts a string ID of a custom field as a parameter, which is defined in method customFields().

Example
$shipping_params = $this->getPackageProperty('param_name');

Values of completed custom fields are displayednext to their names in app backend on an order-viewing page. Displayed custom field names are retrieved from 'title' element of the custom field configuration returned by method customFields().

Requesting preferred delivery date & time

To allow a user to enable delivery date or time selection options and to set up available time intervals for clients, add a control with key desired_delivery and control type DeliveryIntervalControl to configuration file settings.php as shown below:

'desired_delivery' => array(
    'title'        => 'Preferred delivery time',
    'control_type' => 'DeliveryIntervalControl',
    'minutes'      => true,
),

Parameter 'minutes' denotes the requirement to specify minutes in delivery time intervals in addition to hours.

To display fields for specifying delivery date (with a pop-up calendar) and time intervals (as a dropdown list of options set up in shipping method settings), you have to implement public method customFields() in the main plugin class using the following example, which you can customize to match your individual logic:

public function customFields(waOrder $order)
{
    $fields = parent::customFields($order);

    $this->registerControl('CustomDeliveryIntervalControl');
    $setting = $this->getSettings('customer_interval');

    if (!empty($setting['interval']) || !empty($setting['date'])) {
        if (!strlen($this->delivery_time)) {
            $from = time();
        } else {
            $from = strtotime(preg_replace('@,.+$@', '', $this->delivery_time));
        }
        $offset = max(0, ceil(($from - time()) / (24 * 3600)));
        $fields['desired_delivery'] = array(
            'value'        => null,
            'title'        => $this->_w('Preferred delivery time'),
            'control_type' => 'CustomDeliveryIntervalControl',
            'params'       => array(
                'date'      => empty($setting['date']) ? null : ifempty($offset, 0),
                'interval'  => ifset($setting['interval']),
                'intervals' => ifset($setting['intervals']),
            ),
        );
    }
    return $fields;
}
The capability to request preferred delivery time from a client must be supported by the app your shipping plugin is used with. In case of Shop-Script its version must be 7.2.4.114 or higher.

Interaction of shipping method selection form in frontend with plugin

During input of user data in frontend you may need to call some PHP code of your shipping plugin to update the shipping cost displayed to a user or also other interface elements. This can be achieved by the use of AJAX requests sent to a URL generated using this example:

$url_params = array(
   'action_id' => 'foo', //in this example will be called plugin’s public method named 'fooAction', which must return a complete and valid response to an AJAX request
    'plugin_id' => $this->key,
);
wa()->getRouteUrl(sprintf('%s/frontend/shippingPlugin', $this->app_id), $url_params, true);

Shipment state

A shipping plugin deals with shipments. A shipment is an instance of waOrder class. It may be in one of the following states designated by constants of waShipping class:

Use the following methods of class waShipping to read or change the status of a shipment.

setPackageState (waOrder $order, $state, $params = array())

Sets specified state for a shipment and returns result in one of the following formats:

getStateFields ($state, waOrder $order = null, $params = array())

Returns an array of data submitted via the web form that a user is offered to complete when transferring a shipment to the specified $state.

The values of completed form fields are copied to argument $params['shipping_data'] when method setPackageState() is called.

getAdapter()->getAppProperties ($property_name = null)

Helps detect the shipping plugins support level of an app.

The method returns values from parameter 'shipping_plugins' specified in app’s configuration file app.php. It can be either a Boolean value or an associative array of the following items corresponding to various shipment handling functions:

Tips

Base system class of shipping plugins waShipping provides several useful methods, which you can you in the main plugin class.

Useful methods

getAddress($field = null)

Returns either full array of shipping address data or only the value of specified field code; e.g., 'country', 'region', 'city', 'zip', 'street', or any custom field code, if available.

getItems()

Returns array of ordered items (products).

getTotalPrice()

Returns the total shipping cost.

getTotalWeight()

Returns the total shipping weight.