← Назад ко всем проектам

Личный проект14 мин чтения

CactusPlay - магазин игр с сетью проверенных исполнителей

CactusPlay - магазин игр с сетью проверенных исполнителей

Технологии

WordpressPHPNodeJSGolangWooCommerceMySQLE-commerceMarketplaceStartup

Оглавление

Услуги

  • →Сооснование и fullstack-разработка
  • →Скраперы на Node.js для PlayStation Store и Xbox Store
  • →Витрина на WooCommerce с кастомными расширениями на PHP
  • →Конвейер дифференциальной синхронизации цен и система оповещений
  • →Интеграция платёжного шлюза и цифровой доставки
  • →Интеграция с Яндекс.Маркетом: маппинг предложений, синхронизация цен и мгновенная отгрузка кодов
  • →Пять кастомных плагинов WordPress: синхронизация, цены, цифровые коды, соцвход, рассылки
  • →Сервис стилизации обложек на Go и self-hosted поддержка на FreeScout

Результаты

  • ✓Магазин игр с тысячами актуальных SKU по PS и Xbox
  • ✓Автоматическая ежедневная синхронизация цен - ноль ручных обновлений
  • ✓Система оповещений об изменении цен для поиска возможностей для акций
  • ✓Интеграции платёжных шлюзов СНГ с доставкой цифровых ключей
  • ✓Сквозная цепочка цены: PS Store, сайт и затем Яндекс.Маркет, без ручного шага
  • ✓Карточки на Яндекс.Маркете со всеми доступными атрибутами

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

Разбор работающей системы: витрина на WooCommerce, каталог, который наполняет граббер, админка, где ведутся цены и заказы, и то, как эти части связаны между собой. Комментарии на русском.

Задача

Геймеры из СНГ, покупающие игры для PlayStation и Xbox, сталкивались с фрагментированным рынком: цены в официальных магазинах значительно различались по регионам, продавцы подарочных карт были разбросаны по форумам, и не было единого места, чтобы сравнить и купить. Стал сооснователем CactusPlay, чтобы закрыть этот пробел: магазина игр и внутриигровых предметов с ценами из официальных источников, где выполнение заказов вынесено в сеть исполнителей, а не заперто внутри команды.

Ключевая техническая задача: PlayStation Store и Xbox Store не предоставляют публичных API. Чтобы удерживать тысячи SKU с точными ценами почти в реальном времени, пришлось строить слой скрапинга и синхронизации с нуля.

Исследование

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

Рассмотренные варианты

  1. Ручной ввод цен администраторами - отклонено. Не масштабируется; цены меняются ежедневно, во время распродаж - иногда ежечасно.
  2. Сторонний API агрегации цен - для PlayStation/Xbox в СНГ на тот момент такого не существовало.
  3. Кастомные скраперы на Node.js + витрина на WordPress/WooCommerce - выбрано. Скраперы дают реальное владение данными; WooCommerce даёт проверенный в бою коммерческий слой без создания оформления заказа с нуля.

Решение

Скраперы на Node.js, работающие по cron-расписанию, обращаются к эндпоинтам PlayStation Store и Xbox Store, разбирают ответ и отправляют обновления цен и наличия в WooCommerce через его REST API. WordPress + WooCommerce отвечают за витрину, корзину и интеграции платёжных шлюзов. Кастомные расширения на PHP обрабатывают сценарии подключения исполнителей, которые WooCommerce не поддерживал из коробки.

‹›
aA🔒cactusplay.ru/
⇧▢+
Главная

cactusplay.ru, scrollable snapshot of the live storefront

Реализация

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

Интегрированы платёжные шлюзы, поддерживающие способы оплаты СНГ. Кастомные расширения WooCommerce на PHP отвечали за цифровую доставку (коды ключей) и логику выплат исполнителям. Развёрнуто на VPS с автоматическим деплоем через Git-хуки.

Заказы мог выполнять любой, у кого есть турецкие платёжные данные

Именно это и делало магазин работоспособным. Наш товар активируется на аккаунт покупателя, а значит кто-то должен реально купить игру в дешёвом регионе и положить её туда - и у этого кого-то должны быть рабочие турецкие платёжные данные, которых у большинства в СНГ не было. Пока выполнение оставалось внутри команды, потолок магазина равнялся тому, что основатели успевали обработать за день.

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

Юридическая подготовка к приёму платежей

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

Только после этого встал платёжный контур: эквайринг через плагин банка «Точка» для WooCommerce, рядом с ним LifePay CloudPrint для фискальных чеков — сам эквайринговый плагин их не печатал. Для покупателя важна только видимая часть: оплата прямо с карты российского банка на сайте и чек, который приходит как положено.

