🛒 OpenCart 4.x • Полный гид 2025

Интернет-магазин на OpenCart

Пошаговое руководство по установке, настройке и интеграции всех необходимых модулей для работы магазина в России: оплата, доставка, онлайн-касса, Честный знак.

💳
ЮKassa, Робокасса, T-Pay
📦
СДЭК, Яндекс, Почта России
🧾
АТОЛ, Честный знак
Оптимизация под нагрузку
🏗️

Архитектура решения

Общая схема интеграций интернет-магазина

👤 Покупатель
Браузер / Мобильное
🛒 OpenCart 4.x
PHP 8.1+ / MySQL 8.0
💳 Оплата
ЮKassa / Робокасса / T-Pay
📦 Доставка
СДЭК / Яндекс / ПР
🧾 Касса
АТОЛ Онлайн
✅ Честный знак
API маркировки
📊 1С / МойСклад
Учёт товаров
📧 Email / SMS
Уведомления
01

Системные требования

Что нужно для установки и стабильной работы OpenCart

🖥️

Минимальные требования

Для небольшого магазина до 1000 товаров

  • PHP 8.1 или выше
  • MySQL 5.7+ / MariaDB 10.3+
  • Apache 2.4+ или Nginx
  • RAM: 2 GB минимум
  • Диск: 10 GB SSD
  • SSL-сертификат (Let's Encrypt)
🚀

Рекомендуемые требования

Для магазина от 5000+ товаров

  • PHP 8.2+ с OPcache
  • MySQL 8.0 / MariaDB 10.6+
  • Nginx + PHP-FPM
  • RAM: 4-8 GB
  • Диск: 50+ GB NVMe SSD
  • Redis для кэширования

Необходимые PHP-расширения

php.ini
# Обязательные расширения для OpenCart 4.x

extension=curl
extension=gd
extension=mbstring
extension=mysqli
extension=openssl
extension=zip
extension=zlib
extension=xml
extension=intl

# Рекомендуемые настройки
memory_limit = 256M
upload_max_filesize = 50M
post_max_size = 50M
max_execution_time = 300
max_input_vars = 5000

# OPcache для производительности
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=10000
opcache.validate_timestamps=0
💡 Рекомендация по хостингу

Для российского магазина выбирайте хостинг с серверами в РФ: Timeweb, REG.ru, Beget. Это обеспечит быструю загрузку и соответствие 152-ФЗ о персональных данных.

02

Установка OpenCart

Пошаговая инструкция по установке на сервер

1
Скачайте OpenCart

Загрузите последнюю версию с официального сайта или GitHub:

Terminal
# Скачиваем OpenCart 4.0.2.3
cd /var/www/html
wget https://github.com/opencart/opencart/releases/download/4.0.2.3/opencart-4.0.2.3.zip
unzip opencart-4.0.2.3.zip
mv opencart-4.0.2.3/upload/* ./
rm -rf opencart-4.0.2.3 opencart-4.0.2.3.zip
2
Создайте базу данных
MySQL
-- Создаём базу данных и пользователя
CREATE DATABASE opencart_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'opencart_user'@'localhost' IDENTIFIED BY 'Secure_Password_123!';
GRANT ALL PRIVILEGES ON opencart_db.* TO 'opencart_user'@'localhost';
FLUSH PRIVILEGES;
3
Установите права доступа
Terminal
# Права на файлы и папки
chown -R www-data:www-data /var/www/html
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;

# Создаём конфиг файлы
cp config-dist.php config.php
cp admin/config-dist.php admin/config.php
chmod 777 config.php admin/config.php

# Права на папки для записи
chmod -R 777 image/
chmod -R 777 image/cache/
chmod -R 777 image/catalog/
chmod -R 777 system/storage/
4
Запустите установщик

Откройте в браузере: https://your-domain.ru/install/

Следуйте инструкциям мастера установки:

  • Проверка требований системы
  • Ввод данных базы данных
  • Создание администратора
  • Завершение установки
5
Удалите папку install
Terminal
# ВАЖНО! Удалите после установки
rm -rf /var/www/html/install/

# Верните права на конфиг файлы
chmod 644 config.php admin/config.php
ℹ️ Ручная установка через CLI

Для автоматизации развертывания можно использовать CLI-установку.

Terminal
# CLI установка OpenCart
php install/cli_install.php install \
    --db_hostname localhost \
    --db_username opencart_user \
    --db_password Secure_Password_123! \
    --db_database opencart_db \
    --db_driver mysqli \
    --db_port 3306 \
    --username admin \
    --password admin_password \
    --email admin@yoursite.ru \
    --http_server https://yoursite.ru/
💡 Docker для разработки

Docker идеален для локальной разработки и тестирования. Для продакшена рекомендуем традиционную установку.

docker-compose.yml
version: '3.8'

services:
  opencart:
    image: bitnami/opencart:4
    container_name: opencart
    ports:
      - "80:8080"
      - "443:8443"
    environment:
      - OPENCART_HOST=localhost
      - OPENCART_DATABASE_HOST=mariadb
      - OPENCART_DATABASE_PORT_NUMBER=3306
      - OPENCART_DATABASE_USER=opencart_user
      - OPENCART_DATABASE_PASSWORD=opencart_pass
      - OPENCART_DATABASE_NAME=opencart_db
      - OPENCART_USERNAME=admin
      - OPENCART_PASSWORD=admin_password
      - OPENCART_EMAIL=admin@yoursite.ru
    volumes:
      - 'opencart_data:/bitnami/opencart'
    depends_on:
      - mariadb

  mariadb:
    image: mariadb:10.6
    container_name: opencart_db
    environment:
      - MYSQL_ROOT_PASSWORD=root_password
      - MYSQL_DATABASE=opencart_db
      - MYSQL_USER=opencart_user
      - MYSQL_PASSWORD=opencart_pass
    volumes:
      - 'mariadb_data:/var/lib/mysql'

  redis:
    image: redis:7-alpine
    container_name: opencart_redis

volumes:
  opencart_data:
  mariadb_data:
Terminal
# Запуск контейнеров
docker-compose up -d

# Проверка статуса
docker-compose ps

# Логи
docker-compose logs -f opencart

Конфигурация Nginx

/etc/nginx/sites-available/opencart.conf
server {
    listen 80;
    server_name yoursite.ru www.yoursite.ru;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name yoursite.ru www.yoursite.ru;

    root /var/www/html;
    index index.php index.html;

    # SSL сертификаты (Let's Encrypt)
    ssl_certificate /etc/letsencrypt/live/yoursite.ru/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yoursite.ru/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

    # Безопасность
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Gzip сжатие
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
    gzip_min_length 1000;

    # Основной location
    location / {
        try_files $uri $uri/ @opencart;
    }

    location @opencart {
        rewrite ^/(.+)$ /index.php?_route_=$1 last;
    }

    # Обработка PHP
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_buffer_size 128k;
        fastcgi_buffers 256 16k;
    }

    # Статика - кэширование
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Запрет доступа к служебным файлам
    location ~ /\.(htaccess|git|env) {
        deny all;
    }

    location ~ /(system|admin/controller|admin/model|admin/view|catalog/controller|catalog/model) {
        deny all;
    }
}
03

Базовая настройка магазина

Конфигурация магазина, валюты, языки, налоги

🏪

Настройки магазина

Система → Настройки → Магазин

  • Название: Ваш магазин
  • Владелец: ООО/ИП название
  • Адрес: Юридический адрес
  • Email: info@yoursite.ru
  • Телефон: +7 (XXX) XXX-XX-XX
  • Формат изображений: WebP
💰

Локализация

Система → Локализация

  • Страна: Россия
  • Регион: Ваш регион
  • Язык: Русский
  • Валюта: Российский рубль (₽)
  • Единицы измерения: Метрическая
  • Формат даты: d.m.Y

Настройка валюты

Админка: Система → Локализация → Валюты
Настройки для Российского рубля:

Название:        Российский рубль
Код:             RUB
Символ слева:    (оставить пустым)
Символ справа:Десятичных:      2
Курс:            1.00000000
Статус:          Включено
По умолчанию:    Да

Настройка налогов (НДС)

⚠️ Важно для ИП и ООО

Настройка налогов зависит от вашей системы налогообложения (ОСНО, УСН, ПСН). При УСН без НДС выбирайте ставку НДС 0% или "Без НДС".

Налоговый класс Ставка Применение
НДС 20% 20% Стандартные товары (ОСНО)
НДС 10% 10% Продукты, детские товары, книги
НДС 0% 0% Экспорт, УСН
Без НДС УСН, ПСН (не плательщики НДС)
SQL: Добавление налоговых ставок
-- Добавляем налоговые ставки для России

-- Налоговый класс НДС 20%
INSERT INTO `oc_tax_class` (`tax_class_id`, `title`, `description`) 
VALUES (1, 'НДС 20%', 'Стандартная ставка НДС');

-- Налоговая ставка
INSERT INTO `oc_tax_rate` (`tax_rate_id`, `geo_zone_id`, `name`, `rate`, `type`) 
VALUES (1, 0, 'НДС 20%', 20.0000, 'P');

-- Связь класса и ставки
INSERT INTO `oc_tax_rule` (`tax_class_id`, `tax_rate_id`, `based`, `priority`) 
VALUES (1, 1, 'shipping', 1);

Статусы заказов

1 Ожидание
2 В обработке
3 Оплачен
4 Доставляется
5 Выполнен
6 Отменён
7 Возврат
8 Чек отправлен
04

Управление товарами

Структура каталога, категории, атрибуты, импорт

Структура каталога

📁 Каталог
📁 Категории
├── 📂 Электроника
├── 📂 Смартфоны
├── 📂 Ноутбуки
└── 📂 Аксессуары
├── 📂 Одежда
├── 📂 Мужская
└── 📂 Женская
📁 Товары
📁 Атрибуты
📁 Опции
📁 Производители

Создание товара

📦

Основные данные

Вкладка "Основное"

  • Название: Название товара
  • Мета-тег Title: SEO заголовок
  • Мета-тег Description: SEO описание
  • Тег товара: Ключевые слова
  • Описание: Полное описание
💵

Данные товара

Вкладка "Данные"

  • Модель: Артикул / SKU
  • Цена: Розничная цена
  • Количество: Остаток на складе
  • Статус: Есть в наличии
  • Налоговый класс: НДС 20%

Важные поля для маркировки (Честный знак)

⚠️ Обязательно для маркированных товаров

Для товаров, подлежащих маркировке (одежда, обувь, парфюмерия, шины и др.), необходимо хранить коды маркировки Data Matrix.

SQL: Добавление полей для маркировки
-- Добавляем поля для маркировки в таблицу товаров

ALTER TABLE `oc_product` 
ADD COLUMN `gtin` VARCHAR(14) DEFAULT NULL COMMENT 'GTIN/EAN код',
ADD COLUMN `marking_type` VARCHAR(50) DEFAULT NULL COMMENT 'Тип маркировки',
ADD COLUMN `tnved_code` VARCHAR(20) DEFAULT NULL COMMENT 'Код ТН ВЭД';

-- Таблица для хранения кодов маркировки (Data Matrix)
CREATE TABLE `oc_product_marking` (
    `marking_id` INT(11) NOT NULL AUTO_INCREMENT,
    `product_id` INT(11) NOT NULL,
    `order_id` INT(11) DEFAULT NULL,
    `marking_code` VARCHAR(150) NOT NULL COMMENT 'Код Data Matrix',
    `status` ENUM('available', 'reserved', 'sold', 'returned') DEFAULT 'available',
    `date_added` DATETIME NOT NULL,
    `date_sold` DATETIME DEFAULT NULL,
    PRIMARY KEY (`marking_id`),
    KEY `product_id` (`product_id`),
    KEY `order_id` (`order_id`),
    UNIQUE KEY `marking_code` (`marking_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Массовый импорт товаров

Формат CSV файла для импорта товаров:

products_import.csv
product_id;name;model;sku;price;quantity;category;image;description;gtin;weight;status
;Смартфон iPhone 15;IPHONE15;APL-IP15-128;89990;50;Электроника > Смартфоны;iphone15.jpg;Описание товара;4680001234567;0.171;1
;Ноутбук MacBook Air;MBA-M2;APL-MBA-M2;119990;25;Электроника > Ноутбуки;macbook.jpg;Описание ноутбука;4680009876543;1.24;1
;Футболка мужская;TSHIRT-M-BLK;TSH-001;1990;100;Одежда > Мужская;tshirt.jpg;Хлопок 100%;4680005551234;0.15;1
💡 Рекомендуемые модули импорта

Import/Export Tool: Бесплатный базовый импорт
CSV Price Pro: Расширенный импорт с обновлением цен (~2000₽)
Import PRO: Профессиональный импорт с CRON (~5000₽)

Интеграция с 1С через CommerceML:

Настройка 1С обмена
Настройки модуля обмена с 1С:

URL обмена:       https://yoursite.ru/index.php?route=extension/module/exchange1c
Логин:            admin
Пароль:           ваш_пароль
Формат:           CommerceML 2.10
Кодировка:        UTF-8

Параметры синхронизации:
Импорт товаров:  Да
Импорт цен:      Да
Импорт остатков: Да
Экспорт заказов: Да
Интервал:        Каждые 30 минут

Импорт товаров через REST API:

PHP: Импорт через API
<?php
// Импорт товара через OpenCart API

$api_url = 'https://yoursite.ru/index.php?route=api/product/add';
$api_token = 'your_api_token';

$product_data = [
    'name' => 'Новый товар',
    'model' => 'MODEL-001',
    'sku' => 'SKU-001',
    'price' => 9990,
    'quantity' => 100,
    'status' => 1,
    'category_id' => [25],
    'description' => 'Описание товара',
    'meta_title' => 'Новый товар - купить',
    'gtin' => '4680001234567',
    'weight' => 0.5,
    'tax_class_id' => 1
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $api_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($product_data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer ' . $api_token
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);
print_r($result);
05

Модули оплаты

Интеграция ЮKassa, Робокасса, T-Pay

💳

ЮKassa

Яндекс.Касса

Популярная платёжная система. Карты, СБП, электронные кошельки.

  • Комиссия от 2.8%
  • СБП (0% комиссии)
  • Возвраты онлайн
  • Официальный модуль
💰

Робокасса

Robokassa

Множество способов оплаты. Подходит для ИП и самозанятых.

  • Комиссия от 2.3%
  • 40+ способов оплаты
  • Быстрое подключение
  • Работа с самозанятыми

T-Pay

Тинькофф

Интернет-эквайринг от Тинькофф. Быстрое подключение.

  • Комиссия от 2.49%
  • Выплаты на следующий день
  • Рекуррентные платежи
  • API и виджеты

🔧 Настройка ЮKassa

1
Регистрация в ЮKassa

Зарегистрируйтесь на yookassa.ru и получите:

  • shopId — идентификатор магазина
  • Секретный ключ API
2
Установка модуля

Скачайте официальный модуль с GitHub или маркетплейса OpenCart.

Terminal
# Клонируем модуль ЮKassa
cd /var/www/html
git clone https://github.com/yoomoney/yookassa-cms-opencart.git
cp -r yookassa-cms-opencart/src/upload/* ./

# Или установка через админку:
# Расширения → Установка расширений → Загрузить файл .ocmod.zip
3
Конфигурация модуля
Настройки модуля ЮKassa
Расширения → Оплата → ЮKassa

Shop ID:              123456
Секретный ключ:       live_xxxxxxxxxxxxxxxxxxxxx
Режим работы:         Боевой (или Тестовый)

--- Способы оплаты ---
Банковские карты:     Включено
ЮMoney:               Включено
СБП:                  Включено
Кредит/Рассрочка:    По желанию

--- Фискализация ---
Отправка чеков:       Включено
Система налогообложения: УСН доходы (или ваша)
НДС по умолчанию:    Без НДС

--- Уведомления ---
URL для уведомлений:  https://yoursite.ru/index.php?route=extension/payment/yookassa/callback
Статус после оплаты: Оплачен

🔧 Настройка Робокасса

Настройки Робокасса
Расширения → Оплата → Робокасса

Логин магазина:       your_shop_login
Пароль #1:            password1_for_requests
Пароль #2:            password2_for_callback

--- В личном кабинете Робокассы ---
Result URL:  https://yoursite.ru/index.php?route=extension/payment/robokassa/callback
Success URL: https://yoursite.ru/index.php?route=extension/payment/robokassa/success
Fail URL:    https://yoursite.ru/index.php?route=extension/payment/robokassa/fail

Метод отправки:       POST
Алгоритм хеширования: SHA256

--- Фискализация (если нужна) ---
Отправка чеков:       Включено
СНО:                  usn_income (УСН доходы)

🔧 Настройка T-Pay (Тинькофф)

PHP: Интеграция T-Pay
<?php
// catalog/controller/extension/payment/tinkoff.php

namespace Opencart\Catalog\Controller\Extension\Payment;

class Tinkoff extends \Opencart\System\Engine\Controller {

    private $terminal_key;
    private $secret_key;
    private $api_url = 'https://securepay.tinkoff.ru/v2/';

    public function index(): string {
        $this->load->language('extension/payment/tinkoff');

        $data['action'] = $this->url->link('extension/payment/tinkoff|confirm');

        return $this->load->view('extension/payment/tinkoff', $data);
    }

    public function confirm(): void {
        $this->load->model('checkout/order');

        $order_info = $this->model_checkout_order->getOrder(
            $this->session->data['order_id']
        );

        $params = [
            'TerminalKey' => $this->config->get('payment_tinkoff_terminal_key'),
            'Amount'      => round($order_info['total'] * 100),
            'OrderId'     => $order_info['order_id'],
            'Description' => 'Заказ #' . $order_info['order_id'],
            'DATA'        => [
                'Email' => $order_info['email'],
                'Phone' => $order_info['telephone']
            ],
            'Receipt'     => $this->getReceipt($order_info)
        ];

        $params['Token'] = $this->generateToken($params);

        $response = $this->sendRequest('Init', $params);

        if ($response['Success']) {
            $this->response->redirect($response['PaymentURL']);
        }
    }

    private function getReceipt($order_info): array {
        // Формируем чек для фискализации
        $items = [];

        foreach ($this->cart->getProducts() as $product) {
            $items[] = [
                'Name'     => $product['name'],
                'Price'    => round($product['price'] * 100),
                'Quantity' => $product['quantity'],
                'Amount'   => round($product['total'] * 100),
                'Tax'      => 'none' // или 'vat20', 'vat10'
            ];
        }

        return [
            'Email'    => $order_info['email'],
            'Taxation' => 'usn_income', // СНО
            'Items'    => $items
        ];
    }
}
💡 Сравнение платёжных систем
Параметр ЮKassa Робокасса T-Pay
Комиссия (карты) от 2.8% от 2.3% от 2.49%
СБП 0% 0.4% 0.4%
Вывод средств 1-2 дня 1-3 дня 1 день
Фискализация
Рекурренты
Модуль OpenCart Официальный Официальный Неофициальный
Самозанятые
06

Модули доставки

СДЭК, Яндекс Доставка, Почта России

📦

СДЭК

Курьерская доставка

  • Расчёт стоимости по API
  • Выбор ПВЗ на карте
  • Курьерская доставка
  • Постаматы
  • Отслеживание
🚕

Яндекс Доставка

Экспресс и стандарт

  • Экспресс (от 15 мин)
  • Доставка в тот же день
  • Интеграция с Яндекс.Картами
  • Отслеживание курьера
📮

Почта России

По всей России

  • Доставка в любую точку РФ
  • Наложенный платёж
  • API расчёта тарифов
  • Печать бланков

🔧 Настройка СДЭК

1
Получение API ключей

Зарегистрируйтесь в личном кабинете СДЭК и получите:

  • Account (идентификатор клиента)
  • Secure password (секретный ключ)
2
Установка модуля
Настройки модуля СДЭК
Расширения → Доставка → СДЭК

Account:              your_account_id
Secure password:      your_secure_password
Тестовый режим:       Нет (для боевого)

--- Адрес отправки ---
Город отправления:   Москва
Код города:          44 (СДЭК код)
Индекс:              101000
Адрес склада:        ул. Примерная, д. 1

--- Тарифы ---
Склад-Склад (ПВЗ):   Включено (тариф 136)
Склад-Дверь:         Включено (тариф 137)
Дверь-Дверь:         Включено (тариф 139)

--- Параметры по умолчанию ---
Вес по умолчанию:    0.5 кг
Габариты:            20x15x10 см
Наценка:             0% или фикс. сумма
PHP: API СДЭК — расчёт стоимости
<?php
// catalog/model/extension/shipping/cdek.php

namespace Opencart\Catalog\Model\Extension\Shipping;

class Cdek extends \Opencart\System\Engine\Model {

    private $api_url = 'https://api.cdek.ru/v2/';
    private $token;

    public function getQuote($address): array {
        $this->authenticate();

        $method_data = [];

        if (!$this->token) {
            return $method_data;
        }

        // Параметры расчёта
        $params = [
            'from_location' => [
                'code' => $this->config->get('shipping_cdek_city_code')
            ],
            'to_location' => [
                'postal_code' => $address['postcode']
            ],
            'packages' => [
                [
                    'weight' => $this->getCartWeight(),
                    'length' => 20,
                    'width'  => 15,
                    'height' => 10
                ]
            ]
        ];

        // Тарифы для расчёта
        $tariffs = [
            136 => 'СДЭК ПВЗ',      // Склад-Склад
            137 => 'СДЭК Курьер',   // Склад-Дверь
            234 => 'СДЭК Постамат'  // Склад-Постамат
        ];

        foreach ($tariffs as $tariff_code => $tariff_name) {
            $params['tariff_code'] = $tariff_code;

            $result = $this->apiRequest('calculator/tariff', $params);

            if (isset($result['total_sum'])) {
                $quote_data['cdek_' . $tariff_code] = [
                    'code'         => 'cdek.cdek_' . $tariff_code,
                    'name'         => $tariff_name . ' (' . $result['period_min'] . '-' . $result['period_max'] . ' дней)',
                    'cost'         => $result['total_sum'],
                    'tax_class_id' => 0
                ];
            }
        }

        if (!empty($quote_data)) {
            $method_data = [
                'code'       => 'cdek',
                'name'       => 'СДЭК',
                'quote'      => $quote_data,
                'sort_order' => $this->config->get('shipping_cdek_sort_order'),
                'error'      => false
            ];
        }

        return $method_data;
    }

    private function authenticate(): void {
        $auth_data = [
            'grant_type'    => 'client_credentials',
            'client_id'     => $this->config->get('shipping_cdek_account'),
            'client_secret' => $this->config->get('shipping_cdek_password')
        ];

        $ch = curl_init($this->api_url . 'oauth/token');
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($auth_data));
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $response = json_decode(curl_exec($ch), true);
        curl_close($ch);

        $this->token = $response['access_token'] ?? null;
    }

    private function apiRequest($endpoint, $data): array {
        $ch = curl_init($this->api_url . $endpoint);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Authorization: Bearer ' . $this->token
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $response = json_decode(curl_exec($ch), true);
        curl_close($ch);

        return $response ?? [];
    }
}

🔧 Настройка Почты России

PHP: API Почты России
<?php
// Расчёт стоимости через API Почты России

class RussianPostCalculator {

    private $api_url = 'https://otpravka-api.pochta.ru/1.0/';
    private $token;
    private $login;
    private $password;

    public function __construct($login, $password, $token) {
        $this->login = $login;
        $this->password = $password;
        $this->token = $token;
    }

    public function calculate($params): array {
        // Типы отправлений
        $mail_types = [
            'POSTAL_PARCEL'     => 'Посылка',
            'ONLINE_PARCEL'     => 'Посылка онлайн',
            'EMS'               => 'EMS',
            'PARCEL_CLASS_1'    => 'Посылка 1 класса'
        ];

        $results = [];

        foreach ($mail_types as $type => $name) {
            $request = [
                'index-from'     => $params['from_postcode'],
                'index-to'       => $params['to_postcode'],
                'mail-category'  => 'ORDINARY',
                'mail-type'      => $type,
                'mass'           => $params['weight'], // в граммах
                'dimension-type' => 'S',
                'fragile'        => false
            ];

            if (isset($params['declared_value'])) {
                $request['declared-value'] = $params['declared_value'] * 100;
            }

            $response = $this->apiRequest('tariff', $request);

            if (isset($response['total-rate'])) {
                $results[$type] = [
                    'name'          => $name,
                    'cost' $response['total-rate'] / 100,
                    'delivery_days' => $response['delivery-time']['max-days'] ?? 14
                ];
            }
        }

        return $results;
    }

    private function apiRequest($endpoint, $data): array {
        $ch = curl_init($this->api_url . $endpoint);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'Content-Type: application/json',
            'Authorization: AccessToken ' . $this->token,
            'X-User-Authorization: Basic ' . base64_encode($this->login . ':' . $this->password)
        ]);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

        $response = json_decode(curl_exec($ch), true);
        curl_close($ch);

        return $response ?? [];
    }
}

Сравнение служб доставки

Параметр СДЭК Яндекс Доставка Почта России
Сроки (Москва) 1-2 дня от 15 мин 3-5 дней
Сроки (регионы) 2-7 дней 1-3 дня 5-14 дней
Стоимость (1 кг, МСК) от 250₽ от 200₽ от 150₽
ПВЗ 4500+ Партнёры 42000+
Наложенный платёж
API REST API v2 REST API REST API
Модуль OpenCart Есть Нужен кастом Есть
07

Интеграция АТОЛ Онлайн

Онлайн-касса и фискализация чеков по 54-ФЗ

⚠️ Требование 54-ФЗ

Все интернет-магазины обязаны отправлять электронные чеки покупателям. АТОЛ Онлайн — облачное решение, не требующее физической кассы.

🧾

АТОЛ Онлайн

Облачная касса

  • Облачная фискализация
  • Не нужна физическая касса
  • Автоматическая отправка в ФНС
  • Email/SMS чеки покупателям
  • Интеграция с платёжками
💰

Тарифы АТОЛ

Стоимость услуг

  • Подключение: от 3 000₽
  • Абонплата: от 3 000₽/мес
  • За чек: ~2₽ (при большом объёме)
  • ОФД: включено

🔧 Настройка АТОЛ Онлайн

1
Регистрация в АТОЛ Онлайн

Зарегистрируйтесь на online.atol.ru и получите:

  • Login — логин интегратора
  • Password — пароль
  • Group code — код группы касс
  • ИНН организации
2
Настройка кассы в ЛК АТОЛ
Параметры кассы в АТОЛ
Настройки кассы в личном кабинете АТОЛ:

Название компании:      ООО "Ваша компания" или ИП Иванов И.И.
ИНН:                   1234567890
Адрес расчётов:        https://yoursite.ru
Система налогообложения: 
  - osn          // Общая
  - usn_income   // УСН Доходы
  - usn_income_outcome // УСН Доходы-Расходы
  - patent       // Патент

Email отправителя:      kassa@yoursite.ru
3
Установка модуля OpenCart

Установите модуль АТОЛ через админку или вручную.

PHP: Класс интеграции с АТОЛ Онлайн
<?php
// system/library/atol.php

namespace Opencart\System\Library;

class Atol {

    private $api_url = 'https://online.atol.ru/possystem/v4/';
    private $login;
    private $password;
    private $group_code;
    private $inn;
    private $token;
    private $payment_address;
    private $sno; // Система налогообложения

    public function __construct($config) {
        $this->login = $config['login'];
        $this->password = $config['password'];
        $this->group_code = $config['group_code'];
        $this->inn = $config['inn'];
        $this->payment_address = $config['payment_address'];
        $this->sno = $config['sno'] ?? 'usn_income';
    }

    /**
     * Получение токена авторизации
     */
    public function getToken(): ?string {
        $data = [
            'login'    => $this->login,
            'pass'     => $this->password
        ];

        $response = $this->request('getToken', $data, false);

        if (isset($response['token'])) {
            $this->token = $response['token'];
            return $this->token;
        }

        return null;
    }

    /**
     * Отправка чека прихода (продажа)
     */
    public function sendSellReceipt($order, $items): array {
        return $this->sendReceipt('sell', $order, $items);
    }

    /**
     * Отправка чека возврата
     */
    public function sendRefundReceipt($order, $items): array {
        return $this->sendReceipt('sell_refund', $order, $items);
    }

    /**
     * Формирование и отправка чека
     */
    private function sendReceipt($operation, $order, $items): array {
        if (!$this->token) {
            $this->getToken();
        }

        // Формируем позиции чека
        $receipt_items = [];
        $total = 0;

        foreach ($items as $item) {
            $item_total = round($item['price'] * $item['quantity'], 2);
            $total += $item_total;

            $receipt_items[] = [
                'name'             => mb_substr($item['name'], 0, 128),
                'price'            => $item['price'],
                'quantity'         => $item['quantity'],
                'sum'              => $item_total,
                'measurement_unit' => 'шт',
                'payment_method'   => 'full_payment',    // Полная оплата
                'payment_object'   => 'commodity',       // Товар
                'vat'              => [
                    'type' => $this->getVatType($item['tax_rate'] ?? 0)
                ]
            ];

            // Если есть код маркировки
            if (!empty($item['marking_code'])) {
                $receipt_items[count($receipt_items) - 1]['nomenclature_code'] = [
                    'type'  => 'UNKNOWN',
                    'value' => $item['marking_code']
                ];
            }
        }

        // Добавляем доставку, если есть
        if (!empty($order['shipping_cost']) && $order['shipping_cost'] > 0) {
            $receipt_items[] = [
                'name'             => 'Доставка',
                'price'            => $order['shipping_cost'],
                'quantity'         => 1,
                'sum'              => $order['shipping_cost'],
                'payment_method'   => 'full_payment',
                'payment_object'   => 'service',
                'vat'              => ['type' => 'none']
            ];
            $total += $order['shipping_cost'];
        }

        // Формируем чек
        $receipt = [
            'external_id' => $order['order_id'] . '_' . time(),
            'receipt'     => [
                'client' => [
                    'email' => $order['email']
                ],
                'company' => [
                    'email'           => $this->payment_address,
                    'sno'             => $this->sno,
                    'inn'             => $this->inn,
                    'payment_address' => $this->payment_address
                ],
                'items'    => $receipt_items,
                'payments' => [
                    [
                        'type' => 1, // Электронная оплата
                        'sum'  => $total
                    ]
                ],
                'total' => $total
            ],
            'timestamp' => date('d.m.Y H:i:s')
        ];

        // Добавляем телефон если есть
        if (!empty($order['telephone'])) {
            $receipt['receipt']['client']['phone'] = $this->formatPhone($order['telephone']);
        }

        // Отправляем чек
        $endpoint = $this->group_code . '/' . $operation;

        return $this->request($endpoint, $receipt);
    }

    /**
     * Проверка статуса чека
     */
    public function checkStatus($uuid): array {
        if (!$this->token) {
            $this->getToken();
        }

        $endpoint = $this->group_code . '/report/' . $uuid;

        return $this->request($endpoint, [], true, 'GET');
    }

    /**
     * Определение типа НДС
     */
    private function getVatType($rate): string {
        return match(intval($rate)) {
            20  => 'vat20',
            10  => 'vat10',
            0   => 'vat0',
            default => 'none'
        };
    }

    /**
     * Форматирование телефона
     */
    private function formatPhone($phone): string {
        $phone = preg_replace('/[^0-9]/', '', $phone);
        if (strlen($phone) == 10) {
            $phone = '7' . $phone;
        }
        return '+' . $phone;
    }

    /**
     * HTTP запрос к API
     */
    private function request($endpoint, $data, $auth = true, $method = 'POST'): array {
        $ch = curl_init($this->api_url . $endpoint);

        $headers = ['Content-Type: application/json; charset=utf-8'];

        if ($auth && $this->token) {
            $headers[] = 'Token: ' . $this->token;
        }

        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        $result = json_decode($response, true) ?? [];
        $result['http_code'] = $http_code;

        return $result;
    }
}

