01 01234567890 01234567890

Чекаут за двое суток на чужом магазине: кейс рефакторинга OpenCart

Кейсы 13 сентября 2026
~13 минут чтения 19 272 символа 14 просмотров

Оформление заказа держалось на стороннем плагине: 25+ скриптов, свой мини-фреймворк и общий счётчик загрузки, из-за которого один лишний файл ломал форму. Мы собрали замену за двое суток, не выключая магазин, и следующие три месяца приводили сайт в порядок без единой аварии, которую заметил бы покупатель.

Чекаут за двое суток на чужом магазине: кейс рефакторинга OpenCart

У владельца интернет-магазина обычно есть одна кнопка, которую он боится трогать больше всего, — оформление заказа. Она работает, деньги приходят, а внутри стоит стороннее решение, которое поставил подрядчик несколько лет назад и с тех пор пропал. Трогать страшно, не трогать нельзя: любая правка на сайте рискует зацепить именно её.

В magijewelry.com — магазине серебряных украшений на OpenCart 4 — оформление заказа держалось на стороннем плагине, который тянул за собой два с лишним десятка скриптов и стилей, зависел от трёх других расширений и падал от добавления одного файла. Мы собрали ему замену за двое суток, не выключая магазин ни на минуту. Дальше был рефакторинг интернет-магазина на OpenCart длиной в три месяца: что нашли, где ошиблись сами и как правили живой сайт, ни разу не уронив его для покупателя.

Магазин, в который страшно заходить

MagiJewelry продаёт украшения со смыслом — знаки зодиака, камни, число судьбы. Сайт работает на OpenCart 4 в Docker: Apache, MySQL, Redis под кэш и сессии, отдельный поисковый движок. Магазин продавал несколько лет, за это время через него прошло несколько подрядчиков, и каждый оставил свой слой поверх предыдущего.

Владелец сформулировал задачу так: привести накопившееся в порядок и ничего при этом не сломать. Вторая половина оказалась сложнее первой.

Проблема была не в том, что сайт плохо выглядел или медленно грузился. Проблема была в предсказуемости. Никто, включая владельца, не мог заранее сказать, что сломается от очередной правки. Разработчик открывает файл, меняет строчку, обновляет страницу — и ничего не происходит, потому что в проде рендерится совсем другой файл. Или наоборот: правится карточка товара, а перестаёт работать корзина.

Сайт при этом сейчас в режиме технических работ: в шапке каждой страницы висит баннер «оформить заказ можно в нашей группе ВКонтакте». Эта деталь ещё сыграет свою роль.

Почему замену оформления заказа считали невозможной

Старый чекаут — стороннее расширение с собственным мини-фреймворком внутри. При загрузке страницы оно подтягивало больше двадцати пяти скриптов и стилей, несло свои библиотеки для масок ввода, телефонов и всплывающих сообщений и зависело от трёх других расширений магазина. Тронуть в нём одну деталь означало проверять руками всё.

Дальше начинались обстоятельства проекта.

Магазин нельзя выключить: он продаёт каждый день, и любое окно работ — это потерянные заказы. Тестов и автоматических проверок в проекте нет вообще, единственная проверка — глазами, в браузере. Документации от прежних подрядчиков не осталось, спросить не у кого. А поверх всего лежит самодельная система загрузки скриптов, которая считает файлы: количество задаётся в PHP, каждый загруженный файл уменьшает счётчик, и когда счётчик доходит до нуля, страница объявляет себя готовой. Разойдётся на единицу — оформление заказа не инициализируется никогда, покупатель видит зависшую форму.

Складываем: заменить нужно самый ответственный узел магазина, в системе без тестов, без документации, без права на простой, где добавление одного файла может уронить как раз тот самый узел.

Обычный ответ подрядчика в такой ситуации — «давайте перепишем сайт целиком, так надёжнее». Мы считаем это способом переложить риск на заказчика: переписывание ставит под удар данные, адреса страниц, позиции в поиске и привычки менеджеров сразу, а не по частям.