Система за витриной

Магазин - лишь одна грань системы. Граббер на Node.js обходит официальный PlayStation Store (и Xbox Store через ту же абстракцию), забирая 20 000+ позиций каждый час. Игры, дополнения и сервисы он перебирает раздельно, переименовывает категории в собственную таксономию магазина и сводит теги сторов - каталог приходит структурированным, а не плоской выгрузкой.

Остальное несут пять кастомных плагинов WordPress:

  • slave-api - движок синхронизации. На нём абстракция платформ (единый интерфейс поверх API PlayStation и Xbox), обработчики товаров, меняющие видимость и выводящие SKU из оборота, CLI-команды для полной и частичной синхронизации и версионируемые сценарии обновления - правило каталога раскатывается на живой магазин без ручного прохода.
  • product-manager - ценовая сторона витрины. Запрашивает у PlayStation Store актуальные цены в разных валютах, выбирает одну через подключаемую стратегию (штатная берёт наименьшую легальную региональную цену), кэширует результат в transient, чтобы страница товара не ждала внешний вызов, и добавляет нужные процессу кастомные поля в форму товара, корзину, оформление и заказ.
  • product-code-manager - половина, отвечающая за цифровую доставку. Генерирует и хранит коды товаров, распознаёт и активирует их, отдаёт API с авторизацией по JWT - заказ, оформленный в любой части системы, выполняется без участия человека. Обработчики реагируют на создание заказа и смену статуса, и именно поэтому код приходит сразу после покупки цифрового товара, а не после ручной отправки.
  • aw-auth - вход. Добавляет VK, Яндекс, Steam и Discord в формы входа WordPress и WooCommerce по единому контракту провайдера: каждая новая сеть - это клиент, сервис и экран настроек, а не форк общего сценария. Steam проверяется по подписи OpenID 2.0, остальные - обменом OAuth-кода; вернувшийся посетитель сопоставляется со своим аккаунтом, новый создаётся только если совпадения нет. Колбэк может прийти дважды, поэтому код занимается в таблице-замке со счётчиком обращений: второй запрос дожидается первого и уходит по его редиректу вместо создания дубля. Steam не отдаёт email, и такие пользователи попадают на форму с почтой и телефоном до оформления заказа. Вход разрешён только по email, адрес должен быть на домене .ru по требованиям 152-ФЗ, а приватный кэш страниц сбрасывается при входе, чтобы никто не увидел чужую закэшированную страницу.
  • mail-manager - собственный движок рассылок, чтобы клиентская почта не зависела от внешнего сервиса. Аудитории - это категории, кампании привязываются к категориям, а очередь на отправку выводится джойном: получатель, у которого уже есть запись о доставке по кампании, повторно не выбирается, и упавший прогон продолжается ровно с места остановки. Крон отправляет фиксированную порцию с паузой между письмами, удерживая магазин в лимитах SMTP вместо залпа, после которого домен уходит в спам. После активации отправка выключена, а перехватчик пишет письма в лог вместо доставки - копия боевой базы не может написать реальным клиентам; в админке по каждой кампании видно «в очереди / отправлено / ошибка». Колонки открытий и кликов и эндпоинт трекинга заложены - до продакшена дошла именно доставка.

Яндекс.Маркет: сквозная цепочка цены

Интеграция с маркетплейсом замыкает контур. Предложения приводятся к схеме Яндекс.Маркета отдельным слоем мапперов с конфигурацией под каждый магазин, а ценовой сервис держит их в актуальном состоянии. Цепочка работает без человека на любом шаге: цена меняется в официальном PlayStation Store, граббер её забирает, обновляется сайт и затем обновляется предложение на Яндекс.Маркете. Коды отгружаются достаточно быстро, чтобы покупка цифрового товара на маркетплейсе закрывалась в ту же сессию, а карточки заполнены всеми атрибутами, которые Яндекс.Маркет принимает, а не минимумом для публикации.

Одно число - и весь каталог пересчитан

Ценникам всё же иногда нужна рука на рычаге: сдвинулся курс, у партнёра поменялись условия, надо подправить маржу. Вместо правки товаров множитель базовой цены лежит отдельной строкой в базе - RUB_MULTIPLIER, рядом с ним множитель для p2p. Меняешь значение, и ближайшая синхронизация пересобирает по нему все цены каталога: и на сайте, и в фиде Яндекс.Маркета.

