Абонементы и удержание

Как организовать смену тарифа абонемента и избежать ручных перерасчётов

Клиент хочет больше возможностей, временно сокращает расходы или меняет формат тренировок. Разбираем, как провести переход прозрачно для клиента, кассы, ресепшена и бухгалтерии.

01

Начните с правил, которые понимают клиент и сотрудник

Смена тарифа кажется простой операцией, пока клуб не сталкивается с активным сроком, использованными занятиями, скидкой и оплатой через разные каналы. Менеджер часто считает разницу вручную, записывает договорённость в комментарий и просит кассира провести произвольную сумму. В результате клиент видит одну цифру, касса другую, а новый лимит занятий появляется только после дополнительной корректировки.

Хорошее правило отвечает на пять вопросов до нажатия кнопки. Как определяется стоимость неиспользованного остатка? Когда начинает действовать новый тариф? Что происходит с уже использованными занятиями? Как подтверждается доплата? Кто может отменить запланированный переход? Ответы нужно закрепить в продуктовой политике клуба и реализовать одинаково в админке, клиентском приложении и серверной логике.

Для действующего срочного абонемента удобно считать остаток по календарным дням. Цена плана делится на полную длительность, затем умножается на число оставшихся дней. Такой подход легко объяснить и проверить. Пакетные продукты с отдельной стоимостью занятия могут требовать другой политики. Их лучше выделить в отдельный тип расчёта, а не смешивать формулы в одной ручной операции.

  • зафиксируйте поддерживаемые типы тарифов;
  • опишите дату вступления изменений в силу;
  • согласуйте формулу остатка с финансами;
  • определите права сотрудника и клиента;
  • подготовьте тексты для положительных и ошибочных сценариев.

02

Разделите немедленное повышение и будущий переход

Повышение тарифа обычно связано с доплатой и желанием клиента получить новые возможности сразу. Для него подходит сценарий immediate upgrade. Система рассчитывает разницу, создаёт платёжное намерение и применяет тариф после подтверждения денег. До этого момента членство продолжает действовать по прежним правилам.

Понижение тарифа логичнее применять со следующего оплаченного периода. Клиент уже купил текущий набор услуг, клуб уже признал обязательство. Мгновенное уменьшение лимитов создаёт вопросы о возврате, использованных занятиях и доступе в зоны. Scheduled change сохраняет текущий тариф до окончания срока и заранее фиксирует, какой план включится при продлении.

Это разделение полезно и для интерфейса. При upgrade основная кнопка говорит о сумме и переходе к оплате. При будущем изменении кнопка называет дату применения. Пользователь сразу понимает результат действия. Попытка спрятать оба сценария за общей кнопкой приводит к длинным предупреждениям и ошибкам сотрудников.

Клуб может разрешить будущий переход и на более дорогой тариф, например когда клиент хочет сохранить текущий формат до конца месяца. Серверная модель должна поддерживать режим явно. Политика интерфейса может рекомендовать немедленное повышение, сохраняя выбор там, где он действительно нужен.

03

Показывайте предварительный расчёт как отдельный шаг

Preview нужен до создания платежа или расписания. Он собирает актуальное членство, исходный план, целевой план и дату, затем возвращает направление перехода, оставшиеся дни, стоимость остатка, доплату, валюту и будущие лимиты. Сам preview ничего не меняет и может безопасно вызываться при каждом выборе тарифа.

Расчёт должен выполняться на сервере. Если браузер самостоятельно вычитает цены, пользователь сможет увидеть сумму, которая расходится с кассой из-за округления, смены даты или устаревшего плана. Сервер использует денежный тип с фиксированной точностью и одну политику округления. Интерфейс форматирует готовое значение для языка клиента.

В расчёте важно показать основу. Вместо одной строки «доплата 160 000 сум» добавьте оставшийся срок, стоимость текущего остатка и стоимость нового остатка. Сотрудник сможет объяснить цифру без обращения в поддержку. Клиент увидит, что клуб не списывает полную разницу между месячными ценами, если часть срока уже прошла.

