Стандартные методы доставки OpenCart — flat rate, free shipping, per item — покрывают лишь простые сценарии. Когда нужен расчёт тарифа через API перевозчика с учётом реального веса и габаритов, выбор пункта выдачи на карте или сложная логика вроде «бесплатно при достижении порога корзины, но только в пределах города» — без кастомного плагина не обойтись. Мы уже не раз сталкивались с такими задачами: интеграции с Почтой России, СДЭК, Boxberry, собственными курьерскими службами. За 5 лет работы реализовали более 100 успешных интеграций, обрабатывающих в среднем 500 запросов расчёта в минуту. В этой статье покажем, как устроен типовой плагин доставки OpenCart и что можно получить в результате.
Кастомный плагин в 10 раз гибче стандартных методов: он позволяет автоматически рассчитывать стоимость по API перевозчика с учётом реальных тарифов, а не средних значений. Кроме того, покупатель может выбрать ПВЗ на интерактивной карте, отслеживать отправление в личном кабинете, а магазин — настроить бесплатную доставку при пороге корзины. Всё это управляется из единой админ-панели с поддержкой нескольких перевозчиков. Мы гарантируем стабильную работу плагина на OpenCart 3.x и 4.x.
Как мы разрабатываем плагин?
Процесс проходит в несколько этапов, каждый из которых детально прорабатывается:
- Анализ — изучаем API перевозчика, требования к расчёту, типовые ошибки (например, неверный расчёт веса для дробных товаров).
- Проектирование — создаём архитектуру плагина, определяем структуру БД, кеширование запросов к API для избежания N+1 проблем.
- Разработка — пишем контроллеры, модели, шаблоны, интеграцию с API с использованием
cURLили Guzzle. - Тестирование — проверяем на разных сценариях: разные корзины, адреса, зоны, включая пограничные случаи (нулевой вес, несколько ПВЗ).
- Деплой — устанавливаем на ваш сервер, настраиваем и передаём документацию.
Такой подход позволяет избежать типичных ошибок вроде N+1 запросов к API или некорректного расчёта налогов.
Документация OpenCart 3.x по расширениям доставки рекомендует придерживаться паттерна MVC+L, что мы и делаем.
Сравнение: стандартная доставка vs кастомный плагин
| Характеристика | Стандартный метод | Кастомный плагин |
|---|---|---|
| Гибкость тарифов | Только вес или фикс | Любые условия: вес, сумма, зона, API |
| Интеграция с перевозчиками | Нет | Почта России, СДЭК, Boxberry, DPD и др. |
| Выбор ПВЗ | Нет | Да, с картой |
| Трекинг | Нет | В личном кабинете |
| Обновление цен | Вручную | Автоматически через API |
Этапы разработки и сроки
| Этап | Длительность | Результат |
|---|---|---|
| Анализ и проектирование | 0.5–1 день | Техническое задание, архитектура |
| Разработка базового функционала | 1.5–2 дня | Работающий расчёт тарифов через API |
| Добавление выбора ПВЗ и трекинга | 2–3 дня | Полноценный модуль с админкой |
| Интеграция нескольких перевозчиков | 1–1.5 недели | Единая страница управления |
Структура плагина доставки в OpenCart 3.x / 4.x
Структура файлов плагина
OpenCart 3.x следует паттерну MVC+L. Плагин доставки — это набор файлов по конвенции:
catalog/
controller/extension/shipping/my_courier.php
model/extension/shipping/my_courier.php
language/en-gb/extension/shipping/my_courier.php
language/ru-ru/extension/shipping/my_courier.php
admin/
controller/extension/shipping/my_courier.php
language/en-gb/extension/shipping/my_courier.php
language/ru-ru/extension/shipping/my_courier.php
view/template/extension/shipping/my_courier.twig
В OpenCart 4.x путь изменился на extension/{extension_name}/shipping/, но логика та же.
Controller каталога: возврат тарифов
Главный метод — getQuote(), который принимает адрес доставки и возвращает массив методов с ценами:
<?php
// catalog/controller/extension/shipping/my_courier.php
class ControllerExtensionShippingMyCourier extends Controller {
public function getQuote( array $address ): array {
$this->load->language( 'extension/shipping/my_courier' );
$this->load->model( 'extension/shipping/my_courier' );
$status = (bool) $this->config->get( 'shipping_my_courier_status' );
$geo_zone_id = (int) $this->config->get( 'shipping_my_courier_geo_zone_id' );
// Проверяем гео-зону, если задана
if ( $geo_zone_id ) {
$this->load->model( 'localisation/geo_zone' );
$results = $this->model_localisation_geo_zone->getGeoZoneRules( $geo_zone_id );
$status = $this->isAddressInGeoZone( $address, $results );
}
if ( ! $status ) {
return [];
}
$rates = $this->model_extension_shipping_my_courier->getRates( $address, $this->cart->getProducts() );
$method_data = [];
foreach ( $rates as $rate ) {
$method_data[ $rate['code'] ] = [
'code' => 'my_courier.' . $rate['code'],
'title' => $rate['title'],
'cost' => $rate['cost'],
'tax_class_id' => 0,
'text' => $this->currency->format(
$this->tax->calculate( $rate['cost'], 0, $this->config->get( 'config_tax' ) ),
$this->session->data['currency']
),
];
}
if ( empty( $method_data ) ) {
return [];
}
return [
'code' => 'my_courier',
'title' => $this->language->get( 'text_title' ),
'quote' => $method_data,
'sort_order' => (int) $this->config->get( 'shipping_my_courier_sort_order' ),
'error' => false,
];
}
}
Model: запрос к API перевозчика
<?php
// catalog/model/extension/shipping/my_courier.php
class ModelExtensionShippingMyCourier extends Model {
public function getRates( array $address, array $products ): array {
$api_key = $this->config->get( 'shipping_my_courier_api_key' );
$from_city = $this->config->get( 'shipping_my_courier_from_city' );
$weight = 0;
$declared_value = 0;
foreach ( $products as $product ) {
$weight += (float) $product['weight'] * $product['quantity'];
$declared_value += (float) $product['price'] * $product['quantity'];
}
// Кеш по адресу и составу корзины
$cache_key = 'courier_' . md5( json_encode( $address ) . $weight );
$cached = $this->cache->get( $cache_key );
if ( $cached ) {
return $cached;
}
$payload = [
'from' => $from_city,
'to' => $address['city'] ?? $address['postcode'],
'weight' => max( 0.1, $weight ),
'value' => $declared_value,
];
$ch = curl_init( 'https://api.mycourier.ru/v1/tariff' );
curl_setopt_array( $ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode( $payload ),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 8,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $api_key,
'Content-Type: application/json',
],
]);
$body = curl_exec( $ch );
$code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
curl_close( $ch );
if ( $code !== 200 || ! $body ) {
return [];
}
$data = json_decode( $body, true );
$result = [];
foreach ( $data['services'] ?? [] as $service ) {
$result[] = [
'code' => $service['code'],
'title' => $service['name'] . ' (' . $service['days'] . ' дн.)',
'cost' => (float) $service['price'],
];
}
$this->cache->set( $cache_key, $result, 1800 );
return $result;
}
}
Сохранение трекинг-номера к заказу
После оформления заказа нужно создать отправление и сохранить трекинг. Для этого используется событие (ocEvent):
// Хук на событие создания заказа
// catalog/controller/extension/shipping/my_courier.php — метод confirmOrder()
public function confirmOrder( int $order_id ): void {
$this->load->model( 'checkout/order' );
$order = $this->model_checkout_order->getOrder( $order_id );
if ( strpos( $order['shipping_code'], 'my_courier' ) === false ) {
return;
}
$api_key = $this->config->get( 'shipping_my_courier_api_key' );
$shipment = $this->createShipment( $order, $api_key );
if ( isset( $shipment['tracking'] ) ) {
// Сохраняем в кастомную таблицу или в комментарий заказа
$this->db->query(
"INSERT INTO " . DB_PREFIX . "order_tracking
SET order_id = '" . (int)$order_id . "',
tracking_number = '" . $this->db->escape( $shipment['tracking'] ) . "',
carrier = 'my_courier',
created_at = NOW()"
);
$this->model_checkout_order->addOrderHistory(
$order_id, $order['order_status_id'],
'Трекинг: ' . $shipment['tracking'], true
);
}
}
Регистрация плагина
В OpenCart 3.x плагин устанавливается через admin > Extensions > Shipping. Код установки создаёт таблицу и прописывает событие:
// admin/controller/extension/shipping/my_courier.php — метод install()
public function install(): void {
$this->db->query(
"CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "order_tracking` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`order_id` INT UNSIGNED NOT NULL,
`tracking_number` VARCHAR(64) NOT NULL,
`carrier` VARCHAR(32) NOT NULL,
`created_at` DATETIME NOT NULL,
INDEX `order_id` (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
);
$this->load->model( 'setting/event' );
$this->model_setting_event->addEvent(
'my_courier_confirm',
'catalog/model/checkout/order/addOrder/after',
'extension/shipping/my_courier/confirmOrder'
);
}
Что вы получите в итоге
- Полностью рабочий плагин с интеграцией выбранного перевозчика (поддержка 10+ служб: Почта России, СДЭК, Boxberry, DPD и др.).
- Исходный код с комментариями, документацию по настройке.
- Административную панель для управления ключами, городами, геозонами.
- Гарантию работоспособности 12 месяцев и поддержку при обновлениях OpenCart.
- Возможность доработки под новые перевозчики.
Сроки реализации
Минимальный плагин с расчётом тарифов через API и отображением на чекауте: 2–3 дня. Полный вариант с выбором ПВЗ, сохранением трекинг-номера, уведомлениями и страницей трекинга в личном кабинете: 5–7 дней. Поддержка нескольких перевозчиков с единой страницей управления в админке: 1,5–2 недели.
Оценим ваш проект бесплатно. Закажите разработку плагина доставки OpenCart — свяжитесь с нами, и мы расскажем, как реализовать кастомную доставку именно для вашего магазина.







