[CL-W14] LOW-пачка №2: cooling-off double-pay, unsigned webhook кеш, idempotency на vault deposit #518

Merged
andrei merged 2 commits from feature/claude-w14-low-batch2 into master 2026-06-11 11:00:20 +00:00
Owner

Что сделано

LOW-пачка №2 из docs/vault/reviews/mega-audit-20260610.md (money-paths):

  • A1.2: requiresProviderRefund был true для STRIPE/NOWPAYMENTS, хотя код уже зачислил возврат на внутренний баланс пользователя — support, действуя по флагу (ручной Stripe card-refund), выплатил бы дважды. Флаг теперь всегда false; лживый комментарий «credit to platform balance» заменён правдой; trade-off с EU Directive 2011/83 («тем же платёжным средством») задокументирован в коде — настоящий card-refund flow (PENDING_PROVIDER_REFUND + идемпотентный stripe.refunds.create БЕЗ внутреннего кредита) — отдельное решение CEO.
  • B1.1: idempotencyGuard не кеширует 2xx-ответ, помеченный res.locals.webhookSignatureInvalid — подделанный IPN без подписи больше не резервирует idem-ключ и не кеширует 200 на 24h. Флаг ставит nowpayments.handler (его anti-probing 200 сохранён); Sumsub/OpenBanking шлют 401 → не кешировались и раньше.
  • B3.3: requireIdempotencyKey на POST /vault/deposit (double-submit = 2x дебет + 2x on-chain deposit + лишний газ); frontend vault.api.deposit шлёт ключ через общий generateIdempotencyKey.

Зачем

Закрытие money-path-хвоста мега-аудита: A1.2 — операционный двойной возврат; B1.1 — Redis-мусор от неподписанных IPN; B3.3 — паритет vault deposit с buyFromBalance/payouts по идемпотентности.

План тестирования

  • TDD: idempotency.test +2 кейса (forged-200 не кешируется / legit-200 кешируется) RED→GREEN; guard-тест W14-B3.3 RED→GREEN; 3 ассерта A1.2 перевёрнуты RED→GREEN
  • api vitest 725 файлов / 10085 GREEN; app api-тесты 80/80; tsc 0 (оба); eslint 0 (оба); mock-sync clean

Где могу ошибаться

  • A1.2: если где-то в admin-доках/runbook есть инструкция «по флагу делай card-refund» — она теперь мертва по построению (флаг всегда false), но сам runbook стоит вычистить (grep по репо нашёл флаг только в cooling-off.ts и его тестах)
  • B3.3: stale-клиенты без заголовка получат 422 с читаемым сообщением до перезагрузки бандла (тот же приёмлемый режим, что у W13 B1.2)

admin-merge reason: solo-dev (Rule 103.1)

  • local-review: Agent(code-reviewer) BLOCK -> 2 BLOCKER (лживые JSDoc + QA-inventory) + MEDIUM (type narrowing to false) addressed in 0c471912c -> effective APPROVE
  • ci-status: green (head 0c471912c)
  • local: api 725/10085 green, app api 80/80, tsc 0 оба, eslint 0 оба, mock-sync clean, TDD red->green по каждому фиксу