Автоматическая отправка чеков

PHP: Обработчик события оплаты
<?php
// catalog/controller/extension/module/atol_fiscalization.php

namespace Opencart\Catalog\Controller\Extension\Module;

class AtolFiscalization extends \Opencart\System\Engine\Controller {

    /**
     * Событие после изменения статуса заказа
     */
    public function onOrderStatusChange(&$route, &$args): void {
        $order_id = $args[0];
        $new_status_id = $args[1];

        // Статус "Оплачен" — отправляем чек продажи
        $paid_status_id = $this->config->get('module_atol_paid_status_id');

        if ($new_status_id == $paid_status_id) {
            $this->sendSellReceipt($order_id);
        }

        // Статус "Возврат" — отправляем чек возврата
        $refund_status_id = $this->config->get('module_atol_refund_status_id');

        if ($new_status_id == $refund_status_id) {
            $this->sendRefundReceipt($order_id);
        }
    }

    private function sendSellReceipt($order_id): void {
        $this->load->model('checkout/order');
        $order_info = $this->model_checkout_order->getOrder($order_id);

        if (!$order_info) {
            return;
        }

        // Проверяем, не отправлен ли уже чек
        $this->load->model('extension/module/atol');
        if ($this->model_extension_module_atol->isReceiptSent($order_id, 'sell')) {
            return;
        }

        // Получаем товары заказа
        $order_products = $this->model_checkout_order->getProducts($order_id);

        $items = [];
        foreach ($order_products as $product) {
            $items[] = [
                'name'         => $product['name'],
                'price'        => $product['price'],
                'quantity'     => $product['quantity'],
                'tax_rate'     => $product['tax'] ?? 0,
                'marking_code' => $this->getMarkingCode($product['product_id'], $order_id)
            ];
        }

        // Инициализируем АТОЛ
        $atol = new \Opencart\System\Library\Atol([
            'login'           => $this->config->get('module_atol_login'),
            'password'        => $this->config->get('module_atol_password'),
            'group_code'      => $this->config->get('module_atol_group_code'),
            'inn'             => $this->config->get('module_atol_inn'),
            'payment_address' => $this->config->get('config_url'),
            'sno'             => $this->config->get('module_atol_sno')
        ]);

        $order_data = [
            'order_id'      => $order_id,
            'email'         => $order_info['email'],
            'telephone'     => $order_info['telephone'],
            'shipping_cost' => $this->getShippingCost($order_id)
        ];

        $result = $atol->sendSellReceipt($order_data, $items);

        // Сохраняем результат
        $this->model_extension_module_atol->saveReceipt($order_id, 'sell', $result);

        // Логируем
        $this->log->write('ATOL: Чек продажи для заказа #' . $order_id . ' - ' . json_encode($result));
    }
}
💡 Регистрация события

