Delivery service connectors add logistics services with the ability to send packages, track delivery, print labels, and extend the order processing form in CRM.
Creating a connector
Working with connectors for delivery services is carried out in the "Delivery Services" section by clicking the "Files" button. The connector files themselves are located in the delivery
folder of your hack. The file name can consist of small Latin letters and numbers. It must begin with a letter, not a number. You cannot use capital letters and special characters, including hyphens or underscores.
When a connector is created, the corresponding class is automatically created in the file. The class name consists of the word delivery
, the name of the hack and the name of the connector, separated by an underscore, for example: delivery_dhl
. The connector must inherit from the class deliverybase
.
class delivery_dhl extends deliverybase { … }
A service class can implement the following fields and functions:
track()
- parcel tracking, the basis of the work of delivery services.calc()
- calculation of the cost and delivery time.send()
- sending a parcel to the delivery service.$cansend
- sending parcels is supported.$canauto
- automatic sending of parcels is supported.bulkprint()
- bulk printing of labels.$canbulk
- bulk label printing is supported.options()
- available delivery service buttons.actions()
- implementation of actions for delivery service buttons.block()
- content block and additional form for delivery service.ajax()
- implementation of actions and data loading for the content block._courier( $id )
- returns the internal name of the courier by ID from thecourier
field for analytics._tariff( $id )
- returns the internal name of the tariff by ID from thetariff
field for analytics.
There is no mandatory implementation, but at least a tracking implementation is recommended.
Connecting to the system
Connectors will not automatically appear in the list of delivery services, you need to specify them through the startup file. To do this, specify the delivery
key in the initialization array, and the list of connectors available in your hack as the value. For example:
return [ /* other initialization lines */ 'delivery' => [ 'cdek', 'dhl', 'ponyexpress' ], ];
Tracking
The main functionality of any delivery service is package tracking. The track
function must take two parameters as input: a track code and an array with order data. As a result, it should return an associative array with the history of statuses, the current status of the package, and the time of the next check.
return [ 'next' => time() + 43000, // Next check time, UNIX timestamp 'status' => $status, // Current parcel status 'time' => $tm, // Status change time, UNIX timesramp 'stage' => $stage // Statuses array ];
The status
field can take one of the following numeric values:
wait
— the package is waiting to be sent.transfer
— the parcel is on the way, the status is "Delivery".problem
— there was a problem with the delivery of the package, the advertiser's response is required.delivered
— the package has arrived at its destination, the status is "Delivered".paid
— the package has been successfully paid, the status is changed to "Paid".return
— the parcel is returned or disposed of, it is transferred to the "Return" status.comment
— arbitrary comment that does not change the state of the send.
The stage array should consist of parcel progress records. Records are recommended to be sorted from oldest to newest. Each entry can contain the following fields:
status
- numeric status identifier.time
- the time the status occurred.country
- two-character ISO code of the country where the status originated.zip
- index or ZIP code of the post office where the status occurred.city
- the city where the status originated.comment
- arbitrary text comment to the status.md5
- unique status hash used to check for duplicates.
Preparing an array of statuses and defining the final status and time might look like this:
$stage = []; $tm = $status = 0; foreach ( $info as $i ) { // Make the stage $s = [ 'status' => $this->s2i[$i['status']], 'time' => $core->text->number( $i['time'] ), 'country' => $core->text->link( $i['country'] ), 'zip' => $core->text->anum( $i['zip'] ), 'city' => $core->text->line( $i['city'] ), 'comment' => $core->text->line( $i['comment'] ), ]; // Add stage to the list $tm = $s['time']; $status = $s['status']; $s['md5'] = md5( $s['status'].$s['time'].$s['country'].$s['zip'].$s['city'].$s['comment'] ); $stage[] = $s; }
It is recommended that you familiarize yourself with the examples of the implementation of the mechanisms for checking the status of parcels that are available in the system. The code for integration modules with delivery services is open. The files are located in the core/delivery
folder of your platform.
Cost and time calculator
The cost and time calculator allows you to substitute the price and cost of delivery in the package. The calc
function receives an array with order data as input. As a result, it should call the calcs
function with four parameters: delivery price, order data, minimum term, maximum term. Only the shipping price is a mandatory parameter.
An example of a simple function implementation:
public function calc( $o ) { $price = $this->somemagic( $o ); return $this->calcs( $price, $o ); }
An example of the implementation of a function with delivery times:
public function calc( $o ) { $price = $this->_price( $o ); $term = $this->_term( $o ); return $this->calcs( $price, $o, $term['min'], $term['max'] ); }
Sending a parcel
Automatic sending of a parcel is performed through the send
function, which takes an array of order data as input and should return true
or false
depending on success.
In the process of work, the function itself must edit the order and enter the track code and other necessary data into it. This is implemented by the $core->lead->edit()
function, specifying the track code in the track
parameter and activating tracking in the trackon
parameter .
$core->lead->edit( $o['order_id'], [ 'track' => $code, 'trackon' => 1 ] );
An example implementation of the send function:
public function send( $o ) { $code = $this->somemagic( $o ); if ( $code ) { $this->core->lead->edit( $o['order_id'], [ 'track' => $code, 'trackon' => 1 ] ); return true; } else return false; }
To activate automatic sending of parcels, specify at the beginning of the class declaration:
public $canauto = true; public $cansend = true;
Print labels
Mass printing of labels is implemented through the bulkprint
function, which takes an array of parcel track codes as input. In this case, the array key is the order ID in the system. The function must return an array of links or paths to files that store the finished labels to print. Automation itself will download the necessary print forms, create an archive from them and send it to the user.
An example of the implementation of the bulk print function:
public function bulkprint( $ids ) { $files = []; foreach ( $ids as $i ) $files[] = $this->_print( $i ); return $files; }
To enable mass printing of labels, specify at the beginning of the class declaration:
public $canprint = true;
Also, it would be good practice to create the print
function, which takes an array of order data as input and returns a link to the file with the label. An example implementation of such a function:
public function print( $o ) { if ( $o['order_status'] > 9 ) return false; if ( $o['order_status'] < 6 ) return false; if ( ! $o['track_code'] ) return false; return $this->_print( $o['track_code'] ); }
Action buttons
Action buttons are added to the right side of the order form under the block with technical data (tags, geo, sources). With these buttons, you can call internal actions of your delivery service, such as sending a package, printing documents of the desired type, or canceling a package.
The options
function is responsible for the list of actions, which takes one parameter as input - an order array. It should return an array that contains the description of the menu items. Each menu item is an array containing fields:
name
- the title of the menu item, for example "Send package".short
- a short title that is displayed in the list of orders, for example "Submit".icon
is the name of the Font Awesome icon that is attached to the menu item, e.g.fa-paper-plane
.confirm
- the text of the action confirmation pop-up window, for example "Are you sure you want to send this package?"url
- link to the action itself.
A link to an action should be formed with the following function:
$core->u([ 'order', $o['order_id'] ], 'action=dlaction&oa=send' )
Where send
will be your action name. An example function might look like this:
public function options( $o ) { if ( $o['order_status'] > 9 ) return false; if ( $o['order_status'] < 6 ) return false; $core = $this->core; if ( $o['track_code'] ) { return [[ 'icon' => 'fa-address-card', 'url' => $core->u([ 'order', $o['order_id'] ], 'action=dlaction&oa=print' ), 'name' => $core->lang['delivery_print'], 'short' => $core->lang['delivery_print_s'], ]]; } elseif ( $this->config['api'] ) { return [[ 'icon' => 'fa-paper-plane', 'url' => $core->u([ 'order', $o['order_id'] ], 'action=dlaction&oa=send' ), 'name' => $core->lang['delivery_send'], 'short' => $core->lang['delivery_send_s'], 'confirm' => $core->lang['delivery_send_c'], ]]; } }
The action
function is responsible for processing actions. It takes two parameters - the name of the action and the order array. The function should process the action and redirect the user to the order page with the action performed or an error.
An example of a function that implements the previous action array:
public function action( $action, $o ) { switch ( $action ) { case 'send': if ( $this->send( $o ) ) { $this->core->msgo( 'ok', $this->core->u([ 'order', $o['order_id'] ]) ); } else $this->core->msgo( 'error', $this->core->u([ 'order', $o['order_id'] ]) ); case 'print': if ( $url = $this->print( $o ) ) { $this->core->go( $url ); } else $this->core->msgo( 'error', $this->core->u([ 'order', $o['order_id'] ]) ); default: $this->core->msgo( 'error', $this->core->u([ 'order', $o['order_id'] ]) ); } }
To handle an error or success, use a smart redirect like this:
$this->core->msgo( 'error', $this->core->u([ 'order', $o['order_id'] ]) );
Instead of 'error'
, 'ok'
can be given if successful.
Forms
The form is added to the order editing page in CRM. It is located below the list of products, above the main data of the order. In this form, you can implement the choice of couriers, delivery methods, pickup points, date and time of delivery, and any other internal parameters specific to delivery.
The block
function is responsible for displaying the form, which takes one parameter as input - an array of order data. The function should return the generated code to insert into the page.
It is recommended to generate the form code using the built-in templating engine. To load a template, use the $core->hack->dhl->tpl( 'innerform', 'shpt-dhl' )
function, where dhl
is replaced by the name your hack, and shpt-dhl
is the name of your template. To generate a template, use $core->tpl->make( 'innerform' )
.
An example of a function implementation:
public function block( $o ) { $core = $this->core; $tm = xxdec( $o['track_meta'] ); $courier = isset( $tm['courier'] ) ? $tm['courier'] : -1; $core->hack->dhl->tpl( 'innerform', 'shpt-dhl' ); $core->tpl->vars( 'innerform', [ 'u_check' => $core->u( [ 'order', $o['order_id'] ], 'action=dlajax&oa=check' ), 'couriers' => json_encode( $this->courier, JSON_PRETTY_PRINT ), ]); foreach ( $this->courier as $i => $n ) $core->tpl->block( 'innerform', 'courier', [ 'i' => $i, 'n' => $n, 's' => $i == $courier ]); return $core->tpl->make( 'innerform' ); }
AJAX Actions
AJAX actions can be used to retrieve data and perform tasks on inline forms. The ajax
function is responsible for handling the actions, which takes the action code and the order array as input. The function must return an array of data, which will be passed in the response as a JSON object.
An example of a function implementation:
public function ajax( $action, $o ) { switch ( $action ) { case 'cities': $area = $this->core->post['area']; return $this->cities( $area ); } }
The link to the function itself must be formed using the code:
$core->u( [ 'order', $o['order_id'] ], 'action=dlajax&oa=cities' )
Where cities
will be the code of the action to be performed.
You can save additional data inside the order that you use in forms and pass through AJAX actions. This data can store identifiers of tariffs, couriers, points of pickup and other internal delivery entities.
It is customary to store delivery data in the track_meta
field of the order data array. The data is retrieved like this:
$tm = xxdec( $o['track_meta'] );
To save shipping metadata, use the following code:
$core->lead->edit( $o['order_id'], [ 'tmeta' => $tm ] );
Note that $tm
must contain all fields at once. If you submit only part of the fields, other pre-existing fields will be removed.
Analytics by tariffs and couriers
The track_meta
array uses two "magic" parameters that allow you to build delivery analytics in terms of domestic rates and couriers. To implement such statistics, add two fields:
tariff
- identifier of the delivery service tariff.courier
- identifier of the delivery service courier.
It is recommended to use numeric identifiers. For the beauty of displaying statistics, you can add the functions _tariff
and _courier
, which take the identifier of the tariff or courier as input and return its beautiful name.
Setting function
To add additional fields to the settings, you need to implement two functions:
form
- returns an array with additional form fields.save
- accepts raw data and returns an array with configuration fields.
The form
function has no parameters. Configuration fields must be accessed via the $this->config
array. The result of the function should be an array of form fields. Each field is an array that can contain the following fields:
type
- field type:text
,email
,textarea
,number
,select
,mselect
,radio
,checkbox
.name
- field name, it is recommended to add a prefix with the module namehead
- field heading, use language file for storage.descr
- description of the field, use the language file for storage.value
- current field value, use$this->config
options
- a set of options for fields likeselect
,mselect
,radio
.checked
- flag for a field of typecheckbox
.
Form implementation example:
public function form() { return [ [ 'type' => 'head', 'value' => $this->core->lang['settings'] ], [ 'type' => 'text', 'name' => 'generic_url', 'head' => $this->core->lang['delivery_url_track'], 'value' => $this->config['url'] ], [ 'type' => 'text', 'name' => 'generic_doc', 'head' => $this->core->lang['delivery_url_doc'], 'value' => $this->config['doc'] ], ]; }
The save
function receives the $data
parameter as input and must return the processed parameter array as a key-value. Process text values with $this->esc()
. Implementation example:
public function save( $data ) { return [ 'url' => str_replace( '&', '&', $this->esc( $data['generic_url'] ) ), 'doc' => str_replace( '&', '&', $this->esc( $data['generic_doc'] ) ), ]; }