Скидка 10 % сделала заказ дороже на 87 рублей. Код при этом был написан без единой ошибки — неправильной оказалась последовательность действий.
Я доделывал демо-магазин для портфолио: каталог, корзина, промокоды. Обычная витрина, ничего сложного. Перед тем как показывать её людям, прогнал сценарий покупки руками — и получил цифры, которые стоит показать целиком.
Кладу в корзину два товара на 1630 ₽. Доставка бесплатная, порог — от 1500 ₽. Ввожу промокод на 10 %, жму «Применить».
Итог: 1717 ₽. Скидка увеличила сумму на 87 рублей.
Логика была написана буквально по постановке задачи, строчка за строчкой:
const disc = Math.round(sum * promoPct / 100); // скидка 163 ₽
const dlv = (sum - disc) >= 1500 ? 0 : 250; // доставка от суммы со скидкой
const total = sum - disc + dlv;
Считаем по шагам. Товаров на 1630. Скидка 10 % — это 163 рубля. После скидки остаётся 1467, а это уже ниже порога бесплатной доставки. Значит, добавляются 250 рублей за доставку. Итого 1467 + 250 = 1717.
Формально всё верно: скидка применилась, порог проверен, доставка посчитана. Ни одна строка не содержит ошибки. Ошибка — в порядке действий.
Порог бесплатной доставки нужно считать от суммы товаров до скидки:
const disc = Math.round(sum * promoPct / 100);
const dlv = sum >= 1500 ? 0 : 250; // порог — от суммы ДО скидки
const total = sum - disc + dlv;
| было | стало | |
|---|---|---|
| Товары | 1630 ₽ | 1630 ₽ |
| Скидка 10 % | −163 ₽ | −163 ₽ |
| Доставка | +250 ₽ | 0 ₽ |
| Итого | 1717 ₽ | 1467 ₽ |
Правка на одну строку. Найти её без прогона сценария — почти невозможно.
Такой баг не роняет сайт и не пишет ошибку в лог. Он молча делает ровно то, за что покупатель обижается сильнее всего: обещает скидку и берёт больше.
Дальше сценарий предсказуемый. Человек вводит промокод из рассылки, видит, что сумма выросла, и либо уходит совсем, либо пишет в поддержку — и там уже начинается разбирательство про «обман с акцией». Владелец магазина узнаёт о проблеме последним, потому что в аналитике это выглядит как обычные брошенные корзины.
Отдельная неприятность: даже когда порог считается правильно, поведение остаётся неочевидным для покупателя. Заказ на 1550 со скидкой 10 % опускается до 1395 — и человек искренне не понимает, почему доставка вдруг стала платной, если на странице написано «бесплатно от 1500». Поэтому одной правки мало: в корзине должно быть видно, что доставка считается от суммы до скидки. Одна строчка текста снимает половину обращений в поддержку.
Порядок операций — вообще главный источник тихих багов в корзине. Проверьте у себя:
Разобрали выше. Порог — от суммы товаров, иначе промокод может удорожать заказ.
Складываются они или умножаются? Скидка 20 % и ещё 20 % — это 40 % или 36 %? Оба варианта встречаются в жизни, но решение должно быть осознанным, а не случайным следствием того, в каком порядке лежат строки кода.
Если баллы списываются после процентной скидки, покупатель теряет часть их ценности. Если до — теряет магазин. Универсально правильного ответа нет, но у бизнеса он должен быть один, а не разный на сайте и в приложении.
Скидка 33 % от 100 рублей — это 33 рубля или 33,33? Округлять каждую позицию или итог? Расхождение в рубль на позицию превращается в заметную сумму на чеке из тридцати товаров, и потом это всплывает при сверке с бухгалтерией.
Отдельный и куда более опасный случай: если итог приходит с фронтенда, его можно подменить через инструменты разработчика. В демо я специально считаю всё на сервере и цену с клиента не принимаю вовсе — иначе любой промокод превращается в «введи что хочешь».
Дешёвый способ поймать такие вещи — прогнать три-четыре сценария руками до того, как магазин увидят люди:
Пятнадцать минут работы. Мой баг нашёлся на первом же сценарии.
И общий вывод, который стоит дороже конкретной строчки кода: бизнес-логику проверяют цифрами, а не чтением кода. Код здесь был написан правильно. Неправильной была последовательность, а её видно только на живых числах.
Демо, в котором это случилось, открыто: магазин с промокодом DEMO10 и админка, где меняются товары и цены. Можете проверить сами — сейчас считает верно.
Порог бесплатной доставки проверялся по сумме после скидки. Корзина на 1 630 ₽ порог 1 500 ₽ проходила, после скидки 163 ₽ оставалось 1 467 ₽ — порог переставал выполняться, и добавлялась доставка 250 ₽. Итог 1 717 ₽ вместо 1 630 ₽.
Порог бесплатной доставки считается от суммы товаров до скидки, а не после. Правка в одну строку: sum >= 1500 вместо (sum - disc) >= 1500. На тех же числах получается 1 467 ₽.
Второй считается от уже уменьшенной суммы, и вместе они дают 19 %. Это не ошибка в коде, а последствие порядка действий. Какое поведение верное — решает заказчик, и спросить надо до того, как это увидит покупатель.
Прогоном сценария покупки руками, с числами на границе: ровно на пороге, на рубль ниже и на рубль выше. Ошибка была не в формуле, а в порядке действий, и на чтении кода она не видна — каждая строка по отдельности правильная.
Делаете магазин или бота с заказами? Посмотрю вашу логику расчёта — чтобы такие вещи всплывали до запуска, а не после.
Написать в Telegram @vvs35_bot