Как мы собрали магазин бренда одежды с расчётом доставки СДЭК: кейс СТЯГ по шагам

Бренд запускал первый дроп из четырёх футболок и не имел ни сайта, ни способа принять заказ. Рассказываем, как за два этапа сделали лендинг с предзаказом, а потом полноценный магазин: корзина с остатками по размерам, тарифы СДЭК по API, пункты выдачи на карте, оплата, коды маркировки и панель для владельца. С подводными камнями и тем, что сделали бы иначе.

СТЯГ, это новый российский бренд одежды. Военная эстетика без лишней вычурности, чёрные футболки с нагрудным карманом и принтом, размеры L, XL и XXL. Первый дроп из четырёх моделей был почти готов к выпуску, а продавать его было негде: ни сайта, ни приёма заказов, ни договора с доставкой. Владелец бренда пришёл к нам, веб-студии E-comON, с задачей «нужен магазин, и быстро, дроп на носу».

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

С чем пришёл клиент и почему запуск разбили на два этапа

На старте у бренда было: брендбук (тактический чёрный, песочный металл, оливковый, графит и красная «искра»), логотип в формате Illustrator, фирменный шрифт, тексты о бренде, фотографии одной модели из четырёх и список размеров. Не было: цен (клиент планировал объявить их в день дропа), фотографий трёх моделей, договора с транспортной компанией, онлайн-оплаты и юридических документов для сайта.

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

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

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

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

Этап 1. Лендинг за несколько дней и три подводных камня

С дизайном всё было понятно из брендбука, и сложности пришли с неожиданной стороны.

Шрифт нельзя было ставить на сайт. У клиента был фирменный шрифт TT Hoves Pro Compact, и мы уже собирались подключить его через @font-face. Открыли лицензию: у клиента только десктопная, а для использования на сайте нужно отдельное соглашение на веб-шрифт. Ставить на живой сайт шрифт с нарушением лицензии мы не стали. Подобрали близкий по характеру узкий гротеск Oswald, положили файлы на хостинг, прописали запасной стек из системных узких шрифтов. Если бренд купит веб-лицензию, замена займёт полчаса: шрифт подключается через переменные в стилях.

Красный из брендбука не проходил по контрасту. Чистый #FF0000 на чёрном фоне выглядит эффектно на баннере и плохо читается в мелком тексте. Мы оставили брендовый красный для графики, а для заливок и текста взяли два приглушённых оттенка, которые проходят норму контраста. Клиент разницы на глаз не заметил, а читаемость выросла.

Камуфляж на фоне. Клиент просил «еле заметный камуфляж» по всему сайту. Картинкой это делать плохо: тяжело и одинаково на всех блоках. Мы сгенерировали одну SVG-плитку с пятнами и наложили её через маску, а тон, прозрачность, масштаб и сдвиг задали переменными на каждом блоке. В итоге пятна везде чуть разные, а весит это меньше одной фотографии.

Галерею сделали с лайтбоксом, увеличением и свайпом: для одежды важно рассмотреть принт. На хостинге перед Apache стоит nginx, и он гасил тело обычной страницы 404; битые адреса пришлось направить на скрипт, который сам ставит код ошибки и отдаёт оформленную страницу. С редиректом на https похожая история: за nginx признак защищённого соединения приходит двумя способами, и если проверять один, редирект зацикливается.

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

Этап 2. Корзина, которая не даёт продать то, чего нет

Когда клиент прислал цены и остатки по размерам, включили магазин. Главное требование к корзине: нельзя продать футболку, которой нет на складе, и нельзя продать последнюю футболку двоим.

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

Каталог с ценами и остатками хранится в файле JSON, который правится из панели. Для четырёх моделей это быстро и просто, но к этому решению вернёмся в разделе «что сделали бы иначе».

Доставка: тарифы СДЭК по API, карта пунктов выдачи и Москва отдельно

Подсказка города. Покупатель начинает вводить название, сервер спрашивает у СДЭК список подходящих городов с кодами. Код города дальше нужен и для тарифов, и для пунктов выдачи.

