Skip to content

Compare versions

From:To:
~1
Промокод выглядит простой задачей ровно до первой ночной смены: «код на один раз» уехал на три заказа, в чеке скидка не та, что на экране, а гость платит больше, чем видел.Changed

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

Этот список собран не из документации, а из кода четырёх зрелых движков — по одним и тем же вопросам, чтобы ответы стояли рядом.

Подробные разборы с цитатами кода — отдельными списками:

Самое полезное там — не «как у них сделано», а где у них открыто. Две гонки из списка ниже живут в проектах с тысячами звёзд прямо сейчас.

Порядок пунктов не важен: это набор проверок, а не последовательность.

Убедиться, что суммы скидки во входе нет

В схеме запроса на применение кода должна быть строка кода и больше ничего денежного. Не «мы её игнорируем» — её не должно быть в типе.

Проверить, что предпросмотр кода не выдаёт чужие данные

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

Расходовать код одним условным UPDATE

Проверка «не исчерпан ли» и захват применения должны быть одной операцией:

sql
1update promo_codes set uses = uses + 1
2where id = $1 and (max_uses is null or uses < max_uses)
3returning id

Пустой returning — код не достался, заказ отменяется целиком.

Проверить, что предел спрашивается там же, где занимается

Классическая ошибка: сначала SELECT с проверкой лимита без блокировки, потом SELECT ... FOR UPDATE, потом инкремент. Блокировка стоит после проверки и потому ничего не защищает.

Решить, считаются ли заказы в процессе оплатыRecommended

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

Скопировать в заказ код, вид скидки и её значение

В строку применения кладите не только посчитанную сумму, но и код строкой, вид (процент или фикс) и значение на день заказа.

Заменить удаление кода выключением

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

Ограничить скидку суммой позиций и не дать ей тронуть доставку

Скидка вычитается из стоимости блюд или товаров, но не из доставки. Код на 500 ₽ при заказе на 300 ₽ не должен обнулять доставку.

Закрепить границы констрейнтом в базеRecommended

Правило, которое держится порядком строк в коде, живёт до первой перестановки этих строк. Констрейнт живёт дольше:

sql
1check (discount between 0 and items_total)
2check (total = items_total + delivery_fee - discount)
Назвать направление округления вслухRecommended

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

Округлять цену на количество, а не цену за единицуRecommended

Если скидка считается на единицу товара и потом умножается на количество, копейка округления умножается вместе с ней.

Раскидать остаток так, чтобы сумма долей сошлась точноOptional

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

Перечитать правила кода внутри транзакции заказа

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

Не проводить оплату молча, если скидка исчезла

Если при перепроверке код оказался негодным и сумма из-за этого выросла — оплату проводить нельзя. Нужен явный отказ с прежней и новой суммой.

Убедиться, что один заказ не может получить два кодаRecommended

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