Перейти к содержимому
getnextpdf.com

Enterprise редакция

Billing — глубокий справочник

Эта страница — подробный справочник по поверхности биллинга NextPDF Enterprise. У поверхности два слоя. Модель биллинга в NextPDF\Enterprise\Billing определяет тарифные уровни, квоты, политики превышения и дедуплицированные оповещения об использовании. Субстрат принуждения в NextPDF\Enterprise\Billing\Substrate размещает эту модель на живом пути запроса — отказоустойчиво (fail-closed) и безопасно при конкурентности. Точки входа: PlanRegistry, QuotaManager, OverageCalculator, BillingAlertService и QuotaEnforcementGuard. Руководство уровня рабочего процесса см. на странице возможности «Биллинг».

Эта возможность поставляется в NextPDF Enterprise (nextpdf/enterprise) и активируется лицензионным конвертом уровня Enterprise. Развёртывание без этого права не загружает классы возможности. Сравните редакции и получите лицензию.

Биллинг — это базовая возможность Enterprise без отдельного флага для каждой функции; она доступна, как только пакет Enterprise установлен рядом с пакетом Core. У NextPDF Core (Apache-2.0) и NextPDF Pro нет модели тарифов, квот или превышения; у этой поверхности нет эквивалента более низкого уровня. Включения тарифов, квоты и коммерческие условия регулируются лицензионным соглашением, а не принуждением во время выполнения; этот справочник не является юридическим или договорным заключением.

Все символы находятся в NextPDF\Enterprise\Billing. Строки, помеченные substrate, находятся в NextPDF\Enterprise\Billing\Substrate. TenantContext — это тип аутентифицированного арендатора из NextPDF\Enterprise\SaaS.

