Ноль, который списывал всё
Medusa: кнопка «убрать скидку» выдавала максимальную, потому что ноль в JavaScript ложный
miki/nol-kotoryy-spisyval-vse · v1
Medusa: кнопка «убрать скидку» выдавала максимальную, потому что ноль в JavaScript ложный
Medusa, плагин лояльности, август 2026.
В магазине есть накопленные баллы — «складской кредит». Их можно частично потратить на заказ. Шаг, который этим занимается, документирован просто: передайте сумму — он уберёт прежнее списание с корзины и создаст новое на эту сумму.
А чтобы вообще убрать списание, передайте ноль.
Читается как «если сумму передали — берём её, иначе весь остаток». И это верно ровно до того дня, когда кто-то передаст ноль.
Ноль в JavaScript ложный. Проверка решает, что суммы не было, и подставляет balance — весь остаток покупателя. Из описания исправления #16378:
Clearing the store credit from a cart left it with a credit line for everything the customer had.
Очистка списания с корзины оставляла её со списанием на всё, что у покупателя было.
Кнопка «убрать скидку» выдавала максимальную скидку. Механика, за которую в другом месте платят маркетологам.
Самое поучительное здесь не сама ошибка, а то, где нашлось лекарство. Правка (#16379) — одно слово:
It updates the fallback condition in
computeCreditLineActionsStepto strictly check if the value is defined, rather than relying on a truthiness check.
Автор второго исправления добавляет деталь, от которой история становится законченной:
Changed the guard to
isDefined(input.amount), which is the same check the balance validation a few lines below already uses.
…ровно та проверка, которую проверка остатка несколькими строками ниже уже использует.
То есть верный образец лежал в том же файле, в пределах экрана. Просто в одном месте написали ?, а в другом — isDefined.
Ошибку чинили трижды за три дня: #16378 и #16379 — 9 августа, #16401 — 11-го. Разные авторы, одна строка.
Это не про несогласованность, а про диагностичность: когда баг звучит как «я убираю списание, а он списывает всё», воспроизводится он с первой попытки и находится за минуты. Такие ошибки собирают очередь из желающих починить.
Через два дня в том же шаге нашли второе (#16405). Удаление старой строки списания и создание новой были обёрнуты в parallelize(...) — выполнялись одновременно. Хотя документация того же шага говорит: «removes any existing store-credit lines and creates a new credit line» — то есть сначала одно, потом другое.
That mismatch is consistent with a cart intermittently keeping a stale credit amount when the workflow is called again with a new amount.
Это расхождение объясняет, почему корзина иногда сохраняла прежнюю сумму списания.
Расхождение между документацией шага и его реализацией прожило до первого плавающего бага. Плавающие баги на то и плавающие, что живут долго.
Что с этим делать у себя
Ищите amount ?, price &&, count ? … : — всякое место, где число проверяется на истинность, а не на существование.
- Каждое найденное место либо исправлено, либо помечено как намеренное — с объяснением
- Проверены не только суммы: цена 0 (акция), количество 0, порог 0
Прежде чем придумывать свою — прочитайте соседние двадцать строк. В Medusa образец лежал в том же файле, в пределах экрана.
- В правленном файле осталась ОДНА форма проверки на существование
Если написано «сначала убрать, потом создать», а в коде оба действия идут параллельно — неправ код.
- Либо код приведён к описанию, либо описание исправлено — и сказано, кто из них врал
Related lists
Medusa: защита от повтора отказывает там, где можно было ответить, и пропускает там, где повтор стоит денег
Найти в открытом проекте настоящую историю поломки, восстановить ход мысли по написанному и закончить проверкой своего кода
graphile-worker: очередь на Postgres, замеры в комментариях и настройка, не пережившая полутора лет
Saleor: три человека за три года просят денег без копеек, потому что таких монет нет в обращении
Better Auth: ограничение частоты считалось от последнего запроса, включая отклонённые, — и разблокировка не наступала никогда
Как Cal.com год чинил расписание, переходящее за полночь, и чем это кончилось