Зарегистрируйте обработчик в admin/controller/extension/module/atol.php:

PHP: Регистрация события
// При установке модуля регистрируем событие
$this->load->model('setting/event');

$this->model_setting_event->addEvent([
    'code'        => 'atol_fiscalization',
    'description' => 'Отправка чеков в АТОЛ',
    'trigger'     => 'catalog/model/checkout/order/addHistory/after',
    'action'      => 'extension/module/atol_fiscalization|onOrderStatusChange',
    'status'      => 1,
    'sort_order'  => 0
]);
08

Интеграция с Честным знаком

Работа с маркированными товарами через API

🚨 Обязательная маркировка

С 2024 года обязательна маркировка для: обуви, одежды, парфюмерии, шин, фототоваров, молочной продукции, воды, пива и других категорий. Штрафы за нарушение — до 300 000₽.

Что такое Честный знак

Система маркировки

  • Государственная система маркировки
  • Data Matrix код на каждом товаре
  • Отслеживание от производителя до покупателя
  • Передача кодов при продаже
📋

Что нужно сделать

Для интернет-магазина

  • Регистрация в ГИС МТ
  • Получение УКЭП (электронная подпись)
  • Сканирование кодов при приёмке
  • Передача кодов в чеке при продаже

Маркируемые товары

