跳转到内容
getnextpdf.com

Enterprise 版本

计费 — 深度参考

本页是 NextPDF Enterprise 计费表面的深度参考。该表面分为两层。NextPDF\Enterprise\Billing 中的计费模型定义计划层级、配额、超额策略与去重的用量告警。NextPDF\Enterprise\Billing\Substrate 中的强制执行基底将该模型置于实时请求路径上,既 fail-closed 又并发安全。入口点为 PlanRegistryQuotaManagerOverageCalculatorBillingAlertServiceQuotaEnforcementGuard。关于工作流层面的指南,请参阅 Billing 能力页面

此能力随 NextPDF Enterprisenextpdf/enterprise)发行,并以一个 Enterprise 层级的授权信封激活。没有该权益的部署不会加载此能力的类。比较版本并获取授权

Billing 是一项 Enterprise 基础能力,没有单独的每功能标志;一旦将 Enterprise 软件包安装在 Core 软件包旁边即可用。NextPDF Core(Apache-2.0)与 NextPDF Pro 没有计划、配额或超额模型;此表面没有更低层级的等价物。计划包含项、配额与商业条款由授权协议管辖,而非由运行时强制执行;本参考不是法律或合同意见。

所有符号都位于 NextPDF\Enterprise\Billing 之下。标记为 基底 的行位于 NextPDF\Enterprise\Billing\Substrate 之下。TenantContext 是来自 NextPDF\Enterprise\SaaS 的经过认证的租户类型。