Расчёт тарифов. По коду города, весу и габаритам посылки сервер запрашивает у СДЭК список тарифов. Вес считается как количество футболок, умноженное на вес одной; вес и размеры коробки задаются в панели, а не зашиты в код. Из всего списка тарифов берём два самых дешёвых: до пункта выдачи (включая постаматы) и курьером до двери. Покупатель видит два понятных варианта с ценой и сроком в днях, а не десять строк с названиями тарифов.

Пункты выдачи на карте. Если выбран пункт выдачи, сервер запрашивает у СДЭК список точек в городе с координатами и режимом работы и показывает их на карте. Карта на OpenStreetMap: без ключей и лимитов. Точки без координат отбрасываем, их на карте не показать. На случай, если библиотека карты не загрузилась или человеку так удобнее, рядом есть выпадающий список тех же точек: список работает сразу, карта подключается, когда доедет.

Москва отдельно. У бренда склад в Москве, и по городу есть свои варианты: курьер от магазина по фиксированной цене из настроек и Яндекс Доставка экспрессом. Яндекс считает цену по маршруту, а не по городу, поэтому нужны координаты дома. Мы сделали подсказку адреса с координатами (Яндекс Геокодер при наличии ключа, иначе OpenStreetMap), город подставляем в запрос сами, иначе «Ленина 5» находится в половине страны. Пока дом не выбран, показывается ориентировочная цена из настроек, после выбора цена окончательная. Порог бесплатной доставки задаётся в панели.

Три грабли, на которые наступили.

  1. Токен СДЭК привязан к контуру. Сначала считали на тестовом контуре, потому что боевой договор клиент заключал параллельно. Когда переключились на боевой, сайт ещё час отдавал ошибку авторизации: в кеше лежал старый тестовый токен. Теперь токен помечается отпечатком «учётная запись плюс контур», и при переключении запрашивается заново.
  2. СДЭК может не ответить. Расчёт СДЭК обёрнут так, что при его сбое московские варианты всё равно показываются. Покупатель из Москвы не должен страдать из-за чужого сервера.
  3. Стоимость доставки нельзя брать из браузера. При оформлении сервер заново считает варианты доставки для этого города и корзины и берёт цену выбранного варианта из своего расчёта. Иначе доставку можно было бы обнулить, подправив запрос, и оплатить заказ дешевле, чем он стоит. Если за время оформления вариант пропал, покупатель увидит просьбу выбрать доставку заново, а не тихо получит неверную сумму.

Заказы, оплата и маркировка

Заказ записывается в MySQL: шапка с контактами, способом и стоимостью доставки, статусом и оплатой, плюс позиции с размером, ценой и количеством. Номер заказа короткий, вида СТ-4F2A9K, без нулей и букв, которые путаются с цифрами. Телефон и почта обязательны: телефон проверяется на одиннадцать цифр, почта нужна для подтверждения и чека. На оформление стоит лимит заказов с одного адреса и ловушка для ботов.

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

Оплата через ЮKassa включается, как только в панели заданы доступы. Покупатель после оформления уходит на страницу оплаты, а письмо «заказ принят» отправляется только после поступления денег: человек, бросивший оплату, не должен получать подтверждение. Уведомлениям от ЮKassa мы не доверяем: из них берётся только номер платежа, а состояние сервер спрашивает у самой ЮKassa, иначе оплату можно было бы подделать обычным запросом. Платёж создаётся с ключом идемпотентности, привязанным к заказу: если ответ потерялся по дороге, повтор не спишет деньги второй раз. Возврат делается из панели, в том числе частичный, с защитой от двойного нажатия.

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

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

Панель управления и документы

Панель по адресу /admin, при первом входе владелец сам задаёт пароль. Внутри:

  • Сводка. «Требуют действий» (оплачено, но не отправлено), заказы по статусам и по оплате, последние две недели, что покупают, остатки по размерам, экономика с учётом комиссии эквайринга и ручной ввод расходов, например на рекламу.
  • Заказы. Контакты, состав, доставка, смена статуса, подбор кодов маркировки, возврат денег.
  • Заявки. Предзаказы с лендинга с выгрузкой в CSV.
  • Товары и тексты. Цены, остатки по размерам, описания, фото, заголовки и абзацы главной.
  • Доставка, СДЭК, Яндекс Доставка, ЮKassa, маркировка. Настройки и доступы с кнопкой проверки: панель сама пробует получить токен или тарифы и говорит, приняты ли ключи.

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

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