Категория Код товарной группы Обязательна с
Обувь shoes 01.07.2020
Одежда (лёгкая промышленность) lp 01.01.2021
Парфюмерия perfumery 01.10.2020
Шины tires 01.11.2020
Фототовары photo 01.10.2020
Молочная продукция milk 01.09.2022
Вода water 01.03.2023
Пиво beer 01.04.2024
БАДы supplements 01.10.2023

🔧 Интеграция с API Честного знака

PHP: Класс интеграции с Честным знаком
<?php
// system/library/chestny_znak.php

namespace Opencart\System\Library;

class ChestnyZnak {

    // Боевой URL
    private $api_url = 'https://markirovka.crpt.ru/api/v3/';

    // Тестовый URL
    private $test_api_url = 'https://markirovka.sandbox.crpt.tech/api/v3/';

    private $token;
    private $client_id;
    private $client_secret;
    private $test_mode;

    public function __construct($config) {
        $this->client_id = $config['client_id'];
        $this->client_secret = $config['client_secret'];
        $this->test_mode = $config['test_mode'] ?? false;
    }

    /**
     * Получение URL API
     */
    private function getApiUrl(): string {
        return $this->test_mode ? $this->test_api_url : $this->api_url;
    }

    /**
     * Авторизация по сертификату (УКЭП)
     */
    public function authenticate(): bool {
        // Шаг 1: Получаем случайные данные для подписи
        $auth_data = $this->request('auth/cert/key', [], 'GET');

        if (!isset($auth_data['uuid'], $auth_data['data'])) {
            return false;
        }

        // Шаг 2: Подписываем данные УКЭП (требуется CryptoPro)
        $signature = $this->signData($auth_data['data']);

        // Шаг 3: Отправляем подпись и получаем токен
        $token_response = $this->request('auth/cert/' . $auth_data['uuid'], [
            'data' => $signature
        ]);

        if (isset($token_response['token'])) {
            $this->token = $token_response['token'];
            return true;
        }

        return false;
    }

