Промокод выглядит простой задачей ровно до первой ночной смены: «код на один раз» уехал на три заказа, в чеке скидка не та, что на экране, а гость платит больше, чем видел.
Этот список собран не из документации, а из кода четырёх зрелых движков — по одним и тем же вопросам, чтобы ответы стояли рядом.
Подробные разборы с цитатами кода — отдельными списками:
| Проект | Коммит | Разбор |
|---|---|---|
| medusajs/medusa | ecc9f07e | Блокировки, снимок без FK и тихо пропущенная акция |
| saleor/saleor | 0a11eb91 | Три уровня модели и открытая гонка |
| spree/spree | e1839c40 | Типизированные строки и COUNT вместо счётчика |
| vendure-ecommerce/vendure | 1ad4ba80 | Замок на акции и отказ вместо тихого пересчёта |
Самое полезное там — не «как у них сделано», а где у них открыто. Две гонки из списка ниже живут в проектах с тысячами звёзд прямо сейчас.
Порядок пунктов не важен: это набор проверок, а не последовательность.
В схеме запроса на применение кода должна быть строка кода и больше ничего денежного. Не «мы её игнорируем» — её не должно быть в типе.
Кнопка «Применить» обычно доступна без входа. Если в неё передаётся телефон или почта, а ответ зависит от истории этого человека — действие превращается в справочную по чужим клиентам.
Проверка «не исчерпан ли» и захват применения должны быть одной операцией:
Пустой returning — код не достался, заказ отменяется целиком.
Классическая ошибка: сначала SELECT с проверкой лимита без блокировки, потом SELECT ... FOR UPDATE, потом инкремент. Блокировка стоит после проверки и потому ничего не защищает.
Если оплата онлайн отделена во времени от создания заказа, между ними открыто окно: код формально ещё не израсходован, а заказ уже в пути.
В строку применения кладите не только посчитанную сумму, но и код строкой, вид (процент или фикс) и значение на день заказа.
На промокод ссылаются заказы. Удаление либо порвёт эти ссылки, либо утащит за собой строки применения — в обоих случаях старые чеки перестанут читаться.
Скидка вычитается из стоимости блюд или товаров, но не из доставки. Код на 500 ₽ при заказе на 300 ₽ не должен обнулять доставку.
Правило, которое держится порядком строк в коде, живёт до первой перестановки этих строк. Констрейнт живёт дольше:
Округление процента должно быть выбрано явно и в одном месте, с комментарием — почему вверх или вниз.
Если скидка считается на единицу товара и потом умножается на количество, копейка округления умножается вместе с ней.
Если скидка показывается или фискализируется построчно, сумма построчных долей обязана совпасть с общей до копейки. Приём один и тот же у троих: floor по всем долям, остаток раздаётся по одной копейке.
Правила — срок, процент, условия, признак «включён» — должны читаться в той же транзакции, что создаёт заказ, а не браться из проверки, сделанной раньше.
Если при перепроверке код оказался негодным и сумма из-за этого выросла — оплату проводить нельзя. Нужен явный отказ с прежней и новой суммой.
Если правило «один код на заказ» держится только порядком вызовов в коде, закрепите его уникальным индексом на идентификатор заказа в таблице применений.