Что получилось

Бренд принимает заказы с первого дня дропа. Сначала предзаказы через форму, потом заказы с доставкой и оплатой.

Расчёт доставки по всей России. Два варианта СДЭК с ценой и сроком, пункты выдачи на карте, по Москве курьер магазина и экспресс Яндекса.

Нельзя продать то, чего нет. Остатки проверяются на витрине, в корзине и на сервере; товар резервируется на время оплаты и возвращается при отмене.

Цены и доставка считаются только на сервере. Ни одну сумму нельзя подправить из браузера.

Владелец ведёт магазин сам. Цены, остатки, фото, тексты, статусы, коды маркировки, возвраты, доступы к сервисам.

Сайт принадлежит бренду. Хостинг и домен клиента, код на его сервере.

Стек: HTML, CSS, PHP 8, MySQL, API СДЭК, Яндекс Доставка, ЮKassa, карта на OpenStreetMap, Яндекс Метрика. Живой сайт: styag-store.ru.

Что сделали бы иначе

Остатки сразу в базе, а не в файле. Для четырёх моделей файл JSON с блокировкой работает, но заказы уже в MySQL, а остатки отдельно. Когда моделей станет двадцать, мы перенесём остатки в базу и будем списывать их в одной транзакции с заказом. Правильнее было сделать так с самого начала, раз база всё равно появилась.

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

Лицензии на шрифты проверять до макета. История со шрифтом стоила лишнего согласования. Сейчас просим лицензию вместе с брендбуком.

Боевые доступы к сервисам просить в первый же день. Договор со СДЭК, магазин в ЮKassa и токен Яндекс Доставки клиент получал параллельно с разработкой, и часть проверок пришлось делать на тестовых контурах, а потом повторять. Для запуска с нуля это нормально, но чем раньше есть боевые ключи, тем меньше двойной работы.

Сколько это стоит у нас

Проект считается по нашей смете блоками, поэтому расскажем на примере СТЯГ, без привязки к тому, что заплатил именно этот клиент.

Разово, сайт ставится на хостинг и домен клиента и остаётся у него: продающий сайт (основа) от 10 000 ₽, каталог от 10 000 ₽, корзина и оформление от 5 000 ₽, онлайн-оплата от 5 000 ₽, расчёт доставки от 5 000 ₽ за каждую службу, админка от 10 000 ₽. Типовой набор «магазин с доставкой и админкой» выходит от 45 000 ₽; у СТЯГ служб доставки две плюс учёт маркировки, это дороже типового набора. Лендинг первого этапа это та же «основа», а не отдельная строка. Запуск от 14 дней с момента, когда есть материалы. Правки во время работы бесплатны, после сдачи от 1 500 ₽ за час.

По подписке, если стартовый бюджет держать минимальным: сайт с каталогом и корзиной 3 000 ₽ в месяц, в платёж входят хостинг, домен, SSL, обновления, поддержка и админка. Каждая интеграция плюс 500 ₽ в месяц: эквайринг, служба доставки, МойСклад. Магазин с оплатой и одной доставкой это 4 000 ₽ в месяц, за год со скидкой 15 % выходит 40 800 ₽. Смена цен и фото через админку бесплатна, правка, требующая программиста, 1 000 ₽ за изменение. Отключить подписку можно в кабинете одной кнопкой, при желании сайт выкупается и передаётся вместе с доменом.

Остальные наши кейсы и условия обоих форматов есть на сайте студии: https://www.e-comon.ru. Если вы запускали магазин одежды с нуля и у вас были свои истории с доставкой, маркировкой или шрифтами, расскажите в комментариях: с удовольствием ответим на вопросы и сравним грабли.