    /**
     * Проверка кода маркировки
     */
    public function checkCode($code, $product_group = 'lp'): array {
        $this->ensureAuthenticated();

        $endpoint = 'cis/info';

        $response = $this->request($endpoint, [
            'cis' => $code,
            'productGroup' => $product_group
        ]);

        return $response;
    }

    /**
     * Получение информации о нескольких кодах
     */
    public function checkCodes(array $codes, $product_group = 'lp'): array {
        $this->ensureAuthenticated();

        $results = [];

        // API позволяет проверять до 100 кодов за раз
        $chunks = array_chunk($codes, 100);

        foreach ($chunks as $chunk) {
            $response = $this->request('cis/info/array', [
                'cisList'      => $chunk,
                'productGroup' => $product_group
            ]);

            if (isset($response['cisInfos'])) {
                $results = array_merge($results, $response['cisInfos']);
            }
        }

        return $results;
    }

    /**
     * Вывод товара из оборота (при продаже)
     * Выполняется автоматически через ККТ при передаче кода в чек
     */
    public function withdrawFromCirculation(array $codes, $product_group = 'lp'): array {
        $this->ensureAuthenticated();

        $document = [
            'product_group'   => $product_group,
            'withdrawal_type' => 'RETAIL', // Розничная продажа
            'cis_list'        => $codes
        ];

        return $this->request('lk/documents/withdrawal', $document);
    }