符号参数默认行为返回抛出或失败于备注
SaaSPlan (enum)字符串支持的计划层级:standardadvancedhigh_control不抛出label() 返回显示名称
PlanDefinition::__constructSaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncluded不可变的计划值对象;按原样存储输入新实例不抛出final readonly;提升的 public 属性
PlanDefinition::includesCapabilityCapabilityCode $capability严格恒等成员检查bool不抛出
PlanRegistry::__constructlist<PlanDefinition> $definitions按层级索引定义;每个层级以最后一个定义为准新注册表不抛出用于测试与白标计划集
PlanRegistry::getSaaSPlan $plan规范的计划查找PlanDefinition当计划未注册时抛出 InvalidArgumentException
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 stop 与 soft stop 为真
QuotaManager::__constructPlanRegistry $planRegistry, OveragePolicy $overagePolicy将注册表绑定到一个策略新实例不抛出
QuotaManager::checkQuotaTenantContext $tenant, SaaSPlan $plan, float $currentCu在配额之内或恰好等于配额时、或在非阻塞策略下静默返回void在阻塞策略下严格超额时抛出 QuotaExceededException;对未注册计划由注册表抛出 InvalidArgumentExceptionresetsAt = 下月第一天,UTC 午夜
QuotaManager::remainingQuotaSaaSPlan $plan, float $currentCu纯读取;从不阻塞float注册表 InvalidArgumentException超额时为负
QuotaManager::usagePercentageSaaSPlan $plan, float $currentCu纯读取;从不阻塞float注册表 InvalidArgumentException当包含配额为非正时返回 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清除该租户在当前 UTC 周期的已触发状态void仓库定义的失败会向上传播在同一周期内重新武装告警
AlertStateRepositoryInterfacehasAlertFired(), markAlertFired(), clearForPeriod()持久的告警去重持久化契约按方法而定由实现定义运维方负责跨副本的耐久性
InMemoryAlertStateRepository数组支持的已触发状态按接口而定不抛出仅限单请求生命周期与测试
QuotaExceededException只读 currentCu, limitCu, resetsAt, tenantId, isSaaS感知部署模式的配额拒绝即该 throwablehttpStatusCode() 402 SaaS / 403 本地部署;specCode() SPEC-BILLING-003 / SPEC-LIC-001toErrorEnvelope() 产出一个结构化的错误体
DeploymentMode (enum)saas, self_hosted_oss, local_development不抛出基底。 enforcesQuota() 仅对 Saas 为真;退出始终是显式的
QuotaEnforcementGuard::__constructDeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterface组装实时配额闸门新实例不抛出基底。 final readonly
QuotaEnforcementGuard::enforce?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0带原子预留的 fail-closed 配额闸门QuotaDecision(仅允许的结果)参见下方的拒绝分类基底。 挂载于租户认证之后、计费处理器之前
PlanResolverInterface::resolveTenantContext $tenant将租户解析为其计划与每功能策略ResolvedPlanNoPlanForTenantException基底。 为未知租户提供默认计划回退属于缺陷
RegistryPlanResolverarray<non-empty-string, ResolvedPlan> $plansByTenant基于映射的解析器ResolvedPlan对未映射的租户抛出 NoPlanForTenantException基底。 从构造上即 fail-closed
ResolvedPlan::policyFornon-empty-string $featureKey在已解析计划上查找策略?QuotaPolicy不抛出基底。 null 表示未知功能;守卫予以拒绝
QuotaPolicynon-empty-string $featureKey, float $limit, OveragePolicy $overagePolicy每功能的限额与超限策略不抛出基底。 UNLIMITED = -1.00.0 限额表示零额度,而非无限;isUnlimited(), isBlocking()
QuotaDecision静态方法 bypassed(), unlimited(), consumed()允许结果的值对象QuotaDecision不抛出基底。 isAllowed() 始终为真;每次拒绝都改为抛出
UsageCounter行快照:租户、功能、周期边界、used, limit, updatedAt不可变的用量行不抛出基底。 remaining() 可能为负;wouldExceed() 是严格的
UsageCounterStoreInterface::get租户、功能、周期边界、float $limit读取用量行,缺失时以 used = 0 创建UsageCounterUsageStoreUnavailableException基底。 后端失败时绝不返回假值
UsageCounterStoreInterface::tryConsume租户、功能、周期边界、float $amount, float $limit在限额内进行原子的比较并设置预留?UsageCounter(当预留会突破限额时为 nullUsageStoreUnavailableException基底。 必须是针对后端存储的单个原子操作
InMemoryUsageCounterStore存储契约的进程内参考实现按接口而定按接口而定基底。 仅限单进程;用于说明原子性不变量
QuotaEnforcementException (abstract)每个基底拒绝的基类型即该 throwable 家族基底。 每个子类型声明 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 状态触发条件
MissingTenantContextException401SaaS 模式下没有经过认证的租户上下文
NoPlanForTenantException402解析器找不到分配给该租户的计划
UnknownFeatureException402已解析计划未为该功能键定义任何策略
UsageStoreUnavailableException503无法读取用量存储或无法原子更新;对非正的 $amount 也会触发
QuotaExceededException402(SaaS)/ 403(本地部署)阻塞策略的配额被超过,或某个并发预留消耗了最后的余量
  • 默认注册表发行三个层级(Standard / Advanced / High Control),具有递增的 CU 配额与能力集合。一个未注册计划的请求会以一个显式的 InvalidArgumentException 失败。
  • QuotaManager::checkQuota() 仅在两个条件都成立时才触发:策略是阻塞的,且当前用量严格高于包含配额。预算告警策略从不触发;超额通过告警发出信号。
  • remainingQuota()usagePercentage() 是纯读取,从不阻塞。剩余配额在超额时变为负值;用量百分比在超额时超过 1.0
  • 告警按阈值升序评估:80% 警告、100% 警告(critical),然后是预算超出(critical)。预算超出以严格超额为闸门;恰好为 100% 的用量触发 100% 警告,而非预算超出。
  • 每种告警类型在每个租户每个计费周期内最多触发一次。已触发状态通过 AlertStateRepositoryInterface 记录,因此去重的持久性取决于所选实现。
  • 去重键内嵌 UTC YYYY-MM 周期。因此一个新的日历月会自动重新武装每种告警类型;滚动重新武装无需任何清除调用。clearAlerts() 清除当前周期,从而在周期中途重新武装告警,例如在一次计划升级之后。
  • evaluate() 中的一道计划不匹配守卫会拒绝所提供计划与计划定义不一致的调用,从而防范一个来自不同于该租户计划层级的定义。
  • 所有周期运算都锚定于 UTC。配额超出的重置时刻是下一个日历月第一天的 UTC 午夜;一个软停止响应应当将其宣告为重试期限。
  • QuotaEnforcementGuard 在 SaaS 模式下是 fail-closed 的。缺失租户、缺失计划、未知功能、存储中断与配额突破都会拒绝;不会有任何情形穿透为隐式允许。非 SaaS 部署只能通过以一个非 SaaS 的 DeploymentMode 构造守卫来退出。
  • 阻塞策略通过 UsageCounterStoreInterface::tryConsume(一个原子的比较并设置)预留用量。并发请求无法共同将用量推过限额;即便预检通过,竞争的失败者也会收到 QuotaExceededException
  • 在预算告警策略下,守卫尽力记录消耗且从不拒绝;一个越过软上限的预留仍会以限额记录该行。
  • QuotaExceededException 感知部署模式:SaaS 拒绝映射到 HTTP 402,携带规范码 SPEC-BILLING-003 并标记为可重试;本地部署拒绝映射到 HTTP 403,携带 SPEC-LIC-001
  • 该库本身不发出 HTTP 响应。所声明的状态码是面向边缘层的契约——由边缘层将一个抛出的拒绝映射为一个响应,并且必须不调用计费处理器。
  • 非正的包含配额。 usagePercentage()evaluate()OverageCalculator::calculate() 都会产出一个 0.0 的用量比率,而非除以零。此后阈值告警绝不会仅凭比率触发。
  • 预算告警加上大量超额。 管理器与守卫都会返回允许的结果。不要将异常的缺失视为处于配额之内的证明;请查阅 OverageResult 或告警流。
  • 恰好在限额上。currentCu == includedCuQuotacheckQuota() 通过。BudgetExceeded 需要严格超额。UsageCounter::wouldExceed() 同样是严格的。
  • MonthlyCapReached 该枚举声明了这第四种告警类型,但 BillingAlertService::evaluate() 从不发出它;其候选列表只涵盖三种阈值告警。它保留给本模块之外的上限跟踪发出方。
  • 重复的层级定义。 PlanRegistry 按层级值索引;一个层级的最后一个定义会静默替换更早的定义。请从一个去重后的列表构造注册表。
  • 零额度对比无限。 一个 0.0QuotaPolicy 限额意味着周期内的每一次消耗都是超额。只有负的 UNLIMITED 哨兵值才会禁用计量;isUnlimited() 从不阻塞。
  • 非正的预留数量。 enforce() 会以 UsageStoreUnavailableException(503)fail-closed 地拒绝一个非正的 $amount。这是一个调用方缺陷,而非存储中断。
  • 存储中断。 任何读取或预留失败都会以 UsageStoreUnavailableException 浮现并拒绝。守卫绝不会在计量器停机时允许未计量的工作。
  • 内存实现。 InMemoryAlertStateRepositoryInMemoryUsageCounterStore 仅在单个 PHP 进程内是正确的。多副本部署必须提供由具备真正原子性的数据存储支持的实现;一个先读后写的存储属于缺陷,会在负载下允许超配额。
  • FIPS 模式。 Billing 本身不执行任何密码学操作,也没有 FIPS 特有的行为。它所消费的租户身份必须来源于一个经过认证的上下文,其 FIPS 姿态随 SaaS 表面记录。
声明标准条款
402 状态码保留供将来使用;它自身不携带任何规范性的请求语义。RFC 9110§15.5.3
429 表示客户端在给定时间内发送了过多请求(“速率限制”)。RFC 6585§4
Retry-After 指示用户代理在发起后续请求之前应当等待多久。RFC 9110§10.2.3

所有条款均为转述;NextPDF 不复现规范性文本。NextPDF 不为此表面作出任何 HTTP 协议符合性或认证声明。OveragePolicy::httpStatusCode() 声明的 402 / 429 / 200 映射,以及守卫的 401 / 402 / 503 拒绝码,是一种与上述条款对齐的产品约定:RFC 9110 保留 402,因此此处将其用于付款拒绝是常见的行业约定,而非 IETF 定义的语义。软停止的重试期限(resetsAt)是边缘层应作为 Retry-After 指引浮现的值。发出实际的 HTTP 响应、头部与缓存行为是托管应用的职责。

  • PlanRegistry::defaultRegistry()、一个 OveragePolicy 和一个 QuotaManager 组合出模型;为告警添加带有持久 AlertStateRepositoryInterface 实现的 BillingAlertService
  • QuotaEnforcementGuard 挂载在请求管线中租户认证之后、计费处理器之前。在边缘捕获 QuotaEnforcementException 与 billing 的 QuotaExceededException,并将 httpStatusCode() 映射到响应。
  • 本模块中的计划定义是计费的唯一真实来源;不要在部署的别处维护一份并行的计费定义。
  • 内存实现使整个表面无需 I/O 即可单元测试。推荐的边界测试:用量恰好等于配额、高出一个单位、0.8 与 1.0 处的比率阈值、计划不匹配守卫、CAS 竞争(针对最后一个单位余量的两次预留),以及存储中断拒绝。
  • 核心模型类携带 @since 2.2.0;基底携带 @since 2.3.0。当前软件包线为 3.1.0。
  • 运维方拥有告警状态仓库与用量存储的实现、它们跨副本的耐久性,以及通过 clearAlerts() 进行的任何周期中途告警重新武装。

本页仅记录外部可观察的行为与受支持的公共 API 表面。内部命名空间路径、辅助类、机制表、运行手册文件名与工单前缀不在范围内。