Таблица конфигурации в DataGrip на экране ноутбука: RUB_MULTIPLIER равен 1.5, множитель p2p - 1.1, строка с конфигурацией прокси-задачи

Множители цены - по строке на каждый

Логотип, купленный на ярмарке

Знак магазина - не рисунок кактуса вообще, а вполне конкретный кактус. Подвеску я купил на уличной ярмарке в Реколете, Буэнос-Айрес, сфотографировал, отснял на зелёном фоне и обвёл вырезанный силуэт в тот знак, который теперь стоит на сайте, на обложках и сверху каждой карточки маркетплейса.

Стальная подвеска-кактус, купленная на ярмарке в Реколете, Буэнос-Айрес, на цепочке

Подвеска из Реколеты

Та же подвеска, отснятая на зелёном фоне и вырезаемая в Photoshop

Вырезана в Photoshop

Сервис стилизации обложек - сделан, но не внедрён

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

Обложка для маркетплейса, собранная сервисом на Go - размытая подложка, шапка магазина и бейджи издания

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

Набор бейджей, отрисованный в Photoshop для карточек маркетплейса: логотип магазина с кактусом, регион с флагом Аргентины, русский язык с флагом России, цифровая версия со значком загрузки

Набор бейджей по мета-данным товара

Обложки категорий - одним набором

В каталоге было 33 категории, и ни у одной не было обложки. Вместо того чтобы заказывать их поштучно, весь набор сгенерирован через Kie AI по единому стилю промпта, подобранному под сайт: неоновая линейная графика, зелёное на почти чёрном, по одному предмету на плитку. Установка тоже пошла скриптом, а не 33 заходами в медиатеку - WP-CLI импортирует каждый файл и привязывает его к терму как обложку категории, одним прогоном внутри контейнера LiteSpeed.

Обложка категории «Экшен» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Приключения» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Шутеры» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Ужасы» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Симуляторы» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Стратегии» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Ролевые» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Казуальные» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Аркады» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Файтинги» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Семейные» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Головоломки» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Развивающие» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Спорт» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Музыка и ритм» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Вечеринка» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Викторины» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Уникальные» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Инди» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Для взрослых» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Разное» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Виртуальная реальность» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Игры PS4» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Игры PS5» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Дополнения» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Распродажи» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Предложения» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Игры EA» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Доступ к играм EA Play» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Оплата подписки» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Пополнение счета» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Подборка для подростков» - неоновая линейная иконка, зелёное на почти чёрномОбложка категории «Гонки» - неоновая линейная иконка, зелёное на почти чёрном

Все 33 обложки категорий, один сгенерированный набор

Поддержка

Поддержка клиентов работает на self-hosted FreeScout по адресу support.cactusplay.ru: веб-чат и Telegram сведены в один ящик, а рядом с перепиской подтягиваются последние заказы клиента - оператор отвечает, не переключаясь между системами.

Служба поддержки на FreeScout: веб-чат и Telegram в одном ящике, рядом последние заказы клиента

Почта на своём железе

Внешние почтовые сервисы берут за ящик и за объём, а магазину нужно было расти и в том, и в другом. Всю почту забрал на себя mailcow на собственном сервере: mail.cactusgames.ru отправляет по SMTP и принимает по IMAP для всех адресов, к нему же авторизуются поддержка и рассыльщик, и доставка больше не упирается в чужой тариф и чужие правила. Ниже - подключённый к нему FreeScout: отправка на 5587 через StartTLS, забор INBOX на 993 через SSL.

Настройки SMTP во FreeScout: сервер mail.cactusgames.ru, порт 5587, TLS со StartTLS

Отправка через mail.cactusgames.ru

Настройки приёма почты во FreeScout: IMAP к mail.cactusgames.ru на порту 993 через SSL, папка INBOX, проверка сертификата включена

Приём по IMAP

Собственный почтовый движок магазина

mail-manager - тот самый кампанийный движок, описанный выше, - в работе. Он стоит на CactusPlay и на консалтинговых сайтах: именно поэтому клиентская почта не зависит от внешнего провайдера.

Форма до финальной версии

И форма активации, и инструкция стоят поверх product-code-manager - и на CactusPlay, и на 1gros. Здесь более ранняя, промежуточная версия формы: её записали, чтобы обсудить подход с партнёром и убедиться в его корректности до того, как доводить до конца.

Ещё один заход на ту же раннюю форму, уже с превью: до подтверждения покупатель видит, какой товар он получит по коду. Баги JS на экране позже поправили.

Инструкция по активации кода