    /**
     * Возврат товара в оборот
     */
    public function returnToCirculation(array $codes, $product_group = 'lp'): array {
        $this->ensureAuthenticated();

        $document = [
            'product_group'   => $product_group,
            'return_type'    => 'RETAIL_RETURN',
            'cis_list'       => $codes
        ];

        return $this->request('lk/documents/return', $document);
    }

    /**
     * Приёмка товара (от поставщика)
     */
    public function acceptGoods($document_id): array {
        $this->ensureAuthenticated();

        return $this->request('lk/receipt/accept', [
            'document_id' => $document_id
        ]);
    }

    /**
     * Получение входящих документов (УПД от поставщиков)
     */
    public function getIncomingDocuments($date_from = null): array {
        $this->ensureAuthenticated();

        $params = [
            'direction' => 'INCOMING',
            'limit'     => 100
        ];

        if ($date_from) {
            $params['dateFrom'] = $date_from;
        }

        return $this->request('lk/documents', $params, 'GET');
    }

    /**
     * Проверка авторизации
     */
    private function ensureAuthenticated(): void {
        if (!$this->token) {
            $this->authenticate();
        }
    }

    /**
     * Подпись данных УКЭП (заглушка - требуется CryptoPro)
     */
    private function signData($data): string {
        // Здесь должна быть интеграция с CryptoPro CSP
        // Варианты:
        // 1. CryptoPro PHP extension
        // 2. Внешний сервис подписи
        // 3. КриптоАРМ ГОСТ

        // Пример вызова через командную строку:
        // $signed = shell_exec("cryptcp -sign -der -cert $cert_thumbprint $data");

        throw new \Exception('Требуется настройка CryptoPro для подписи');
    }

    /**
     * HTTP запрос к API
     */
    private function request($endpoint, $data = [], $method = 'POST'): array {
        $url = $this->getApiUrl() . $endpoint;

        if ($method === 'GET' && !empty($data)) {
            $url .= '?' . http_build_query($data);
        }

        $ch = curl_init($url);

        $headers = [
            'Content-Type: application/json',
            'Accept: application/json'
        ];

        if ($this->token) {
            $headers[] = 'Authorization: Bearer ' . $this->token;
        }

        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);

        if ($method === 'POST') {
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
        }

        $response = curl_exec($ch);
        $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        $result = json_decode($response, true) ?? [];
        $result['_http_code'] = $http_code;

        return $result;
    }
}

Управление кодами маркировки в OpenCart

PHP: Модель управления маркировкой
<?php
// admin/model/extension/module/marking.php

namespace Opencart\Admin\Model\Extension\Module;

class Marking extends \Opencart\System\Engine\Model {