СимволПараметрыПоведение по умолчаниюВозвращаетБросает или завершается сПримечания
SaaSPlan (enum)Строковые тарифные уровни: standard, advanced, high_controlНе бросаетlabel() возвращает отображаемое имя
PlanDefinition::__constructSaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncludedНеизменяемый объект-значение тарифа; хранит входные данные как естьНовый экземплярНе бросаетfinal readonly; продвинутые публичные свойства
PlanDefinition::includesCapabilityCapabilityCode $capabilityПроверка вхождения по строгой идентичностиboolНе бросает
PlanRegistry::__constructlist<PlanDefinition> $definitionsИндексирует определения по уровню; последнее определение для уровня побеждаетНовый реестрНе бросаетДля тестов и white-label наборов тарифов
PlanRegistry::getSaaSPlan $planКанонический поиск тарифаPlanDefinitionInvalidArgumentException, когда тариф не зарегистрирован
PlanRegistry::hasSaaSPlan $planПроверка регистрацииboolНе бросает
PlanRegistry::defaultRegistry (static)Продакшен-умолчания: Standard 1,000 CU; Advanced 5,000 CU плюс Intelligence Pack; High Control 20,000 CU плюс Intelligence и Privacy PackPlanRegistryНе бросаетИспользуйте, если договорные условия не требуют пользовательских определений
OveragePolicy (enum)hard_stop, soft_stop, budget_alertНе бросаетhttpStatusCode() сопоставляет 402 / 429 / 200; isBlocking() истинно только для hard и soft stop
QuotaManager::__constructPlanRegistry $planRegistry, OveragePolicy $overagePolicyСвязывает реестр с одной политикойНовый экземплярНе бросает
QuotaManager::checkQuotaTenantContext $tenant, SaaSPlan $plan, float $currentCuМолча возвращает при потреблении на уровне квоты или ниже, либо при неблокирующей политикеvoidQuotaExceededException при строгом превышении под блокирующей политикой; InvalidArgumentException из реестра при незарегистрированном тарифеresetsAt = первый день следующего месяца, полночь UTC
QuotaManager::remainingQuotaSaaSPlan $plan, float $currentCuЧистое чтение; никогда не блокируетfloatInvalidArgumentException реестраОтрицательно при превышении
QuotaManager::usagePercentageSaaSPlan $plan, float $currentCuЧистое чтение; никогда не блокируетfloatInvalidArgumentException реестра0.0, когда включённая квота неположительна; выше 1.0 при превышении
OverageCalculator::calculatePlanDefinition $plan, float $currentCuВычисляет неизменяемый снимок превышенияOverageResultНе бросаетfinal readonly, без состояния
OverageResultincludedCu, usedCu, overageCu, usageRatio, isOverageНеизменяемый результат вычисленияНе бросаетoverageCu = max(0, used - included); isOverage требует строгого превышения
BillingAlertType (enum)quota_warning_80, quota_warning_100, budget_exceeded, monthly_cap_reachedНе бросаетthreshold() 0.8 / 1.0 / 1.0 / 1.0; severity() warning / critical / critical / critical
BillingAlertService::__constructAlertStateRepositoryInterface $alertStateСвязывает хранилище дедупликацииНовый экземплярНе бросает
BillingAlertService::evaluateTenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCuСрабатывают ещё не сработавшие оповещения в порядке возрастания порогов и записываютсяlist<BillingAlertType>InvalidArgumentException при несоответствии тарифа/определенияКлюч дедупликации: арендатор, тип, период UTC YYYY-MM
BillingAlertService::clearAlertsTenantContext $tenantОчищает состояние срабатывания арендатора за текущий период UTCvoidОшибки, определённые репозиторием, распространяютсяПеревзводит оповещения в том же периоде
AlertStateRepositoryInterfacehasAlertFired(), markAlertFired(), clearForPeriod()Контракт долговечного хранения дедупликации оповещенийПо методуОпределяется реализациейОператор владеет долговечностью между репликами
InMemoryAlertStateRepositoryСостояние срабатывания на основе массиваПо интерфейсуНе бросаетТолько для жизненных циклов одного запроса и тестов
QuotaExceededExceptionReadonly currentCu, limitCu, resetsAt, tenantId, isSaaSОтказ по квоте с учётом режима развёртыванияЯвляется исключениемhttpStatusCode() 402 SaaS / 403 on-prem; specCode() SPEC-BILLING-003 / SPEC-LIC-001; toErrorEnvelope() возвращает структурированное тело ошибки
DeploymentMode (enum)saas, self_hosted_oss, local_developmentНе бросаетSubstrate. enforcesQuota() истинно только для Saas; отказ от участия всегда явный
QuotaEnforcementGuard::__constructDeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterfaceСобирает живой шлюз квотНовый экземплярНе бросаетSubstrate. final readonly
QuotaEnforcementGuard::enforce?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0Отказоустойчивый (fail-closed) шлюз квот с атомарным резервированиемQuotaDecision (только разрешённые исходы)См. таксономию отказов нижеSubstrate. Монтируйте после аутентификации арендатора, перед оплачиваемым обработчиком
PlanResolverInterface::resolveTenantContext $tenantРазрешает арендатора в его тариф и политики для каждой функцииResolvedPlanNoPlanForTenantExceptionSubstrate. Резервный тариф по умолчанию для неизвестных арендаторов — это дефект
RegistryPlanResolverarray<non-empty-string, ResolvedPlan> $plansByTenantРезолвер на основе отображенияResolvedPlanNoPlanForTenantException для неотображённых арендаторовSubstrate. Отказоустойчивый по построению
ResolvedPlan::policyFornon-empty-string $featureKeyПоиск политики в разрешённом тарифе?QuotaPolicyНе бросаетSubstrate. null означает неизвестную функцию; шлюз отклоняет её
QuotaPolicynon-empty-string $featureKey, float $limit, OveragePolicy $overagePolicyЛимит для каждой функции и политика нарушенияНе бросаетSubstrate. UNLIMITED = -1.0; лимит 0.0 — это нулевое разрешение, а не безлимит; isUnlimited(), isBlocking()
QuotaDecisionСтатические bypassed(), unlimited(), consumed()Объект-значение разрешённого исходаQuotaDecisionНе бросаетSubstrate. isAllowed() всегда истинно; каждый отказ вместо этого бросает исключение
UsageCounterСнимок строки: арендатор, функция, границы периода, used, limit, updatedAtНеизменяемая строка использованияНе бросаетSubstrate. remaining() может быть отрицательным; wouldExceed() строгий
UsageCounterStoreInterface::getАрендатор, функция, границы периода, float $limitЧитает строку использования, создавая её с used = 0, когда отсутствуетUsageCounterUsageStoreUnavailableExceptionSubstrate. Никогда не возвращает ложное значение при сбое бэкенда
UsageCounterStoreInterface::tryConsumeАрендатор, функция, границы периода, float $amount, float $limitАтомарное резервирование compare-and-set в пределах лимита?UsageCounter (null, когда резервирование нарушило бы лимит)UsageStoreUnavailableExceptionSubstrate. Должно быть единственной атомарной операцией над хранилищем
InMemoryUsageCounterStoreВнутрипроцессная эталонная реализация контракта хранилищаПо интерфейсуПо интерфейсуSubstrate. Только один процесс; документирует инвариант атомарности
QuotaEnforcementException (abstract)Базовый тип каждого отказа substrateЯвляется семейством исключенийSubstrate. Каждый подтип объявляет httpStatusCode()
public function checkQuota(TenantContext $tenant, SaaSPlan $plan, float $currentCu): void
public function evaluate(
TenantContext $tenant,
SaaSPlan $plan,
PlanDefinition $planDef,
float $currentCu,
): array
public function enforce(?TenantContext $tenant, string $featureKey, float $amount = 1.0): QuotaDecision
public function tryConsume(
string $tenantId,
string $featureKey,
DateTimeImmutable $periodStart,
DateTimeImmutable $periodEnd,
float $amount,
float $limit,
): ?UsageCounter;

