17
апр 2026
LatestMinor
v1.4.0сегодня
Реальная оплата через YooKassa для чаевых, сертификатов и подписок
Закрыта последняя крупная дыра в платёжной логике — раньше чаевые, подарочные сертификаты и подписки активировались БЕЗ фактической оплаты, любой пользователь мог напечатать себе сертификат на 999 999 ₽ или активировать PRO-подписку за 0 ₽. Теперь все три проходят через единый flow с PaymentsService и активируются только в webhook payment.succeeded после реального списания денег.
Безопасность3Breaking1Новое5Инфраструктура2
- БезопасностьЧаевые теперь создаются в
PENDINGи активируются вPAIDтолько через webhook YooKassa. РаньшеTip.createсразу писалstatus='PAID' + paidAt: new Date()без какой-либо оплаты — мастеру шли «деньги», которых не было. - БезопасностьПодарочные сертификаты теперь создаются в
PENDING(зарезервированный код) и переводятся вACTIVEтолько webhook'ом.redeem()блокируетPENDING-сертификаты — раньше любой авторизованный пользователь мог дёрнутьPOST /gift-certificatesс amount=999999 и сразу использовать его на оплату своих броней. - БезопасностьПодписки и аддоны теперь создаются в
PENDING(новое значение вSubscriptionStatus). Активация вACTIVE+ обновлениеMasterProfile.subscriptionPlan/Status/ExpiresAtпроисходит атомарно в webhookpayment.succeeded. Раньшеsubscribe()сразу выдавал PRO/BUSINESS без оплаты. - BreakingТребуется миграция БД:
npx prisma migrate deployзапустит миграцию20260417_subscription_pending_status. ALTER TYPE ADD VALUE IF NOT EXISTS — не breaking, не требует обновления данных. - НовоеPaymentsService.handleWebhook расширен — switch по
paymentTypeпосле атомарного перевода Payment в PAID: - НовоеКаскадная отмена при
payment.canceled— связанная сущность переводится вFAILED/CANCELLED, освобождая ресурсы. - НовоеВсе активации идемпотентны через
updateManyс условиемstatus='PENDING'— повторный webhook от YooKassa не вызовет двойную активацию. - НовоеВсе активации завёрнуты в try/catch — падение downstream-операции не откатывает основной платёж (деньги уже списаны, разбираем руками).
- НовоеDemo-режим (без
YOOKASSA_SHOP_ID) синхронно эмулирует webhook — Tips/Cert/Subscriptions работают локально без реальной YooKassa. - Инфраструктура
PaymentsModuleпомечен@Global()— Tips/GiftCertificates/Subscriptions инжектятPaymentsServiceбез явного импорта. - Инфраструктура
PaymentTypeenum расширен значениемGIFT_CERTIFICATE.