Версия: 5.x
Инструкция по разработке модулей интеграции с сервисами доставки

Рассмотрим пример создания модуля для вымышленного типа доставки CustomDeliveryType. Структура папок модуля рекомендуется следующая:

  • customdeliverytype //название модуля. Можно использовать любое английское имя
    • config //Здесь должны находиться конфигурационные классы модуля
      • file.inc.php //Конфигурационный файл модуля
      • handlers.inc.php //Файл с обработчиками событий, на которые подписан модуль
      • module.xml //Файл со сведениями о модуле и его настройках по-умолчанию
    • model //Модели модуля
      • deliverytype //Контейнер для классов доставки
        • deliverytype.inc.php //класс типа доставки

Непосредственно функциональность модуля доставки реализована в классе DeliveryType. Класс DeliveryType должен быть унаследован от базового класса Shop::Model::DeliveryType::AbstractType.

Класс, описывающий тип доставки (deliverytype.inc.php)

Рассмотрим методы базового класса Shop::Model::DeliveryType::AbstractType, которые необходимо реализовать/унаследовать в нашем классе DeliveryType.

Основная информация о типе доставки

/**
* Возвращает название расчетного модуля (типа доставки)
*
* @return string
*/
abstract public function getTitle();
/**
* Возвращает описание типа доставки
*
* @return string
*/
abstract public function getDescription();
/**
* Возвращает идентификатор данного типа доставки. (только англ. буквы)
*
* @return string
*/
abstract public function getShortName();

Дополнительная форма для способа доставки

Форма отображается пользователью в окне редактирования способа доставки.

/**
* Возвращает ORM объект для генерации формы в административной панели или null
*
* @return FormObject|void
*/
public function getFormObject()
{}

Пример готовой формы:

public function getFormObject()
{
$properties = new PropertyIterator([
'api_key' => (new Type\Integer())
->setDescription(t('API ключ')),
...
]);
$form_object = new FormObject($properties);
$form_object->setParentObject($this);
$form_object->setParentParamMethod('Form');
return $form_object;
}

Инициализация типа доставки

/**
* Устанавливает настройки типа доставки
* Также, в данном методе могут быть инициализированы параметры связанных вспомогательных объектов
*
* @param array|null $opt
* @return void
*/
public function loadOptions(array $opt = null)
{
$this->opt = $opt;
}

Описание взаимодействия с адресом при оформлении заказа

/**
* Возвращает список полей адреса, которые являются обязательными для заполнения при выборе данного типа доставки
*
* @return string[]
*/
public function getRequiredAddressFields(): array
{
/** @var ShopConfig $config */
$config = ConfigLoader::byModule('shop');
return $config->getRequiredAddressFields();
}
/**
* Возвращает true если при оформлении заказа стоимость доставки можно рассчитать на основе адреса доставки, без указания дополнительных параметров
*
* @param Address $address - адрес
* @return bool
*/
public function canCalculateCostByDeliveryAddress(Address $address): bool
{
return true;
}

Проверка ошибок

/**
* Возвращает ошибки, мешающие выбрать способ доставки в списке доставок
*
* @param Order $order - заказ
* @return string
*/
public function getSelectError(Order $order): string
{
return '';
}
/**
* Возвращает ошибки, мешающе оформить заказ с данным способом доставки
*
* @param Order $order - заказ
* @return string
*/
public function getCheckoutError(Order $order): string
{
return '';
}

Калькуляция стоимости и сроков доставки

Необходимо реализовать метод getDeliveryCost, который должен возвращать стоимость доставки заказа $order по адресу $address с параметрами доставки $delivery.

ReadyScript предлагает дефолтную реализацию определения сроков, которая зависит от указанных в административной панели сроков у регионов доставки. Если ваш способ доставки позволяет автоматически получать срок доставки, то необходимо реализовать всю логику получения сроков доставки в методе calcDeliveryPeriod. Метод должен возвращать объект Shop::Model::DeliveryType::Helper::DeliveryPeriod или null (если срок не удалось определить)

Отрывок из абстрактного класса Shop::Model::DeliveryType::AbstractType