    /**
     * Добавление кода маркировки к товару
     */
    public function addMarkingCode($product_id, $marking_code): int {
        $this->db->query("
            INSERT INTO `" . DB_PREFIX . "product_marking` 
            SET 
                product_id = '" . (int)$product_id . "',
                marking_code = '" . $this->db->escape($marking_code) . "',
                status = 'available',
                date_added = NOW()
        ");

        return $this->db->getLastId();
    }

    /**
     * Массовый импорт кодов маркировки
     */
    public function importMarkingCodes($product_id, array $codes): int {
        $imported = 0;

        foreach ($codes as $code) {
            $code = trim($code);

            if (empty($code)) {
                continue;
            }

            // Проверяем, нет ли уже такого кода
            $existing = $this->db->query("
                SELECT marking_id FROM `" . DB_PREFIX . "product_marking` 
                WHERE marking_code = '" . $this->db->escape($code) . "'
            ");

            if (!$existing->num_rows) {
                $this->addMarkingCode($product_id, $code);
                $imported++;
            }
        }

        return $imported;
    }

    /**
     * Получение доступного кода для товара
     */
    public function getAvailableCode($product_id): ?array {
        $query = $this->db->query("
            SELECT * FROM `" . DB_PREFIX . "product_marking`
            WHERE product_id = '" . (int)$product_id . "'
            AND status = 'available'
            ORDER BY date_added ASC
            LIMIT 1
        ");

        return $query->num_rows ? $query->row : null;
    }

    /**
     * Резервирование кода для заказа
     */
    public function reserveCode($marking_id, $order_id): bool {
        $this->db->query("
            UPDATE `" . DB_PREFIX . "product_marking`
            SET 
                order_id = '" . (int)$order_id . "',
                status = 'reserved'
            WHERE marking_id = '" . (int)$marking_id . "'
            AND status = 'available'
        ");

        return $this->db->countAffected() > 0;
    }

    /**
     * Подтверждение продажи (код выведен из оборота)
     */
    public function confirmSold($marking_id): void {
        $this->db->query("
            UPDATE `" . DB_PREFIX . "product_marking`
            SET 
                status = 'sold',
                date_sold = NOW()
            WHERE marking_id = '" . (int)$marking_id . "'
        ");
    }

    /**
     * Возврат кода в оборот
     */
    public function returnCode($marking_id): void {
        $this->db->query("
            UPDATE `" . DB_PREFIX . "product_marking`
            SET 
                status = 'returned',
                order_id = NULL
            WHERE marking_id = '" . (int)$marking_id . "'
        ");
    }

    /**
     * Получение кодов маркировки для заказа
     */
    public function getOrderMarkingCodes($order_id): array {
        $query = $this->db->query("
            SELECT pm.*, p.name as product_name, p.model
            FROM `" . DB_PREFIX . "product_marking` pm
            LEFT JOIN `" . DB_PREFIX . "product_description` p 
                ON (pm.product_id = p.product_id AND p.language_id = '" . (int)$this->config->get('config_language_id') . "')
            WHERE pm.order_id = '" . (int)$order_id . "'
        ");

        return $query->rows;
    }

    /**
     * Статистика по кодам маркировки
     */
    public function getStatistics($product_id = null): array {
        $where = $product_id ? "WHERE product_id = '" . (int)$product_id . "'" : '';

        $query = $this->db->query("
            SELECT 
                status,
                COUNT(*) as count
            FROM `" . DB_PREFIX . "product_marking`
            " . $where . "
            GROUP BY status
        ");

        $stats = [
            'available' => 0,
            'reserved'  => 0,
            'sold'      => 0,
            'returned'  => 0
        ];

        foreach ($query->rows as $row) {
            $stats[$row['status']] = (int)$row['count'];
        }

        $stats['total'] = array_sum($stats);

        return $stats;
    }
}

Передача кода маркировки в чек

ℹ️ Важно

Код маркировки передаётся в чек через поле nomenclature_code в АТОЛ или аналогичное поле в других системах. При фискализации чека код автоматически выводится из оборота в системе Честный знак.

PHP: Формирование чека с маркировкой
// Формирование позиции чека с кодом маркировки

$receipt_item = [
    'name'             => 'Футболка мужская размер L',
    'price'            => 1990.00,
    'quantity'         => 1,
    'sum'              => 1990.00,
    'measurement_unit' => 'шт',
    'payment_method'   => 'full_payment',
    'payment_object'   => 'commodity',
    'vat'              => ['type' => 'none'],

    // Код маркировки (Data Matrix)
    'nomenclature_code' => [
        'type'  => 'UNKNOWN',  // или 'EAN13', 'ITF14'
        'value' => '010460406000600021N4N57RSCBUZTQ' // полный код DataMatrix
    ],

    // Для товарной группы (необязательно)
    'product_code' => [
        'code'         => '04604060006000',  // GTIN
        'product_id'   => 'N4N57RSCBUZTQ',  // Серийный номер
        'product_type' => '1'               // Тип (для мехов/драгоценностей)
    ]
];
09

Оптимизация производительности

Кэширование, оптимизация БД, CDN

PHP OPcache

Кэш байткода

Ускорение PHP в 2-3 раза. Обязательно для продакшена.

🗄️

Redis Cache

Кэш сессий и данных

Кэширование сессий, запросов к БД, объектов.

🌐

CDN

Статика и изображения

CloudFlare, KeyCDN для быстрой загрузки.

Настройка Redis для OpenCart

PHP: Кэширование через Redis
<?php
// system/library/cache/redis.php

namespace Opencart\System\Library\Cache;

class Redis {

    private $redis;
    private $prefix = 'oc_';
    private $expire = 3600;

    public function __construct($config = []) {
        $this->redis = new \Redis();

        $host = $config['host'] ?? '127.0.0.1';
        $port = $config['port'] ?? 6379;

        $this->redis->connect($host, $port);

        if (!empty($config['password'])) {
            $this->redis->auth($config['password']);
        }

        if (isset($config['database'])) {
            $this->redis->select($config['database']);
        }

        $this->expire = $config['expire'] ?? 3600;
    }

    public function get($key): mixed {
        $data = $this->redis->get($this->prefix . $key);

        if ($data === false) {
            return null;
        }

        return json_decode($data, true);
    }

    public function set($key, $value, $expire = null): void {
        $expire = $expire ?? $this->expire;

        $this->redis->setex(
            $this->prefix . $key,
            $expire,
            json_encode($value)
        );
    }

    public function delete($key): void {
        $this->redis->del($this->prefix . $key);
    }

    public function flush(): void {
        $keys = $this->redis->keys($this->prefix . '*');

        if ($keys) {
            $this->redis->del($keys);
        }
    }
}

Оптимизация базы данных

SQL: Оптимизация индексов
-- Добавляем недостающие индексы для ускорения

-- Индекс на товары по статусу и количеству
ALTER TABLE `oc_product` 
ADD INDEX `idx_status_quantity` (`status`, `quantity`);

-- Индекс на категории товаров
ALTER TABLE `oc_product_to_category`
ADD INDEX `idx_category_product` (`category_id`, `product_id`);

-- Индекс на заказы по дате и статусу
ALTER TABLE `oc_order`
ADD INDEX `idx_date_status` (`date_added`, `order_status_id`);

-- Индекс на историю заказов
ALTER TABLE `oc_order_history`
ADD INDEX `idx_order_date` (`order_id`, `date_added`);

-- Оптимизация таблиц
OPTIMIZE TABLE `oc_product`, `oc_product_description`, `oc_category`, 
               `oc_order`, `oc_customer`, `oc_session`;

-- Очистка старых сессий
DELETE FROM `oc_session` WHERE expire < NOW();

-- Очистка старых логов API
DELETE FROM `oc_api_session` WHERE date_added < DATE_SUB(NOW(), INTERVAL 7 DAY);

Оптимизация изображений

PHP: Автоконвертация в WebP
<?php
// system/library/image.php - модификация

public function save($file, $quality = 90): void {
    $info = pathinfo($file);
    $extension = strtolower($info['extension']);

    // Автоматическая конвертация в WebP
    if (function_exists('imagewebp') && in_array($extension, ['jpg', 'jpeg', 'png'])) {
        $webp_file = $info['dirname'] . '/' . $info['filename'] . '.webp';

        if (!file_exists($webp_file)) {
            imagewebp($this->image, $webp_file, $quality);
        }
    }

    // Сохраняем оригинал
    switch ($extension) {
        case 'jpeg':
        case 'jpg':
            imagejpeg($this->image, $file, $quality);
            break;
        case 'png':
            imagepng($this->image, $file);
            break;
        case 'webp':
            imagewebp($this->image, $file, $quality);
            break;
    }
}

CRON-задания для оптимизации

crontab -e
# Очистка кэша каждую ночь в 3:00
0 3 * * * php /var/www/html/cli/cache_clear.php

# Оптимизация БД раз в неделю
0 4 * * 0 php /var/www/html/cli/db_optimize.php

# Синхронизация с 1С каждые 30 минут
*/30 * * * * php /var/www/html/cli/sync_1c.php

# Обновление курсов валют каждый день
0 9 * * * php /var/www/html/cli/currency_update.php

# Отправка отложенных email
*/5 * * * * php /var/www/html/cli/mail_queue.php

# Проверка статусов доставки
0 */2 * * * php /var/www/html/cli/delivery_status.php

# Генерация sitemap
0 6 * * * php /var/www/html/cli/sitemap_generate.php
💡 Чек-лист оптимизации
PHP OPcache включен Критично
Gzip сжатие Критично
Кэширование статики (30 дней) Важно
Redis для сессий Важно
CDN для изображений Рекомендуется
WebP изображения Рекомендуется
10

Безопасность

Защита магазина от взлома и атак

🔒

Обязательные меры

Минимум для продакшена

  • SSL-сертификат (HTTPS)
  • Сложный пароль админа
  • Переименование /admin/
  • Удаление папки /install/
  • Регулярные бэкапы
  • Обновления безопасности
🛡️

Дополнительная защита

Продвинутые меры

  • Fail2ban для SSH
  • WAF (CloudFlare/ModSecurity)
  • Двухфакторная авторизация
  • Ограничение доступа по IP
  • Мониторинг файлов
  • Security headers

Переименование админки

Terminal
# 1. Переименовываем папку admin
mv /var/www/html/admin /var/www/html/secure_panel_xyz123

# 2. Обновляем config.php в админке
nano /var/www/html/secure_panel_xyz123/config.php

# Меняем строки:
# define('DIR_APPLICATION', '/var/www/html/secure_panel_xyz123/');
# define('HTTP_SERVER', 'https://yoursite.ru/secure_panel_xyz123/');
# define('HTTPS_SERVER', 'https://yoursite.ru/secure_panel_xyz123/');

Защита .htaccess

.htaccess
# Защита от XSS
Header always set X-XSS-Protection "1; mode=block"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"

# Content Security Policy
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:;"

# Запрет доступа к служебным файлам
<FilesMatch "^(config|php\.ini|\.htaccess|\.env)">
    Order allow,deny
    Deny from all
</FilesMatch>

# Запрет выполнения PHP в папке изображений
<Directory "/var/www/html/image">
    php_admin_flag engine Off
</Directory>

# Защита от брутфорса (ограничение запросов)
<IfModule mod_evasive20.c>
    DOSHashTableSize 3097
    DOSPageCount 5
    DOSSiteCount 100
    DOSPageInterval 1
    DOSSiteInterval 1
    DOSBlockingPeriod 60
</IfModule>

Автоматические бэкапы

backup.sh
#!/bin/bash

# Настройки
SITE_PATH="/var/www/html"
BACKUP_PATH="/var/backups/opencart"
DB_NAME="opencart_db"
DB_USER="opencart_user"
DB_PASS="your_password"
DATE=$(date +%Y%m%d_%H%M%S)
DAYS_KEEP=7

# Создаём папку для бэкапов
mkdir -p $BACKUP_PATH

# Бэкап базы данных
mysqldump -u$DB_USER -p$DB_PASS $DB_NAME | gzip > $BACKUP_PATH/db_$DATE.sql.gz

# Бэкап файлов (только важные)
tar -czf $BACKUP_PATH/files_$DATE.tar.gz \
    -C $SITE_PATH \
    config.php \
    admin/config.php \
    image/catalog \
    system/storage \
    --exclude="*.log"

# Удаляем старые бэкапы
find $BACKUP_PATH -type f -mtime +$DAYS_KEEP -delete

# Опционально: отправка на удалённый сервер
# rsync -avz $BACKUP_PATH/ user@backup-server:/backups/opencart/

echo "Бэкап завершён: $DATE"
🚨 Критические уязвимости OpenCart

Всегда обновляйте OpenCart! Регулярно проверяйте обновления на GitHub.

Распространённые проблемы:
• SQL-инъекции в устаревших модулях
• XSS в пользовательских данных
• Загрузка вредоносных файлов через формы
• Утечка данных через /system/storage/

Итоговый чек-лист запуска

Проверьте всё перед запуском магазина

🔧 Техническая часть

  • PHP 8.1+ установлен
  • SSL сертификат настроен
  • OpenCart установлен
  • Папка /install/ удалена
  • Админка переименована
  • Права на файлы настроены
  • OPcache включен
  • Бэкапы настроены

💳 Оплата

  • ЮKassa/Робокасса подключена
  • Тестовый платёж прошёл
  • Callback URL настроен
  • Боевой режим включен
  • Возвраты работают

📦 Доставка

  • СДЭК/ПР подключены
  • Расчёт стоимости работает
  • Адрес склада указан
  • Тарифы выбраны
  • Создание заявок работает

🧾 Фискализация

  • АТОЛ Онлайн подключен
  • Тестовый чек отправлен
  • СНО указана верно
  • Email/SMS чеки приходят
  • Возвратные чеки работают

✅ Маркировка (если нужна)

  • Регистрация в ГИС МТ
  • УКЭП получена
  • API подключен
  • Коды загружены в систему
  • Передача в чек настроена

📋 Юридическое

  • Политика конфиденциальности
  • Пользовательское соглашение
  • Договор оферты
  • Реквизиты компании на сайте
  • Согласие на обработку ПД