Проверяйте, что оба тарифа принадлежат одной организации и активны. Запрещайте переход в тот же план. Учитывайте статус членства и незавершённые финансовые процессы. Чёткий код ошибки позволяет интерфейсу сказать, что именно требуется: закрыть текущую оплату, выбрать другой план или обратиться к сотруднику с нужным правом.

  • исходный и целевой тариф;
  • полный и оставшийся срок;
  • стоимость обоих остатков;
  • сумма и валюта доплаты;
  • будущие лимиты членства.

04

Замораживайте условия в момент создания оплаты

Между расчётом и подтверждением платежа может пройти несколько минут или часов. За это время администратор способен изменить цену тарифа, срок или число занятий. Платёж нельзя пересчитывать задним числом, потому что клиент уже согласился с конкретной суммой. Поэтому payment intent хранит неизменяемый снимок preview.

В снимок входят идентификаторы планов, даты, денежные значения, будущий баланс и версия алгоритма. Подписанный callback провайдера применяет именно этот снимок. Текущая карточка тарифа используется для проверки доступности, но не меняет согласованную сумму. Новая попытка после истечения intent получает свежий расчёт и отдельный ключ.

Открытие платёжной ссылки не является подтверждением денег. Возврат пользователя из Click или Payme тоже не подходит как источник истины. ProGym ждёт server-to-server callback, проверяет подпись, сумму, идентификатор операции и состояние intent. Повторное уведомление возвращает уже записанный результат.

Идемпотентность нужна на создании intent и на его выполнении. Одинаковый ключ с теми же параметрами возвращает прежний объект. Тот же ключ с другим целевым тарифом сообщает о конфликте. Такой контракт защищает от двойного клика, повторной отправки формы, сетевого таймаута и параллельной обработки callback.

05

Сохраняйте использованные занятия при повышении

У клиента есть общий лимит и доступный остаток. Если план на восемь занятий меняется на шестнадцать после трёх посещений, новый остаток должен составить тринадцать. Простая замена остатка на шестнадцать подарит уже использованные занятия второй раз. Сброс до разницы в восемь, наоборот, заберёт пять доступных занятий старого плана.

Надёжная формула сначала определяет использованное количество: общий лимит минус остаток. Затем вычитает это число из лимита нового плана. Для переходов со сложными правилами можно хранить отдельные entitlement, но принцип остаётся тем же: история фактического потребления не переписывается.

При переходе со следующего периода старые занятия обычно обнуляются в дату окончания, а новое членство получает лимит целевого плана. Если клуб разрешает перенос остатка, правило должно быть отдельным и видимым в preview. Скрытая ручная корректировка разрушает отчётность и создаёт разные ответы у сотрудников.

Каждое изменение баланса записывайте в ledger с причиной и итоговым остатком. Запись связывается с mutation смены тарифа. Тогда сотрудник может открыть историю и увидеть, как именно сформировалось значение, а руководитель отличает продажи от корректировок и переходов.

06

Планируйте переход как версионируемую операцию

Запланированная смена хранит исходный план, целевой план, дату применения, причину, комментарий, preview и статус. На одно членство достаточно одной активной записи. Если клиент передумал, текущую запись отменяют и создают новую. Так история остаётся последовательной, а фоновый обработчик получает однозначное действие.

Отмена использует expected version. Сотрудник, открывший старую карточку, не должен отменить переход, который уже применился или был изменён в другом окне. Сервер сравнивает версию и сообщает о конфликте. Интерфейс обновляет данные и показывает фактическое состояние.

Наступившие переходы обрабатывает системный loop с областью конкретной организации. Он блокирует запись, повторно проверяет статус и применяет изменение один раз. Успешный результат создаёт append-only mutation, запись ledger при необходимости, audit и доменное событие. Ошибка одной организации не должна останавливать обработку остальных.

Особое внимание требуется границе продления. Клиент может открыть оплату в день, когда scheduled change уже наступил, но приложение ещё передаёт идентификатор старого плана. Сервер сначала применяет due change, затем заменяет исходный план целевым и создаёт продление по новой цене. Этот сценарий нужно закрепить интеграционным тестом на PostgreSQL.

07

Дайте админке и клиенту одинаковую фактическую картину

В рабочем месте сотрудника рядом с абонементом нужны четыре действия: сравнить тарифы, создать ссылку оплаты, запланировать переход и отменить ожидающий план. Ниже показывается история с датой, направлением, суммой, статусом и автором. Кнопки зависят от прав роли и состояния операции.