Таксономия отказов QuotaEnforcementGuard::enforce

ИсключениеHTTP-статусВозникает, когда
MissingTenantContextException401Режим SaaS без аутентифицированного контекста арендатора
NoPlanForTenantException402Резолвер не находит тарифа, назначенного арендатору
UnknownFeatureException402Разрешённый тариф не определяет политику для ключа функции
UsageStoreUnavailableException503Хранилище использования нельзя прочитать или атомарно обновить; также возникает при неположительном $amount
QuotaExceededException402 (SaaS) / 403 (on-prem)Квота блокирующей политики превышена, или конкурентное резервирование израсходовало последний запас
  • Реестр по умолчанию поставляет три уровня (Standard / Advanced / High Control) с растущими квотами CU и наборами возможностей. Запрос незарегистрированного тарифа завершается явным InvalidArgumentException.
  • QuotaManager::checkQuota() вызывает исключение только тогда, когда выполняются оба условия: политика блокирующая и текущее потребление строго выше включённой квоты. Политика budget-alert никогда не вызывает исключения; превышение сигнализируется через оповещения.
  • remainingQuota() и usagePercentage() — это чистые чтения и никогда не блокируют. Остаток квоты уходит в минус при превышении; процент использования превышает 1.0 при превышении.
  • Оповещения вычисляются в порядке возрастания порогов: предупреждение 80%, предупреждение 100% (critical), затем budget-exceeded (critical). Budget-exceeded ограничено строгим превышением; потребление ровно на 100% запускает предупреждение 100%, а не budget-exceeded.
  • Каждый тип оповещения срабатывает не более одного раза на арендатора за расчётный период. Состояние срабатывания записывается через AlertStateRepositoryInterface, поэтому дедупликация настолько долговечна, насколько долговечна выбранная реализация.
  • Ключ дедупликации встраивает период UTC YYYY-MM. Поэтому новый календарный месяц автоматически перевзводит каждый тип оповещения; для перевзвода при переходе периода не нужен вызов очистки. clearAlerts() очищает текущий период, что перевзводит оповещения в середине периода, например после апгрейда тарифа.
  • Защита от несоответствия тарифа в evaluate() отклоняет вызов, где переданный тариф и определение тарифа расходятся, защищая от определения уровня, отличного от тарифа арендатора.
  • Вся арифметика периодов привязана к UTC. Момент сброса превышения квоты — первый день следующего календарного месяца в полночь UTC; ответ soft-stop должен указывать его как горизонт повтора.
  • QuotaEnforcementGuard отказоустойчив (fail-closed) в режиме SaaS. Отсутствующий арендатор, отсутствующий тариф, неизвестная функция, сбой хранилища и нарушение квоты — всё отклоняется; ничто не проваливается к неявному разрешению. Развёртывания вне SaaS отказываются только через конструирование шлюза с не-SaaS DeploymentMode.
  • Блокирующие политики резервируют использование через UsageCounterStoreInterface::tryConsume — атомарный compare-and-set. Конкурентные запросы не могут совместно вытолкнуть использование за лимит; проигравший в гонке получает QuotaExceededException, даже если предварительная проверка прошла.
  • При политике budget-alert шлюз записывает потребление по мере возможности (best-effort) и никогда не отклоняет; резервирование сверх мягкого потолка всё равно записывает строку по лимиту.
  • QuotaExceededException учитывает режим развёртывания: отказы SaaS сопоставляются с HTTP 402 со спец-кодом SPEC-BILLING-003 и помечаются как повторяемые; отказы on-prem сопоставляются с HTTP 403 с SPEC-LIC-001.
  • Библиотека сама не выдаёт HTTP-ответы. Объявленные коды статусов — это контракт для граничного слоя, который сопоставляет брошенный отказ с ответом и не должен вызывать оплачиваемый обработчик.
  • Неположительная включённая квота. usagePercentage(), evaluate() и OverageCalculator::calculate() дают коэффициент использования 0.0 вместо деления на ноль. Пороговые оповещения тогда никогда не срабатывают только по коэффициенту.
  • Budget-alert плюс большое превышение. И менеджер, и шлюз возвращают разрешённые исходы. Не трактуйте отсутствие исключения как доказательство нахождения в пределах квоты; сверяйтесь с OverageResult или потоком оповещений.
  • Ровно на лимите. checkQuota() при currentCu == includedCuQuota проходит. BudgetExceeded требует строгого превышения. UsageCounter::wouldExceed() тоже строгий.
  • MonthlyCapReached. Enum объявляет этот четвёртый тип оповещения, но BillingAlertService::evaluate() никогда его не выдаёт; его список кандидатов покрывает только три пороговых оповещения. Он зарезервирован для эмиттеров отслеживания лимита вне этого модуля.
  • Дублирующиеся определения уровней. PlanRegistry индексирует по значению уровня; последнее определение для уровня молча заменяет более ранние. Стройте реестры из дедуплицированного списка.
  • Нулевое разрешение против безлимита. Лимит QuotaPolicy, равный 0.0, означает, что любое потребление в периоде является превышением. Только отрицательный маркер UNLIMITED отключает учёт; isUnlimited() никогда не блокирует.
  • Неположительная величина резервирования. enforce() отклоняет неположительный $amount отказоустойчиво с UsageStoreUnavailableException (503). Это дефект вызывающей стороны, а не сбой хранилища.
  • Сбой хранилища. Любой сбой чтения или резервирования проявляется как UsageStoreUnavailableException и отклоняет. Шлюз никогда не разрешает неучтённую работу, пока счётчик недоступен.
  • Реализации в памяти. InMemoryAlertStateRepository и InMemoryUsageCounterStore корректны только в пределах одного процесса PHP. Развёртывания с несколькими репликами должны предоставлять реализации на основе хранилища с настоящей атомарностью; хранилище с чтением-затем-записью — это дефект, допускающий превышение квоты под нагрузкой.
  • Режим FIPS. Биллинг не выполняет собственных криптографических операций и не имеет специфичного для FIPS поведения. Личность арендатора, которую он потребляет, должна исходить из аутентифицированного контекста, чья позиция по FIPS задокументирована вместе с поверхностью SaaS.
