Перейти к содержанию

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

Курс добавил заказы, платежи, подписку и возвраты. Теперь нужно определить, как они ограничивают конкретную функцию. Если обработчик просто проверяет «последний платеж succeeded», он может разрешить доступ после окончания периода или запретить его из-за отменённого нового платежа, хотя старый период ещё оплачен.

В «Пространстве» право на платную функцию хранится как entitlement: feature, интервал, orderId и отметка отзыва. Сервер проверяет его вместе с текущей компанией и ролью. Team даёт advanced_export, а базовые проекты остаются доступными без оплаты. Полная интеграция находится в архиве биллинга; приложения и экспорты при подготовке не запускались.

Право имеет предметное название

Feature отвечает на вопрос, что разрешено сделать, например расширенную выгрузку проектов и задач. PlanCode отвечает на вопрос, какое предложение куплено. Эти значения не нужно считать одним понятием. Несколько тарифов могут давать одну функцию, а один тариф — несколько разных прав.

Ниже — полный SQL проверки оплаченного grant в ограниченном сеансе. @now получен серверными часами, @organization — доверенной областью. Повторный успешный webhook не создаёт дополнительные интервалы, поскольку grant связан с одним заказом.

SELECT EXISTS (
  SELECT 1 FROM entitlements
  WHERE organization_id=@organization
    AND feature='advanced_export'
    AND starts_at<=@now
    AND through_until>@now
    AND revoked_at IS NULL
);

Through_until сравнивается строго больше текущего момента. На границе старый интервал уже завершён, следующий может начаться в этот же момент. Право не зависит от открытой вкладки и локальных часов браузера. Пользователь может изменить часы устройства, но серверный результат не изменится.

Если у компании есть несколько подходящих grant, EXISTS достаточно для разрешения одной функции. Для квоты или количества мест потребуется другая модель: значение лимита и правило суммирования либо выбора. Нельзя автоматически складывать лимиты всех старых заказов, потому что продления могли перекрывать друг друга или относиться к одной подписке.

Оплата не заменяет роль участника

Компания приобрела Team, но viewer не должен создавать экспорт, если матрица продукта требует для него роль editor. Платное право относится к организации, а роль — к человеку внутри неё. Операция должна пройти обе проверки. В нашем контракте advanced export требует разрешение write или отдельное явно выбранное право экспорта, как описано README.

Состояние организации также проверяется независимо. Read_only допускает экспорт в SaaS-контракте; платное entitlement может быть активным или отсутствовать. Deleting не допускает новую пользовательскую выгрузку, даже если оплаченный срок ещё не завершился. Покупка не отменяет решение владельца закрыть компанию.

Не следует возвращать один общий «нет доступа» для всех этих причин, когда пользователь уже состоит в компании. Предметный код paid_feature_required помогает интерфейсу показать тариф, permission_denied — обратиться к владельцу, а organization_unavailable — объяснить состояние. При отсутствии членства по-прежнему не раскрывается существование компании.

Проверка ставится в сам обработчик

Маршрут /orgs/{organizationId}/exports/advanced сначала проходит tenant-фильтр, затем открывает транзакцию, повторно проверяет роль, состояние и entitlement. Только после этого создаёт задание. Скрытие кнопки покупки либо доверие к клиентскому plan: team не участвуют в решении.

Worker перед выполнением повторно проверяет актуальное право инициатора и grant компании. Между постановкой и обработкой мог произойти полный возврат либо истечение срока. Если продукт обещает завершать все принятые до границы задания, он может фиксировать разрешение в момент принятия, но это другой контракт. Учебный пример выбирает актуальную проверку и объясняет возможный отказ поздней задачи.

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

Отмена, продление и возврат дают разные эффекты

После cancel_at_end текущий entitlement продолжает действовать до through_until. Scheduler перестаёт создавать новую покупку. Если pending-продление отменено, оно не выдаёт нового grant, но старый оплаченный интервал остаётся неизменным. Полный возврат конкретного заказа отзывает его grant по правилу предыдущего урока.

Эта модель объясняет, почему проверка subscription.state == active недостаточна. Cancel_at_end может всё ещё иметь оплаченный доступ. Active с просроченным pending-продлением может уже не иметь права. State управляет расписанием, а entitlement — основанием конкретной функции в текущий момент.

Также «последний платеж компании» не является стабильным источником доступа. Последняя попытка может быть canceled, но предшествующий период ещё действует. Или succeeded-платёж относится к старому истёкшему месяцу. Сохранённый интервал и связь с заказом дают более точный ответ без догадки по времени последних строк.

Сквозной локальный сценарий

Сначала Анна читает каталог и оформляет Team v1 с ключом команды. Ожидается один open-заказ и pending fixture. Продвинутая выгрузка пока получает paid_feature_required. Затем локальный fixture переводится в succeeded, а webhook либо сверка применяет подтверждённый снимок.

После commit появляется grant advanced_export с границами заказа. Анна создаёт расширенный экспорт, worker формирует JSON, защищённый маршрут выдаёт результат. Повтор webhook не двигает границу. Борис, подставив UUID северного задания в область «Юга», по-прежнему получает 404: платная функция не обходит изоляцию компаний.

Далее Анна отключает продление. Доступ сохраняется до границы, а tick не создаёт следующий заказ. В альтернативном сценарии полный fixture refund отзывает grant раньше; новые advanced exports получают отказ. История сохраняет оплату и возврат отдельно. Эти эффекты описаны как ожидаемые, без утверждения о выполненном прогоне.

Архив является завершённым учебным снимком с теми же UUID и базовыми contracts SaaS. README отдельно описывает новую базу и добавление billing-схемы к локальному baseline. Это не автоматическая миграция произвольной production-версии: перед переносом нужно сопоставить права, названия маршрутов, календарный договор и реальный адаптер. Связь двух курсов помогает понять механизм, но не заменяет такой разбор интеграции.

Вы получили полный локальный путь от серверного предложения до платной функции. Для дальнейшего развития выберите один самостоятельный результат: лимит мест, смена тарифа, grace period либо настоящий тестовый адаптер. Каждый потребует своей политики и сохранённого состояния; расширять модель полезнее через явный контракт, чем через новое значение в единственном поле «подписка активна».

Оглавление серии