Клиентское приложение использует тот же Core API через защищённый BFF. Клиент выбирает доступный тариф, получает preview, подтверждает оплату или дату следующего периода. Служебные ключи, внутренние комментарии и чужие членства в проекцию не входят. Telegram авторизация связывает сессию с проверенной клиентской identity.

На телефоне диалог удобно показывать как bottom sheet. Заголовок и кнопка закрытия остаются видимыми, длинная история прокручивается внутри, основное действие доступно в нижней части. Фокус переходит в диалог и возвращается на кнопку после закрытия. Escape, Tab, экранный диктор и крупный масштаб проверяются отдельно.

Тексты должны называть действие и результат. «Оплатить 160 000 сум и перейти сейчас» понятнее общего «Подтвердить». Для будущего перехода укажите точную дату. После запроса показывайте итоговый статус, а при ошибке сохраняйте выбранный тариф и объясняйте следующий шаг.

08

Ограничьте права и сохраните доказуемую историю

Просмотр preview можно дать сотруднику с правом чтения членства. Создание платежа, расписания и отмена требуют права управления абонементами. Системный loop работает через отдельный технический контекст. Все запросы проверяют organization identifier, а составные внешние ключи и row level security защищают связи между tenant.

Результат смены тарифа храните как append-only mutation. В ней есть снимок до и после, источник, идентификатор schedule или payment, сумма, валюта, actor и idempotency key. Обновление или удаление такой записи блокируется на уровне базы. Исправление оформляется следующей операцией с отдельным основанием.

Audit отвечает на вопрос, кто и через какой интерфейс инициировал действие. Доменное событие сообщает автоматизациям о запланированном, отменённом или применённом переходе. Событие не должно содержать платёжный секрет или лишние персональные данные. Уведомление клиента обрабатывается отдельно и не откатывает подтверждённую финансовую операцию при временной ошибке доставки.

Для Taska Core capability оформляется аддитивно. Общие модели и платёжный purpose расширяются совместимо, а operator scopes включаются только в manifest ProGym. OneSwim, Flowa, Restora и другие продукты сохраняют прежний состав операторов и поведение. Такой product gate позволяет развивать фитнес-функции без скрытого изменения соседних решений.

09

Соберите приёмку из реальных положительных и отрицательных сценариев

Начните с месячного абонемента и двух планов. Например, базовый стоит 300 000 сум, премиальный 600 000 сум, до конца осталось шестнадцать дней из тридцати. Ожидаемая доплата равна разнице дневной стоимости за остаток: 160 000 сум. Сохраните preview и проверьте одинаковое отображение в админке и приложении.

Создайте payment intent, затем измените цену премиального плана. Callback должен применить прежние 160 000 сум и сохранённые лимиты. Отправьте callback повторно и убедитесь, что mutation и ledger не дублируются. Повторите создание с тем же idempotency key и другими параметрами, система должна вернуть конфликт.

Проверьте баланс занятий. При лимите восемь и остатке пять после перехода на шестнадцать ожидается остаток тринадцать. Затем создайте downgrade, отмените его с правильной версией и повторите отмену с тем же ключом. История должна показать один результат отмены. Попытка со старой версией получает конфликт.

Создайте новый scheduled change и перенесите дату на сегодня в тестовом контуре. Запустите два обработчика. Только один применяет переход. Затем повторите сценарий в момент продления, передав старый plan id. Новый payment intent должен ссылаться на целевой план и его цену.

Завершите проверкой ролей, tenant isolation и мобильного интерфейса. Сотрудник с чтением видит preview, но не создаёт платёж. Клиент получает только собственное членство. Данные другой организации недоступны даже при известном идентификаторе. Диалог работает на ширине 390 пикселей, с клавиатуры и экранным диктором. Русская, узбекская и английская версии сохраняют смысл сумм, дат и действий.

  • точный дневной перерасчёт;
  • неизменяемый снимок оплаты;
  • сохранение использованных занятий;
  • идемпотентность и контроль версии;
  • гонка продления и scheduled change;
  • права, tenant isolation и мобильная доступность.

Демонстрация

Проверьте сценарии на примере своего клуба

Подготовим роли, филиал, расписание и путь клиента. Покажем работающие функции в системе.

Покажем CRM, расписание, кассу, СКУД и приложения на сценарии вашего клуба.