УтверждениеСтандартПункт
Код статуса 402 зарезервирован для будущего использования; сам по себе он не несёт нормативной семантики запроса.RFC 9110§15.5.3
429 указывает, что клиент отправил слишком много запросов за отведённое время («ограничение частоты»).RFC 6585§4
Retry-After указывает, сколько пользовательский агент должен ждать перед повторным запросом.RFC 9110§10.2.3

Все пункты изложены своими словами; NextPDF не воспроизводит нормативный текст. NextPDF не заявляет о соответствии HTTP-протоколу или сертификации для этой поверхности. Сопоставление 402 / 429 / 200, объявленное OveragePolicy::httpStatusCode(), и коды отказов шлюза 401 / 402 / 503 — это продуктовая конвенция, согласованная с приведёнными выше пунктами: RFC 9110 резервирует 402, поэтому его использование здесь для отказа по оплате — общепринятая отраслевая конвенция, а не семантика, определённая IETF. Горизонт повтора soft-stop (resetsAt) — это значение, которое граничный слой должен предоставлять в качестве рекомендации Retry-After. Выдача фактических HTTP-ответов, заголовков и поведения кэширования — это ответственность хостящего приложения.

  • Составьте модель из PlanRegistry::defaultRegistry(), одной OveragePolicy и QuotaManager; добавьте BillingAlertService с долговечной реализацией AlertStateRepositoryInterface для оповещений.
  • Монтируйте QuotaEnforcementGuard в конвейере запросов после аутентификации арендатора и перед оплачиваемым обработчиком. Ловите QuotaEnforcementException и биллинговый QuotaExceededException на границе и сопоставляйте httpStatusCode() с ответом.
  • Определения тарифов в этом модуле — единственный источник истины для биллинга; не поддерживайте параллельное определение биллинга где-либо ещё в вашем развёртывании.
  • Реализации в памяти делают всю поверхность юнит-тестируемой без ввода-вывода. Рекомендуемые граничные тесты: потребление ровно на квоте, на одну единицу выше, пороги коэффициента на 0.8 и 1.0, защита от несоответствия тарифа, гонка CAS (два резервирования против последней единицы запаса) и отказ при сбое хранилища.
  • Классы основной модели несут @since 2.2.0; субстрат несёт @since 2.3.0. Текущая линия пакета — 3.1.0.
  • Оператор владеет реализациями репозитория состояния оповещений и хранилища использования, их долговечностью между репликами и любым перевзводом оповещений в середине периода через clearAlerts().

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