Двое суток: тринадцать задач параллельно работающему магазину

Решение было в том, чтобы ничего не выключать. Новый чекаут собирался рядом со старым, на собственном маршруте: старый продолжал принимать заказы, новый жил параллельно и открывался по отдельному адресу. Магазин всё это время работал.

За двое суток, 20 и 21 июня, закрыли тринадцать задач — по сути, весь путь покупателя от корзины до подтверждения заказа. Шапка и корзина, купоны и промокоды, итоговые суммы, форма данных покупателя, выбор доставки, выбор оплаты, экран подтверждения, регистрация нового покупателя.

Устройство выбрали намеренно скучное. Форма и корзина отдаются сервером сразу готовыми: страница приходит к покупателю целиком, без ожидания скриптов. Запросами догружаются только две вещи, которые иначе тормозят всю страницу, — расчёт доставки через внешний сервис (он отвечает примерно за полсекунды) и способы оплаты. Всё остальное работает без единой строчки чужого фреймворка.

Заодно поменяли вход. Вместо пароля — email и одноразовый код: покупатель вводит почту, получает код, попадает в свой заказ. Паролей, которые все забывают, на этом этапе больше нет.

К концу вторых суток новый чекаут был опубликован, кэш включён, а рядом поднято отдельное тестовое окружение — чтобы следующие изменения было где проверять.

Что это дало владельцу

Оформление заказа переехало на кодовую базу, которую можно менять точечно: правка в форме доставки больше не требует проверять работу купонов. Страница перестала зависеть от чужого счётчика скриптов, то есть исчез самый неприятный класс аварий — когда магазин выглядит рабочим, а заказ оформить нельзя. Вход по коду убрал восстановление пароля из пути к покупке.

Честная оговорка: старый чекаут в проде остался. Технически он всё ещё доступен, и решение о его выключении владелец принимает отдельно: тут дело в готовности расстаться с запасным вариантом, код ни при чём. Пока в магазине два механизма оформления заказа, и мы считаем это открытым пунктом, а не особенностью.

Второй честный момент: роста продаж мы здесь не показываем. Магазин переведён в режим технических работ по решению владельца, продажи идут через сообщество ВКонтакте, и приписывать чекауту выручку в такой ситуации было бы враньём.

Три месяца после: рефакторинг интернет-магазина на OpenCart

Дальше пошла долгая часть — разбор наследства.

В июле мы провели разведку по пяти направлениям: фронтенд и производительность, поисковая оптимизация, серверные зависимости, видимость для новых поисковых систем, архитектурный долг. Получилось 26 карточек задач, каждая с файлами, шагами и приоритетом. За четыре недели закрыли восемнадцать, по коммиту на задачу.

Из того, что понятно без технического бэкграунда: из проекта выкинули огромную неиспользуемую библиотеку для облачных сервисов, которую кто-то поставил и забыл. Разблокировали поисковых роботов Apple и Common Crawl — прежний подрядчик закрыл им доступ списком в настройках сервера, и сайт своими руками вычёркивал себя из поиска Siri и из данных, на которых учатся поисковики с искусственным интеллектом. Свели в одну точку служебные ссылки, которые говорят поисковику, какая страница главная. Включили нормальную отложенную загрузку картинок вместо самодельной, при которой все изображения всё равно грузились сразу.

В сентябре закрыли четыре ошибки в поиске по магазину. Самая заметная: при недоступности поискового движка магазин должен был переключаться на поиск средствами базы данных, и это переключение не срабатывало — покупатель просто получал пустой результат.

