fix(tradein/payments): отказ банка на Init не блокирует покупателя на 30 минут — мёртвая попытка уходит из предиката живого платежа #3361

Merged
bot-backend merged 2 commits from fix/3323-payment-init-fail-409 into main 2026-09-05 19:19:01 +00:00

2 commits

Author SHA1 Message Date
ec8213c269 fix(payments): обрыв соединения и Init без PaymentURL перестают запирать покупателя
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
Три дыры в одном замке (строка без payment_url невидима для _find_live_payment,
но видима предикату UNIQUE 279 → ложный 409 на 30 минут):

- tbank_client ловил пару (TimeoutException, NetworkError): RemoteProtocolError,
  ProxyError и UnsupportedProtocol летели наружу голым httpx-типом мимо
  `except TBankApiError` в checkout. Ловим родителя — httpx.TransportError.
- Ветка «Success:true без PaymentURL» отвечала 502, не трогая статус, — здесь
  банк заказ ПРИНЯЛ. Тот же терминальный статус, error_code=no_payment_url;
  UPDATE вынесен в общий _mark_init_failed.
- _FakeDb в тестах применял статус по наличию ключа в params, а не по тексту
  SQL: мутант без `SET status = :status` оставался зелёным. Гейт по SQL —
  мутант краснит все три теста про замок.

Комментарий про «возможный холд» на ветке отказа Init поправлен: Init холд не
создаёт, авторизация идёт с оплаты формы, а форму покупателю не выдавали.
2026-09-06 00:11:35 +05:00
5bd9c586b7 fix(payments): отказ банка на Init больше не запирает покупателя на 30 минут
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m52s
Строка после провального Init оставалась в NEW и без payment_url: для
_find_live_payment (фильтр payment_url IS NOT NULL) её нет, а для предиката
UNIQUE миграции 279 — есть. Следующий checkout ловил конфликт и получал 409
'retry shortly' до истечения _ABANDONED_AFTER_MINUTES — из-за сбоя банка, а
не своего действия.

Переводим такую строку в терминальный DEADLINE_EXPIRED (тот же, которым
checkout уже помечает брошенные попытки) в том же UPDATE, что пишет
error_code/error_message: статус вне предиката 279, пара
(estimate_id, product_code) освобождается сразу. Новый статус в CHECK 233 не
заводим — миграция ради ярлыка не нужна, причина и так в error_*.

Refs #3323
2026-09-05 23:54:22 +05:00