Демонстрационное видео для самого сайта: как пользователю активировать код. Стоит на CactusPlay и на консалтинговом 1gros. Такой онбординг сделал процесс понятным и прозрачным - и покупатели перестали жаловаться на него в Яндекс.

На сайте была проблема: после покупки на Яндекс.Маркете пользователи переходили на форму активации игры и терялись там, не понимая, как активировать игру. Поэтому я выделил время, написал сценарий и записал это видео. Покупатели видели его прямо в форме активации, и оно помогало им не потеряться, разобраться и довести активацию до конца.

Вход через российских провайдеров

Модуль aw-auth, который существует и отдельно, а здесь переиспользован на CactusPlay и на консалтинговых сайтах. Он закрывает OAuth-авторизацию через провайдеров, готовых решений под которых тогда не было: Яндекс, насколько знаю, вообще первый из доступного, плюс VK и Discord. Упор на рынок РФ - со всеми требованиями законодательства по данным.

Экран входа Yandex ID, открытый из магазина: вход по почте или номеру телефона

Yandex ID - первый подключённый провайдер

Документы прежде оплат

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

С готовыми документами эквайринг стал досягаем. Просто так его не включают: магазин сначала прошёл модерацию, ради прохождения которой весь пакет документов и собирался. После неё расчётный счёт ушёл в Точку - её плагин для WooCommerce проводит оплату на сайте, а чеки печатает LifePay cloudprint. Покупатели из России платят рублями картой МИР, Visa или Mastercard либо через СБП, не уходя с оформления заказа.

Логотипы способов оплаты на оформлении заказа: МИР, Mastercard, Visa и СБП

Оплата рублями картой или через СБП

Та же схема ушла и в партнёрские магазины. Ниже - оформление заказа на 1gros: подытог в рублях, значки карт и СБП и строка о том, что платежи обеспечивает платформа Tochka.

Страница оформления заказа магазина 1gros на телефоне: подытог 851 рубль, значки МИР, Mastercard, Visa и СБП и строка о том, что платёжную платформу обеспечивает Tochka

Оформление заказа в партнёрском магазине

Моментальная отгрузка для Яндекс.Маркета

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

Решением стала кастомная форма активации ключей. При покупке наш API выдаёт покупателю ключ - по сути отложенное обязательство, - и этим закрывается окно отгрузки Яндекса; сама активация на аккаунт выполняется уже на нашей платформе, закрывая заказ на стороне Яндекса.

Та же цепочка работает и в партнёрских магазинах. Ниже - витрина 1gros на Яндекс.Маркете: карточки собраны автоматизацией каталога, а не заполнены руками, и каждая продажа сама выпускает ключ активации, который покупатель потом гасит на сайте.

Витрина магазина 1gros на Яндекс.Маркете с автоматически собранными карточками игр для PS4 и PS5, ценами и отгрузкой на почту в тот же день

1gros на Яндекс.Маркете, карточки собраны автоматизацией

Когда работы стало больше, чем рук

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

Страница постановки на плагин к FreeScout с интеграцией партнёрского API Яндекс.Маркета: экран настроек, список чатов, история сообщений с ленивой подгрузкой, отправка сообщений и файлов, создание чата по orderId

Постановка на один из аутсорс-модулей

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

Обезличенная переписка в Telegram с кандидатом, который отказывается от дальнейшей работы, ссылаясь на университет и две другие работы в следующем семестре

Кандидат уходит

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

Чат по заказу в партнёрском интерфейсе Яндекс.Маркета: покупатель подтверждает покупку и присылает скриншот страницы заказа, персональные данные и код активации замазаны

Чат по заказу, разбираемый руками

Почтовый ящик на iOS, забитый уведомлениями Яндекс.Маркета по магазину 1GROS.RU: новый заказ, открыт чат, новое сообщение в чате, заказ отменён, 1296 непрочитанных

Обычный день в ящике

Координация шла через сервисные чаты, а не через доску задач. У каждого магазина был свой, и бот писал в него в тот момент, когда CRM регистрировала заказ - номер, магазин, статус, время, - так что свободный человек забирал его вместо того, чтобы все обновляли админку.

Командный чат в Telegram с сообщением бота: заказ #333066 создан на ps-store.ru в статусе Paid; имена участников и закреплённое сообщение замазаны

Сервисный чат: бот CRM пишет о новых заказах

Консалтинг для других магазинов