/**
* Возвращает стоимость доставки для заданного заказа.
*
* @param Order $order - объект заказа
* @param Address $address - Адрес доставки
* @param Delivery $delivery - объект доставки
* @param bool $use_currency - конвертировать стоимость в валюту заказа
* @return float
*/
abstract public function getDeliveryCost(Order $order, Address $address = null, Delivery $delivery, $use_currency = true);
/**
* Рассчитывает структурированную информацию по сроку, который требуется для доставки товара по заданному адресу
*
* @param Order $order объект заказа
* @param Address $address объект адреса
* @param Delivery $delivery объект доставки
* @return Helper\DeliveryPeriod | null
*/
protected function calcDeliveryPeriod(Order $order, Address $address = null, Delivery $delivery = null)
{
if (!$delivery) {
$delivery = $order->getDelivery();
}
//Здесь у потомков нужно либо реализовать собственный рассчет сроков доставки,
//либо вернуть заданные у доставки сроки по-умолчанию
if (!empty($delivery['delivery_periods'])) {
//Получим все зоны
$zone_api = new ShopZoneApi();
$zones = $zone_api->getZonesByRegionId($address['region_id'], $address['country_id'], $address['city_id']);
foreach ($delivery['delivery_periods'] as $delivery_period) {
if ($delivery_period['zone'] == 0 || in_array($delivery_period['zone'], $zones)) {
return new Helper\DeliveryPeriod(isset($delivery_period['days_min']) ? $delivery_period['days_min'] : null,
isset($delivery_period['days_max']) ? $delivery_period['days_max'] : null,
isset($delivery_period['text']) ? $delivery_period['text'] : null);
}
}
}
return null;
}

Пункты выдачи заказов

/**
* Возвращает указанный в заказе ПВЗ
*
* @param Order $order - заказ
* @return Pvz|null
*/
public function getSelectedPvz(Order $order): ?Pvz
{
if ($this->hasPvz()) {
$delivery_extra = $order->getExtraKeyPair(Order::EXTRAKEYPAIR_DELIVERY_EXTRA);
if (!empty($delivery_extra[self::EXTRA_KEY_PVZ_DATA])) {
$pvz_data = json_decode(htmlspecialchars_decode($delivery_extra[self::EXTRA_KEY_PVZ_DATA]), true);
if ($pvz_data) {
return Pvz::loadFromArray($pvz_data);
}
}
}
return null;
}
/**
* Возвращает список доступных для указанного адреса ПВЗ
*
* @param Address $address - адрес получателя
* @return Pvz[]
*/
public function getPvzByAddress(Address $address)
{
return [];
}
/**
* Возвращает, поддерживает ли данный способ доставки ПВЗ
*
* @return bool
*/
public function hasPvz(): bool
{
return false;
}

Прочие методы

/**
* Функция срабатывает после создания заказа в БД
*
* @param Order $order - объект заказа
* @param Address $address - Объект адреса
* @return void
*/
public function onOrderCreate(Order $order, Address $address = null)
{}
/**
* Возвращает дополнительный HTML для публичной части
*
* @param Order $order - объект заказа
* @return string
*/
protected function getAdditionalHtml(Order $order): string
{
return '';
}
/**
* Возвращает дополнительный HTML для админ части в заказе
* (устаревший метод, не рекомендуется использовать)
*
* @return string
*/
public function getAdminHTML(Order $order)
{
return "";
}

Регистрация нового типа доставки в системе

Для регистрации в системе нового типа доставки, необходимо обработать событие delivery.gettypes. Согласно формату моделей в ReadyScript, регистрация обработчика событий производится в файле /config/handlers.inc.php.

Пример регистрации нового типа доставки CustomDeliveryType.

<?php
namespace CustomDeliveryType\Config;
/**
* Класс содержит обработчики событий, на которые подписан модуль
*/
class Handlers extends \RS\Event\HandlerAbstract
{
/**
* Добавляет подписку на события
*
* @return void
*/
function init()
{
$this->bind('delivery.gettypes');
}
/**
* Регистрирует в системе тип доставки
*
* @param $list
* @return array
*/
public static function deliveryGetTypes($list)
{
$list[] = new \CustomDeliveryType\Model\DeliveryType\DeliveryType();
return $list;
}
}

Интерфейс работы с заказами на доставку

