Розробка кастомного плагіна доставки OpenCart

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.
Розробка та обслуговування будь-яких видів сайтів:
Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Розробка кастомного плагіна доставки OpenCart
Середня
~3-5 робочих днів
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Розробка користувацького плагіна доставки для OpenCart

Стандартні методи доставки OpenCart — flat rate, free shipping, per item — охоплюють базові випадки. Коли потрібен розрахунок тарифу через API перевізника, вибір ПВЗ або логіка на зразок «безплатно від 3000 рублів, але лише в межах міста» — пишеться користувацький плагін.

Структура плагіна доставки в 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/, але логіка та сама.

Контролер каталогу: повернення тарифів

Головний метод — 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,
        ];
    }
}

Модель: запит до 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;
    }
}

Адміністрація: форма налаштувань

Форма в Twig з полями для ключа API, міста відправлення та гео-зони:

{# admin/view/template/extension/shipping/my_courier.twig #}
<div class="panel-body">
  <div class="form-group required">
    <label class="col-sm-2 control-label">API-ключ</label>
    <div class="col-sm-6">
      <input type="text" name="shipping_my_courier_api_key"
             value="{{ shipping_my_courier_api_key }}" class="form-control"/>
    </div>
  </div>
  <div class="form-group required">
    <label class="col-sm-2 control-label">Місто відправлення</label>
    <div class="col-sm-6">
      <input type="text" name="shipping_my_courier_from_city"
             value="{{ shipping_my_courier_from_city }}" class="form-control"/>
    </div>
  </div>
  <div class="form-group">
    <label class="col-sm-2 control-label">Гео-зона</label>
    <div class="col-sm-6">
      <select name="shipping_my_courier_geo_zone_id" class="form-control">
        <option value="0">Усі зони</option>
        {% for geo_zone in geo_zones %}
          <option value="{{ geo_zone.geo_zone_id }}"
            {% if geo_zone.geo_zone_id == shipping_my_courier_geo_zone_id %}selected{% endif %}>
            {{ geo_zone.name }}
          </option>
        {% endfor %}
      </select>
    </div>
  </div>
</div>

Збереження трекінг-номера до замовлення

Після розміщення замовлення потрібно створити відправлення та зберегти трекінг-номер:

// Гачок на подію створення замовлення
// 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'
    );
}

Строки реалізації

Мінімальний плагін з розрахунком тарифів через API та відображенням на чекауті: 2–3 дня. Повна версія з вибором ПВЗ, збереженням трекінг-номера, повідомленнями та сторінкою трекінгу в особистому кабінеті: 5–7 днів. Підтримка кількох перевізників з єдиною сторінкою управління в адміністрації: 1,5–2 тижня.