Граббер и наработки вокруг него уходили и в консалтинг. Я применял их на 1gros.ru, ps-store.ru, playstaionplus.ru и xbox-store.ru: синхронизация базы через мой парсер, подключение сторонних магазинов, донастройка WordPress и сервисное обслуживание. На видео один из таких сайтов - 1gros.

Баннеры на главные страницы партнёрских магазинов

Работа по этим сайтам была не только про трубопроводы. Главным страницам нужно было с чего-то начинаться, и подписка PlayStation Plus получила собственный баннер - по уровням, в двух вариантах оформления на выбор магазина.

Баннер подписки PlayStation Plus на тёмном фоне с тремя уровнями: Основной, Экстра и Люкс

Тёмный вариант

Тот же баннер PlayStation Plus в тёплом градиентном оформлении, в заголовке перечислены Essential, Extra и Deluxe

Градиентный вариант

Обслуживание откуда придётся

Магазины продают круглосуточно, поэтому обслуживание не ждало рабочего места. Изрядная часть его шла по SSH с телефона: правка крона синхронизации на VPS 1gros, проверка баз playstationplus.ru и ps-store.ru, наблюдение за нагрузкой на сервере Xbox API. Красоты в этом нет, но синхронизация цен, вставшая в полночь, чинится в полночь.

Крон VPS 1gros, открытый по SSH с телефона: таймеры синхронизации товаров для playstationplus.ru и ps-store.ru

Крон синхронизации на VPS 1gros

Консоль MySQL по SSH с телефона со списком баз обслуживаемых магазинов: 1gros.ru, playstationplus.ru, ps-store.ru, psnwallet.ru

Базы обслуживаемых магазинов

htop по SSH с телефона: сервер Xbox API, 1,3% CPU и 11 дней аптайма

Нагрузка на сервере Xbox API

Граббер в работе

Граббинг статичных списков товаров с сайта PlayStation - то сырьё, из которого потом собирается каталог.

Вторая половина той же работы: настройка коэффициента цены, который превращает цену магазина в цену, по которой продаёт шоп.

Результат

Граббер держит 20 000+ позиций в час, а изменения цен доходят и до магазина, и до карточки на Яндекс.Маркете без чьего-либо участия. Платформа обрабатывала реальные транзакции с момента запуска, а инфраструктура скрапинга пережила несколько редизайнов фронтенда PlayStation и Xbox Store за счёт точечных обновлений селекторов.

Поддержка: нагрузка от краулеров LLM

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

Поддержка: не наша главная страница

Хостинг тоже попадал под атаку. Один из обслуживаемых магазинов однажды открылся с полностью подменённой главной - вместо игр там была витрина запрещённых веществ. Разрулили в тот же день. Где-то внутри было даже приятно, что нашему магазинчику досталось столько внимания.

🔒 Подменённая страница

Скриншот закрыт. Введите код, чтобы посмотреть.

Проекты

Aurora.Luxe - техническая экспертиза и ТЗ для магазина шёлковой одежды

Aurora.Luxe - техническая экспертиза и ТЗ для магазина шёлковой одежды

До: магазин разрабатывала другая команда, и когда обмен с 1С падал, никто не мог понять причину или точно проверить работу. После: техническая экспертиза и приёмка на стороне владельца и ТЗ, например на систему логирования, по которым новые доработки проверяются по пунктам.

CasaLista - маркетплейс бытовых услуг в Буэнос-Айресе

CasaLista - маркетплейс бытовых услуг в Буэнос-Айресе

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

Челябинская Газобетонная Компания - сайт и калькулятор

Челябинская Газобетонная Компания - сайт и калькулятор

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

Статьи

Счётчикам аналитики не место в файлах темы

Сниппет трекинга, вставленный в шаблон, исчезает при следующем обновлении темы, соперничает со страницей за браузер и удваивает цифры, если его вставили дважды. У всех трёх проблем одно лечение.

Пусть выдача будет следствием оплаты, а не задачей, которую кто-то выполняет

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

Очередь надо выводить, а не хранить: возобновляемые рассылки

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

Как назначать цены по чужому магазину, которым вы не управляете

При перепродаже ваши цены зависят от того, что делает с ценами кто-то другой. Запрос при загрузке страницы роняет магазин вместе с источником, ночная выгрузка устаревает. Середина - кэш, за которым стоит стратегия выбора цены.

Виджет поддержки, который уже знает, с кем разговаривает

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

Как принимать оплату через шлюз, оформленный на другой сайт

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

Открыт для работы по контракту

Я доступен для работы по контракту. Если у вас есть интересная идея проекта, запишитесь на звонок через Calendly.

Записаться на 30-минутный звонок