И отдельным заходом — пять партий работ по технической оптимизации для поиска, за два дня. Коротко, что изменилось:

  • несуществующие адреса отдавали главную страницу с кодом «всё в порядке» — теперь отдают честную ошибку 404;
  • карты сайта и файла с правилами для роботов не существовало вовсе — появились, 154 адреса;
  • разметки для поисковиков (товар, цена, хлебные крошки, статьи, организация) не было ни на одной странице — появилась;
  • девять групп товаров делили одинаковые заголовки, 26 групп страниц — одинаковые описания; теперь у всех уникальные;
  • 21 страница была без заголовка первого уровня, семь страниц показывали текст-заглушку «информация готовится» — не осталось ни одной;
  • пагинация категорий на всех 29 разделах вела на задвоенный адрес;
  • из 59 непонятных адресов в индексе Яндекса осталось семь, и реальной работы требовали два.

Перед каждой правкой базы делался бэкап, для изменений настроек писались скрипты отката.

Как мы правили живой магазин и ни разу его не уронили

Это та часть работы, которую заказчик обычно не видит, и именно она отвечает на вопрос «а вы не сломаете мне сайт».

Первое правило: любое изменение сначала живёт в режиме предпросмотра. Его видно только тому, кто открыл страницу со специальным параметром в адресе; обычный посетитель получает прежнюю версию. Владелец открывает ссылку, смотрит на реальных данных, соглашается — и только после этого правка становится общей. Этап предпросмотра не пропускался даже там, где работа выглядела на две минуты. Через этот маршрут прошли все пять партий сентябрьских работ.

Второе правило: на боевом сайте кнопки нажимает владелец. Диагностический код пишем мы, запускает его он и присылает результат. Это медленнее, чем делать самому, зато на работающем магазине не появляется неожиданных действий, и владелец в каждый момент понимает, что происходит с его сайтом.

Третье: одна задача — один коммит, с записью о том, чем реальность отличалась от плана. Плюс бэкап базы перед каждым изменением данных. Скучно, зато любой шаг откатывается отдельно от остальных.

И четвёртое, которое сложно продать, но которое окупается: мы вели документ-хронику. Он читается сверху вниз как журнал работ — находка, правка, предпросмотр, проверка, публикация, следующая находка. Сверху сводка актуального состояния. Отдельно и дословно записаны ошибочные выводы, включая наши собственные.

Две ошибки, которые мы записали

Кейс без этого раздела читается как реклама, поэтому вот два места, где мы ошиблись.

Сломанный файл, который продолжал отдавать «всё в порядке»

При второй правке одного контроллера скобка закрыла метод раньше времени, разметка статей на живом сайте пропала, а попытка починить файл скриптом довела его до синтаксической ошибки. Сайт при этом продолжал отвечать кодом 200 — сервер держал в памяти прежнюю, ещё рабочую версию файла. Мы откатили его к последнему коммиту и собрали заново, проверяя синтаксис после каждой вставки. Вывод, который стоит дороже самой ошибки: «сайт открывается» ничего не говорит о том, всё ли цело.

Страница «Оптовое предложение», рассказывавшая про подарочные карты

Эта страница оказалась копией страницы подарочных сертификатов, и два блока контента из трёх достались ей по наследству от сертификатов. Собственного текста у страницы нет — она целиком собирается из блоков в админке, поэтому в коде подмена не была видна. Коммерческая страница для оптовых покупателей несколько месяцев рассказывала им про подарочные карты. Чужие блоки убрали, но выяснилось, что оптовых блоков в магазине не существует вовсе — страницу нужно наполнять с нуля.

Что внутри этого проекта: раздел для технических читателей

Дальше механика, ради которой всё вышеописанное и понадобилось. Владелец бизнеса этот раздел может пропустить.

