Баланс пользователя
Баланс пользователя — опциональный внутренний кошелёк Minishop. Пользователь может пополнить его через обычного платёжного провайдера и затем полностью или частично оплачивать подписку, продление, докупку трафика и устройств. Средства учитываются в неизменяемом журнале операций, а не в одном перезаписываемом поле пользователя.
Обычный баланс и баланс партнёрской программы — разные источники средств. Обычный баланс нельзя вывести. Администратор может конвертировать средства между источниками, но перевод из обычного баланса в партнёрский остаётся невыводимым и поэтому не превращает пользовательское пополнение в доступный к выплате партнёрский доход.
Включение и настройка
Заголовок раздела «Включение и настройка»Откройте Система → Настройки → Общие → Баланс пользователя. Настройки доступны как overrides
поверх .env:
| Переменная | Назначение |
|---|---|
USER_BALANCE_ENABLED |
Показывает баланс в личном кабинете и разрешает пополнение и оплату из него. По умолчанию выключен. |
USER_BALANCE_CURRENCY |
Код валюты баланса. Пустое значение наследует валюту платежей из DEFAULT_CURRENCY_SYMBOL. |
USER_BALANCE_TOPUP_MIN_AMOUNT |
Минимальная сумма одного пользовательского пополнения. |
USER_BALANCE_TOPUP_MAX_AMOUNT |
Максимальная сумма одного пользовательского пополнения. Не может быть меньше минимальной. |
USER_BALANCE_TOPUP_PRESETS |
JSON-массив быстрых сумм, например [500, 1000, 2000, 5000]. Значения вне диапазона отбрасываются. |
Валюта нормализуется к верхнему регистру. Для известных валют учитывается их реальная точность: например, RUB/USD/EUR имеют две дробные цифры, а валюты без дробной части работают целыми единицами. Интерфейс показывает локализованный символ, когда он известен, и код валюты в остальных случаях.
После смены USER_BALANCE_CURRENCY старые записи журнала не конвертируются автоматически. Они
остаются в исходной валюте, а интерфейс и новые операции работают с выбранной текущей валютой.
Поздний callback уже созданного пополнения зачисляет деньги в валюте исходного счёта, чтобы смена
настройки не привела к потере оплаченных средств. Перед сменой валюты всё равно рекомендуется
завершить открытые платежи и зафиксировать порядок переноса остатков отдельно.
Пользовательский сценарий пополнения
Заголовок раздела «Пользовательский сценарий пополнения»- Пользователь нажимает кнопку пополнения на главной странице или в настройках личного кабинета.
- Вводит сумму либо выбирает один из настроенных пресетов.
- Выбирает доступного внешнего платёжного провайдера.
- Minishop создаёт обычный
Paymentс режимом продажиbalance_topup. - После подтверждённого callback сумма один раз записывается в журнал баланса и становится доступной для покупок. Подписка при этом не активируется и не продлевается.
Telegram Stars, обычный и партнёрский баланс не предлагаются как способы пополнения: это исключает циклические операции и смешение денежных контуров. Провайдер также должен поддерживать выбранную валюту и сумму. Ограничение частоты запросов применяется до создания платежа.
Повторный callback безопасен: платеж блокируется на время финализации, завершённый платеж повторно
не проводится, а запись пополнения имеет ключ user-balance-payment-topup:<payment_id>. Успешное
пополнение появляется в Система → Логи с пользователем, суммой, валютой, провайдером и ID
платежа; те же основные поля пишутся в технический лог backend. Если включены LOG_PAYMENTS и
LOG_CHAT_ID, после успешного коммита в платёжную тему/чат Telegram отправляется отдельное
уведомление «Пополнен баланс пользователя» с теми же реквизитами. Оно не содержит фиктивного
периода подписки и не отправляется при повторном callback.
Refund или reversal успешного пополнения создаёт отдельную отрицательную запись и не меняет исходную. Если пользователь уже успел потратить деньги, итоговый баланс может стать отрицательным. Новые списания и ручные операции, требующие доступных средств, блокируются до погашения долга.
Оплата из баланса
Заголовок раздела «Оплата из баланса»На checkout пользователь включает оплату с баланса. Если одновременно доступны обычный и партнёрский источники, по умолчанию выбирается обычный баланс, а источник можно изменить через выпадающее меню.
- Если выбранный баланс покрывает всю серверную котировку, создаётся внутренний успешный
Paymentдля аудита и запускается обычная финализация покупки без внешнего провайдера. - При частичной оплате провайдеру передаётся остаток. Minishop сохраняет полную стоимость и долю, зарезервированную на выбранном балансе.
- Если у провайдера есть минимальная внешняя сумма, баланс применяется только так, чтобы остаток не оказался ниже этого минимума.
- Валюта checkout должна совпадать с валютой выбранного баланса.
- Во время создания платежа пользователь и остаток блокируются транзакцией. Параллельные запросы не могут дважды потратить одну и ту же сумму.
- Для отменённого, истёкшего или неуспешного внешнего платежа резерв возвращается отдельной ledger-записью. Если поздний callback всё же завершил оплату, возврат помечается неактивным и списание остаётся в силе.
- Фоновая сверка возвращает зависшие резервы полностью оплаченных с баланса покупок, если процесс завершения аварийно остановился, и повторно проверяет терминальные платежи без записи возврата.
Правила партнёрского баланса, включая hold, отрицательные reversal и доступность оплаты, описаны в документации партнёрской программы.
Администрирование
Заголовок раздела «Администрирование»Когда функция включена, текущий обычный баланс показывается в основной информации карточки пользователя и отдельной колонке списка пользователей. При включённой партнёрской программе рядом показывается партнёрский остаток в валюте обычного баланса. Список получает остатки пакетными агрегатами для текущей страницы и не выполняет отдельный запрос на каждого пользователя.
В карточке пользователя раздел управления балансом позволяет:
- добавить, списать или установить точную сумму обычного баланса;
- оставить обязательную для операционного процесса причину/комментарий (поле технически допускает пустое значение и тогда записывается стандартная причина);
- конвертировать средства между обычным и партнёрским балансом активного партнёра;
- кнопкой Макс подставить весь доступный остаток выбранного источника;
- просмотреть последние операции неизменяемого журнала.
Списание и конвертация не допускают сумму выше актуального остатка. Проверка в интерфейсе нужна для удобства, но окончательная проверка выполняется сервером после блокировки пользователя и партнёрского профиля. Ключ идемпотентности защищает повторную отправку формы.
При конвертации действуют дополнительные правила:
- нужен активный партнёрский профиль; paused/closed-профиль не подходит;
- обе стороны используют
USER_BALANCE_CURRENCY, поэтому смешивать валюты одной операцией нельзя; - при переводе партнёрских средств в обычные сначала расходуется невыводимая часть партнёрского остатка, затем выводимая;
- перевод обычного баланса в партнёрский создаёт невыводимую партнёрскую запись;
- обе ledger-записи создаются в одной транзакции: частично выполненной конвертации быть не может.
Отключение и эксплуатация
Заголовок раздела «Отключение и эксплуатация»Выключение функции скрывает обычный баланс и блокирует новые пользовательские пополнения и оплаты, но не удаляет историю и остатки. Администратор по-прежнему видит журнал в карточке пользователя и может подготовить остатки к повторному включению. Не удаляйте ledger-записи вручную и не исправляйте итог прямым SQL — используйте корректировку с причиной.
Для проверки инцидента сопоставляйте:
- запись
Paymentи еёsale_mode, статус, сумму, валюту и провайдера; - запись журнала баланса по
reference_type=paymentиreference_id=<payment_id>; - событие
balance_topup_succeededв админских логах; - callback и технические сообщения провайдера в backend.
Если платёж успешен у провайдера, но баланс не зачислен, не добавляйте сумму вручную до проверки
user-balance-payment-topup:<payment_id>: повторная ручная корректировка поверх позднего retry
приведёт к двойному зачислению.