Чтобы тип доставки поддерживал взаимодействие с заказами на доставку на стороне внешнего сервиса доставки, он должен имплементировать интерфейс Shop::Model::DeliveryType::InterfaceDeliveryOrder и использовать трейт Shop::Model::DeliveryType::TraitInterfaceDeliveryOrder. Трейт создан для упрощения разработки, в нем уже реализованы некоторые методы интерфейса, но при необходимости вы сможете их перегрузить в вашем классе платежной системы.

Основные методы интерфейса

Данные методы обеспечивают базовый функционал интерфейса, их реализация обязательна:

/**
* Создаёт заказ на доставку
*
* @param Order $order - объект заказка
* @return DeliveryOrder
* @throws ShopException
*/
public function createDeliveryOrder(Order $order): DeliveryOrder;
/**
* Удаляет заказ на доставку
*
* @param DeliveryOrder $delivery_order - объект заказка на доставку
* @return void
* @throws ShopException
*/
public function deleteDeliveryOrder(DeliveryOrder $delivery_order): void;
/**
* Обновляет данные заказа на доставку
*
* @param DeliveryOrder $delivery_order - объект заказка на доставку
* @return void
* @throws ShopException
*/
public function refreshDeliveryOrder(DeliveryOrder $delivery_order): void;
/**
* Возвращает список данных заказа на доставку
*
* @param DeliveryOrder $delivery_order - объект заказка на доставку
* @return array
*/
public function getDeliveryOrderDataLines(DeliveryOrder $delivery_order): array;

Для сохранения заказа на доставку в системе RS следует использовать класс Shop::Model::Orm::DeliveryOrder. Ниже приведён список полей объекта Shop::Model::Orm::DeliveryOrder, которые необходимо заполнить перед сохранением:

external_id - (string) Внешний идентификатор заказа на доставку (используется для внешних запросов)
order_id - (int) id заказа на стороне RS
delivery_type - (string) Расчётный класс доставки (получается при помощи вызова метода getShortName() у типа доставки)
number - (string) Номер заказа на доставку
data - (array) Сохранённые произвольные данные
address - (string) Адрес, на который создан заказ на доставку

Дополнительные методы интерфейса

Данные методы могут быть унаследованы для реализации специфических функций, применяемых к созданному заказу на доставку:

/**
* Возвращает список дополнительных действий, доступных для указанного заказа на доставку
* Каждое "действие" в списке имеет следующую структуру:
* [
* 'title' => (string) текст на кнопке
* 'class' => (string) стилизующие css классы (например, 'btn-primary btn-alt')
* 'action' => (string) идентификатор действия который будет передан в метод executeInterfaceDeliveryOrderAction()
* 'attributes' => (array) дополнительные html аттрибуты (например, ['target' => '_blank'])
* ]
*
* @param DeliveryOrder $delivery_order - объект заказка на доставку
* @return array
*/
public function getDeliveryOrderActions(DeliveryOrder $delivery_order): array;
/**
* Исполняет действие интерфейса заказов на доставку
* При успехе - возвращает инструкции для вывода результата, при неудаче - бросает исключение
*
* Инструкция для вывода результата - это массив ключ=>значение, в котором тип отображения результата указывается в ключе 'view_type' (message|form|html|output)
* [
* 'view_type' => 'message' - показ текстового уведомления
* 'message' => (string) текст сообщения
* ];
* [
* 'view_type' => 'form' - отображение формы
* 'title' => (string) заголовок формы
* 'assign' => (array) переменные, которые будут переданы в шаблон
* 'template' => (string) шаблон тела формы
* 'bottom_toolbar' => (\RS\Html\Toolbar\Element) нижняя панель действий формы
* ];
* [
* 'view_type' => 'html' - возврат чистого html
* 'html' => (string) возвращаемый html
* ];
* [
* 'view_type' => 'output' - возврат содержимого без обёртки в ResultStandard (используется для отображения файлов)
* 'content' => (string) текст сообщения
* ];
*
*
* @param HttpRequest $http_request - объект запроса
* @param Order $order - объект заказа
* @param string $action - идентификатор действия
* @return array
* @throws ShopException
*/
public function executeInterfaceDeliveryOrderAction(HttpRequest $http_request, Order $order, string $action): array;