Возвраты
Возврат делается из кабинета в пару кликов. Если касса умеет возвраты по API — деньги уходят клиенту через провайдера прямо из кабинета; если нет — возврат фиксируется в базе, а переводите вы сами. В обоих случаях транзакция помечается REFUNDED, юзер получает уведомление, а выданную подписку можно тут же отключить.
Включение
Возвраты по умолчанию выключены. Включаются настройкой REFUND_ENABLED в разделе «Настройки» (группа «Платежи») — пока она выключена, запрос возврата вернёт 403.
Как сделать возврат
- Раздел Платежи → вкладка «Транзакции» — у каждой завершённой транзакции есть кнопка ↩.
- Подтвердите возврат.
- Ответьте на вопрос «отключить выданную подписку?» — можно вернуть деньги и оставить доступ, а можно отозвать подписку сразу.
Вернуть можно только транзакцию в статусе COMPLETED — на любой другой статус кабинет ответит 409.
По API провайдера или вручную
| Кассы | Как уходят деньги |
|---|---|
| YooKassa, Cryptomus, Heleket, CloudPayments | Возврат по API провайдера: YooKassa — POST /v3/refunds (идемпотентный — один полный возврат на платёж), CloudPayments — POST /payments/refund по id транзакции |
| Все остальные | Record-only: транзакция помечается REFUNDED, кабинет предупредит «нет API-возврата — деньги нужно вернуть вручную» |
Если провайдер отказал в возврате, кабинет ответит 502 и транзакция останется COMPLETED — статус не поменяется, пока деньги реально не ушли.
Что происходит под капотом
- Статус меняется атомарно
COMPLETED → REFUNDED. Строка блокируетсяFOR UPDATE: двойной клик или два админа одновременно — второй запрос увидит уже сделанный возврат и получит 409, повторный возврат у провайдера не выстрелит. - Возврат пополнения баланса списывает сумму обратно с кошелька юзера — но никогда не уводит баланс в минус: если юзер уже потратил деньги, кабинет предупредит «на балансе меньше суммы возврата — баланс не списан».
- Каждый возврат пишется в аудит-лог: кто, какую транзакцию, через что вернул и отзывалась ли подписка.
Отключение подписки
Если при возврате выбрано «отключить подписку», база гасит её panel-first: сначала выключает юзера на панели Remnawave, затем помечает подписку DISABLED локально. Если панель в этот момент недоступна, подписка всё равно отключается локально сразу, а команда панели уходит в фоновую очередь ретраев — как только панель ответит, доступ будет закрыт. Кабинет предупредит: «панель недоступна — выключим подписку в фоне, как ответит».
Уведомление юзеру
После возврата юзер получает сообщение в Telegram по редактируемому шаблону «Возврат» (правится в кабинете вместе с остальными шаблонами уведомлений; шаблон можно и отключить). К сообщению добавляется комментарий, если вы указали его при возврате — удобно объяснить причину.