## Что сделано LOW-пачка №2 из `docs/vault/reviews/mega-audit-20260610.md` (money-paths): - **A1.2**: `requiresProviderRefund` был `true` для STRIPE/NOWPAYMENTS, хотя код **уже** зачислил возврат на внутренний баланс пользователя — support, действуя по флагу (ручной Stripe card-refund), выплатил бы дважды. Флаг теперь всегда `false`; лживый комментарий «credit to platform balance» заменён правдой; trade-off с EU Directive 2011/83 («тем же платёжным средством») задокументирован в коде — настоящий card-refund flow (PENDING_PROVIDER_REFUND + идемпотентный `stripe.refunds.create` БЕЗ внутреннего кредита) — отдельное решение CEO. - **B1.1**: `idempotencyGuard` не кеширует 2xx-ответ, помеченный `res.locals.webhookSignatureInvalid` — подделанный IPN без подписи больше не резервирует idem-ключ и не кеширует 200 на 24h. Флаг ставит nowpayments.handler (его anti-probing 200 сохранён); Sumsub/OpenBanking шлют 401 → не кешировались и раньше. - **B3.3**: `requireIdempotencyKey` на `POST /vault/deposit` (double-submit = 2x дебет + 2x on-chain deposit + лишний газ); frontend `vault.api.deposit` шлёт ключ через общий `generateIdempotencyKey`. ## Зачем Закрытие money-path-хвоста мега-аудита: A1.2 — операционный двойной возврат; B1.1 — Redis-мусор от неподписанных IPN; B3.3 — паритет vault deposit с buyFromBalance/payouts по идемпотентности. ## План тестирования - TDD: idempotency.test +2 кейса (forged-200 не кешируется / legit-200 кешируется) RED→GREEN; guard-тест W14-B3.3 RED→GREEN; 3 ассерта A1.2 перевёрнуты RED→GREEN - api vitest 725 файлов / 10085 GREEN; app api-тесты 80/80; tsc 0 (оба); eslint 0 (оба); mock-sync clean ## Где могу ошибаться - A1.2: если где-то в admin-доках/runbook есть инструкция «по флагу делай card-refund» — она теперь мертва по построению (флаг всегда false), но сам runbook стоит вычистить (grep по репо нашёл флаг только в cooling-off.ts и его тестах) - B3.3: stale-клиенты без заголовка получат 422 с читаемым сообщением до перезагрузки бандла (тот же приёмлемый режим, что у W13 B1.2) --- admin-merge reason: solo-dev (Rule 103.1) - local-review: Agent(code-reviewer) BLOCK -> 2 BLOCKER (лживые JSDoc + QA-inventory) + MEDIUM (type narrowing to false) addressed in 0c471912c -> effective APPROVE - ci-status: green (head 0c471912c) - local: api 725/10085 green, app api 80/80, tsc 0 оба, eslint 0 оба, mock-sync clean, TDD red->green по каждому фиксу
[CL-W14] fix(low-batch2): cooling-off double-pay footgun, unsigned webhook не кешируется, idempotency на vault deposit
All checks were successful
CI / Telegram Mini App (pull_request) Successful in 1m33s
CI / Contracts (pull_request) Successful in 1m59s
CI / Python SDK (pull_request) Successful in 25s
CI / Secrets Scan (pull_request) Successful in 12s
CI / Prisma Migrate Gate (pull_request) Successful in 1m26s
React Doctor / React Doctor / App (pull_request) Successful in 2m36s
CI / App (pull_request) Successful in 6m44s
CI / API (pull_request) Successful in 7m17s
2ee129a273
LOW-находки mega-audit-20260610:
- A1.2: requiresProviderRefund был true для STRIPE/NOWPAYMENTS при УЖЕ зачисленном
  внутреннем балансе — support по флагу оформил бы card-refund = двойная выплата.
  Флаг всегда false; лживый комментарий заменён; EU 2011/83 trade-off
  задокументирован (card-refund flow = отдельное решение CEO)
- B1.1: idempotencyGuard не кеширует 200 c res.locals.webhookSignatureInvalid —
  подделанный IPN без подписи не резервирует idem-ключ на 24h
- B3.3: requireIdempotencyKey на /vault/deposit; frontend шлёт ключ
TDD: idempotency.test +2, guard W14-B3.3, 3 ассерта A1.2 перевёрнуты; Rule 10
мок requireIdempotencyKey добавлен. api 725/10086, app api 80/80
[CL-W14] review: JSDoc и QA-inventory перестали инструктировать двойной возврат; тип сужен до false
All checks were successful
API Tests (QA suite) / API smoke (Playwright) (pull_request) Successful in 10s
CI / Python SDK (pull_request) Successful in 33s
CI / Secrets Scan (pull_request) Successful in 16s
CI / Telegram Mini App (pull_request) Successful in 1m44s
CI / Contracts (pull_request) Successful in 1m52s
CI / Prisma Migrate Gate (pull_request) Successful in 1m48s
React Doctor / React Doctor / App (pull_request) Successful in 3m9s
CI / App (pull_request) Successful in 7m11s
CI / API (pull_request) Successful in 7m36s
0c471912c0
2 BLOCKER + 1 MEDIUM code-review PR #518:
- JSDoc cancelWithinCoolingOff утверждал 'real-money refund must be processed
  separately via the payment provider' — ровно та инструкция double-pay,
  которую чинит A1.2; заменён правдой
- qa/api-tests inventory note 'returns requiresProviderRefund=true (provider
  refund must be done separately)' — машинно-читаемая ложь для QA-агентов;
  заменена явным 'do NOT issue a manual provider card-refund'
- тип requiresProviderRefund: boolean -> false (литерал) — truthy-ветка у
  любого будущего потребителя станет ошибкой компиляции
api-reference.md/endpoints.json — флага не содержат (проверено grep)
andrei merged commit cc184ed3b0 into master 2026-06-11 11:00:20 +00:00
andrei deleted branch feature/claude-w14-low-batch2 2026-06-11 11:00:20 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
europa-tech-srl/europatech!518
No description provided.