Главное: часть шаблонов подменяется на лету. Стартап-контроллер кастомного расширения вешает обработчик на событие view/*/before и для пяти маршрутов — шапка, корзина, поиск, главная, миниатюра товара — подменяет путь шаблона на файл из расширения. При этом PHP-контроллер резолвится из ядра, а одноимённый файл внутри расширения не вызывается никогда. Отсюда два следствия: ядерные шаблоны для этих маршрутов в проде не рендерятся вообще, а аудит по чтению кода выдаёт находки про страницы, которых для посетителя не существует.

Второе: часть модификаций сделана прямо в ядре, минуя механизм расширений. Блок полностраничного кэша вшит в system/framework.php, сторонние библиотеки лежат в system/library/ вместо своего неймспейса. Обновление OpenCart на таком проекте тянет на отдельный проект с окном работ.

Третье: полностраничный кэш складывает готовый HTML в Redis на сутки и не различает посетителей по кукам. Механизм «покажи это только мне» поверх такого кэша невозможен, если в самом кэше нет исключения. Оно есть: кэш пропускается, когда среди GET-параметров или кук встречается ключ, оканчивающийся на _preview. Отсюда прямое проектное правило — флаг предпросмотра обязан называться именно так, иначе несогласованная версия страницы уйдёт в общий кэш и её увидят все.

Четвёртое: данные каталога живут в двух независимых таблицах. Фасеты фильтра читают одну таблицу атрибутов, карточка товара — другую, и они расходятся: карточка показывает один цвет вставки, фильтр относит товар к другому.

Отдельно — то, что мы называем невидимым налогом на каждую правку. После изменения любого PHP-файла нужно перезагрузить Apache в контейнере, иначе проверяешь старую версию из кэша байткода. Чистка Redis командой FLUSHDB вычищает вместе с кэшем живые сессии покупателей. Локально страница не открывается по адресу контейнера — Apache отвечает 421, пока не подсунешь правильный хост через curl --resolve. Заголовок Cache-Control ставится в двух файлах, и побеждает второй. Файла composer.lock в проекте нет, а автозагрузчик в рантайме не composer'овский, поэтому пакет, убранный из composer.json, продолжает грузиться.

И вывод про сам аудит. Примерно в трети из 26 карточек исходное предположение оказалось неточным или прямо неверным: задача «убрать двойную загрузку CSS» закрылась без правок, потому что нужный шаблон подменяется событием; дублирование служебных ссылок нашлось в девяти контроллерах вместо двух; расширение мультисайтовости оказалось полностью мёртвым кодом. Аудит по чтению кода на таком проекте даёт хорошие гипотезы и плохие факты. Правильный первый вопрос перед правкой звучит не «что здесь не так», а «этот файл вообще участвует в рендере?».

Что осталось открытым

Переход на HTTP/2 оказался переездом: контейнер собран так, что нужный модуль в нём не поддерживается в принципе. Нужна пересборка образа и окно работ. Старый чекаут по-прежнему доступен. Перенос прямых правок ядра в штатный механизм расширений оценён в 150–200 часов и ждёт окна перед обновлением OpenCart. Гонка при параллельном изменении размеров картинок известна с июня и не исправлена.

Часть блокеров вообще не в коде. Сайт не подтверждён в Google Search Console — это может сделать только владелец аккаунта, а до тех пор половина рынка вне наблюдения. Счётчик Метрики не связан с Вебмастером. Каталог показывает малую часть ассортимента, и это решение владельца, от которого зависит смысл почти любой работы по трафику.

Что забрать себе, если у вас похожий магазин

Если старый узел мешает, но трогать его страшно — стройте замену рядом с работающей версией. Новый чекаут работал параллельно старому, и в любой момент можно было вернуться назад одним переключением. Это дороже на несколько часов и дешевле на один несостоявшийся простой.

Требуйте от подрядчика способ показать изменение только вам. Ссылка, которая открывает новую версию страницы для вас и не трогает покупателей, стоит дороже любых описаний словами. Если подрядчик говорит, что так нельзя, — это вопрос к архитектуре сайта, а не к вашему запросу.

Не считайте отсутствие ошибок доказательством работоспособности. Сайт может отвечать «всё в порядке» из кэша, страница — собираться из чужих блоков, а цифры в отчёте — объясняться причиной, которую видно на первом экране сайта невооружённым глазом.

Частые вопросы

Сколько стоит разобраться в чужом сайте, если подрядчик пропал?

Мы делим работу надвое: фиксированная разведка с понятным результатом — карта проекта, список находок, приоритеты — и последующие работы, которые оцениваются уже по этой карте. Разведка на проекте вроде этого занимает около недели. Называть точную сумму за весь объём до разведки — гадание, которое обычно оборачивается против одной из сторон.

Правда ли можно заменить оформление заказа за двое суток?

На этом проекте — да, и вот при каких условиях: замена собиралась рядом с работающей версией, объём был зафиксирован тринадцатью задачами, а внешние интеграции остались прежними. Если по ходу меняется логика скидок, добавляются новые службы доставки или переделывается дизайн, срок другой. Двое суток — это про сборку работающего механизма, а не про переделку бизнес-процесса.

Не проще ли переписать магазин с нуля?

Обычно нет. В работающем магазине накоплены данные, адреса страниц, позиции в поиске и привычки менеджеров, и переписывание рискует всем этим сразу. Переписывание оправдано, когда платформа перестала поддерживаться или бизнес-модель меняется целиком. В остальных случаях выгоднее поэтапная работа, где каждый шаг проверяется и откатывается отдельно.

Что вы делаете, чтобы не сломать работающий сайт?

Три вещи. Каждое изменение сначала показываем только владельцу по отдельной ссылке. На боевом сайте команды выполняет владелец, а не мы. Перед любым изменением данных делается бэкап, а для изменений настроек пишется скрипт отката. Плюс одна задача — один коммит, чтобы откатывать можно было по одной.

Как понять, что мой сайт в таком же состоянии?

Три признака заметны быстро. Прошлый подрядчик недоступен или не передавал документацию. Простые правки регулярно занимают в разы больше времени, чем вы ожидали. Разработчики говорят «мы боимся это трогать» про конкретные страницы. Любого из трёх достаточно, чтобы начать с карты проекта, а не с задач.

Можно ли обновить OpenCart, если в него вносили правки руками?

Можно, но закладывайте это как отдельный проект, а не как обслуживание. Сначала инвентаризация всех правок ядра, потом перенос их в механизм расширений, и только потом обновление. Если обновиться поверх, правки затрутся молча, а обнаружится это на боевом сайте через несколько дней.

Сколько времени занимает такая работа целиком?

Здесь получилось три месяца неполной занятости: двое суток на чекаут, неделя на разведку, четыре недели на первые восемнадцать задач, дальше точечные заходы. Главный ограничитель — скорость согласований со стороны заказчика, а не объём кода: каждая партия правок ждёт, пока владелец посмотрит предпросмотр.

Итог

Самое сложное в таком проекте — не написать код, а не сломать то, что кормит бизнес, пока этот код пишется. Оформление заказа мы заменили за двое суток, потому что собирали замену рядом с работающей версией, а три месяца рефакторинга прошли без единой аварии, которую заметил бы покупатель, потому что каждое изменение сначала видел только владелец.

Если у вас магазин с историей и ощущение, что каждая правка стала рискованной, начните не с задач, а с карты: что в вашем сайте на самом деле работает, что дублируется и что давно мертво. Мы делаем такую карту отдельной работой с понятным результатом на выходе — расскажите про свой проект.

Смежные разборы: как выбрать подрядчика по разработке сайта — чек-лист для тех, кто уже обжёгся; сколько стоит сайт в год — про стоимость владения, включая поддержку; интернет-магазин для малого бизнеса — если магазина ещё нет и вы выбираете платформу; интеграция CRM с сайтом — что происходит с заявками после оформления заказа.

Азамат

Азамат

Основатель «А Квадрат»

Работаю в web-деве с 2014 года. Прошел путь от фрилансера до фрилансера-руководителя))

Остались вопросы по проекту? Обсудим

Написать нам
Сколько стоит поддержка сайта: что входит в абонемент
Следующая статья 13.09.2026