Enterprise エディション
課金 — 詳細リファレンス
このページは、NextPDF Enterprise の課金サーフェスに関する詳細リファレンスです。このサーフェスには 2 つのレイヤーがあります。NextPDF\Enterprise\Billing の課金モデルは、プランティア、クォータ、超過ポリシー、重複排除された利用状況アラートを定義します。NextPDF\Enterprise\Billing\Substrate の強制 substrate は、そのモデルをライブのリクエストパス上に、フェイルクローズドかつ並行安全に配置します。エントリーポイントは PlanRegistry、QuotaManager、OverageCalculator、BillingAlertService、QuotaEnforcementGuard です。ワークフローレベルのガイドについては、課金の機能ページを参照してください。
提供とライセンス
「提供とライセンス」という見出しのセクションこの機能は NextPDF Enterprise(nextpdf/enterprise)で提供され、Enterprise ティアのライセンスエンベロープで有効化されます。そのエンタイトルメントを持たないデプロイメントは、この機能のクラスをロードしません。エディションを比較してライセンスを取得する。
課金は Enterprise の基本機能であり、機能ごとの個別のフラグはありません。Core パッケージの隣に Enterprise パッケージがインストールされれば利用できます。NextPDF Core(Apache-2.0)と NextPDF Pro には、プラン、クォータ、超過のモデルがありません。このサーフェスには下位ティア相当のものが存在しません。プランの包含内容、クォータ、商業条件は、実行時の強制ではなくライセンス契約によって定められます。このリファレンスは法的または契約上の見解ではありません。
パブリック API サーフェス
「パブリック API サーフェス」という見出しのセクションすべてのシンボルは NextPDF\Enterprise\Billing の下にあります。substrate とマークされた行は NextPDF\Enterprise\Billing\Substrate の下にあります。TenantContext は NextPDF\Enterprise\SaaS の認証済みテナント型です。
| シンボル | パラメーター | デフォルトの挙動 | 戻り値 | スロー/失敗条件 | 備考 |
|---|---|---|---|---|---|
SaaSPlan(enum) | — | 文字列ベースのプランティア。standard、advanced、high_control | — | スローしない | label() は表示名を返す |
PlanDefinition::__construct | SaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncluded | 不変のプラン値オブジェクト。入力をそのまま格納 | 新しいインスタンス | スローしない | final readonly。昇格されたパブリックプロパティ |
PlanDefinition::includesCapability | CapabilityCode $capability | 厳密な同一性メンバーシップチェック | bool | スローしない | — |
PlanRegistry::__construct | list<PlanDefinition> $definitions | 定義をティアごとにインデックス化。ティアごとに最後の定義が優先 | 新しいレジストリ | スローしない | テストおよびホワイトラベルのプランセット向け |
PlanRegistry::get | SaaSPlan $plan | 正規のプランルックアップ | PlanDefinition | プランが未登録の場合は InvalidArgumentException | — |
PlanRegistry::has | SaaSPlan $plan | 登録の有無を確認 | bool | スローしない | — |
PlanRegistry::defaultRegistry(static) | — | 本番デフォルト。Standard 1,000 CU、Advanced 5,000 CU+Intelligence Pack、High Control 20,000 CU+Intelligence Pack および Privacy Pack | PlanRegistry | スローしない | 契約条件でカスタム定義が求められない限り使用 |
OveragePolicy(enum) | — | hard_stop、soft_stop、budget_alert | — | スローしない | httpStatusCode() は 402 / 429 / 200 にマッピング。isBlocking() はハードストップとソフトストップのみ true |
QuotaManager::__construct | PlanRegistry $planRegistry, OveragePolicy $overagePolicy | レジストリを 1 つのポリシーにバインド | 新しいインスタンス | スローしない | — |
QuotaManager::checkQuota | TenantContext $tenant, SaaSPlan $plan, float $currentCu | クォータ以下、または非ブロッキングポリシーの下では静かに返す | void | ブロッキングポリシーの下での厳密な超過時は QuotaExceededException。未登録プランの場合はレジストリからの InvalidArgumentException | resetsAt = 翌月初日、UTC 真夜中 |
QuotaManager::remainingQuota | SaaSPlan $plan, float $currentCu | 純粋な読み取り。決してブロックしない | float | レジストリの InvalidArgumentException | 超過時はマイナス |
QuotaManager::usagePercentage | SaaSPlan $plan, float $currentCu | 純粋な読み取り。決してブロックしない | float | レジストリの InvalidArgumentException | 包含クォータが非正の場合は 0.0。超過時は 1.0 を超える |
OverageCalculator::calculate | PlanDefinition $plan, float $currentCu | 不変の超過スナップショットを算出 | OverageResult | スローしない | final readonly、ステートレス |
OverageResult | includedCu, 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::__construct | AlertStateRepositoryInterface $alertState | 重複排除ストアをバインド | 新しいインスタンス | スローしない | — |
BillingAlertService::evaluate | TenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCu | まだ発火していないアラートをしきい値の昇順で発火し記録 | list<BillingAlertType> | プランと定義の不一致時は InvalidArgumentException | 重複排除キー。テナント、タイプ、UTC YYYY-MM 期間 |
BillingAlertService::clearAlerts | TenantContext $tenant | 現在の UTC 期間におけるテナントの発火状態をクリア | void | リポジトリ定義の失敗は伝播 | 同一期間内でアラートを再アーム |
AlertStateRepositoryInterface | hasAlertFired(), markAlertFired(), clearForPeriod() | 耐久性のあるアラート重複排除の永続化コントラクト | メソッドごと | 実装定義 | オペレーターがレプリカ間の耐久性を所有 |
InMemoryAlertStateRepository | — | 配列ベースの発火状態 | インターフェイスに準拠 | スローしない | 単一リクエストのライフサイクルとテストのみ |
QuotaExceededException | 読み取り専用の currentCu, limitCu, resetsAt, tenantId, isSaaS | デプロイメントモードを認識するクォータ拒否 | — | スロー対象そのもの | httpStatusCode() は 402 SaaS / 403 オンプレム。specCode() は SPEC-BILLING-003 / SPEC-LIC-001。toErrorEnvelope() は構造化されたエラーボディを生成 |
DeploymentMode(enum) | — | saas、self_hosted_oss、local_development | — | スローしない | Substrate. enforcesQuota() は Saas のみ true。オプトアウトは常に明示的 |
QuotaEnforcementGuard::__construct | DeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterface | ライブクォータゲートを組み立て | 新しいインスタンス | スローしない | Substrate. final readonly |
QuotaEnforcementGuard::enforce | ?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0 | アトミックな予約を伴うフェイルクローズドなクォータゲート | QuotaDecision(許可された結果のみ) | 下記の拒否分類を参照 | Substrate. テナント認証の後、課金対象ハンドラーの前にマウント |
PlanResolverInterface::resolve | TenantContext $tenant | テナントをそのプランと機能ごとのポリシーに解決 | ResolvedPlan | NoPlanForTenantException | Substrate. 未知のテナントに対するデフォルトプランのフォールバックは欠陥 |
RegistryPlanResolver | array<non-empty-string, ResolvedPlan> $plansByTenant | マップベースのリゾルバー | ResolvedPlan | マッピングされていないテナントには NoPlanForTenantException | Substrate. 構造上フェイルクローズド |
ResolvedPlan::policyFor | non-empty-string $featureKey | 解決済みプラン上のポリシールックアップ | ?QuotaPolicy | スローしない | Substrate. null は未知の機能を意味し、ガードはそれを拒否 |
QuotaPolicy | non-empty-string $featureKey, float $limit, OveragePolicy $overagePolicy | 機能ごとの上限と超過ポリシー | — | スローしない | Substrate. UNLIMITED = -1.0。0.0 の上限は無制限ではなくゼロ許容。isUnlimited()、isBlocking() |
QuotaDecision | 静的メソッド bypassed(), unlimited(), consumed() | 許可結果の値オブジェクト | QuotaDecision | スローしない | Substrate. isAllowed() は常に true。拒否は代わりにすべてスロー |
UsageCounter | 行スナップショット。テナント、機能、期間境界、used, limit, updatedAt | 不変の利用状況行 | — | スローしない | Substrate. remaining() はマイナスになり得る。wouldExceed() は厳密 |
UsageCounterStoreInterface::get | テナント、機能、期間境界、float $limit | 利用状況行を読み取り、存在しない場合は used = 0 で作成 | UsageCounter | UsageStoreUnavailableException | Substrate. バックエンド障害時に falsy 値を返すことはない |
UsageCounterStoreInterface::tryConsume | テナント、機能、期間境界、float $amount, float $limit | 上限内でのアトミックな比較設定(compare-and-set)予約 | ?UsageCounter(予約が上限を超える場合は null) | UsageStoreUnavailableException | Substrate. バッキングストアに対する単一のアトミック操作でなければならない |
InMemoryUsageCounterStore | — | ストアコントラクトのインプロセス参照実装 | インターフェイスに準拠 | インターフェイスに準拠 | Substrate. 単一プロセスのみ。アトミック性の不変条件を文書化 |
QuotaEnforcementException(abstract) | — | すべての substrate 拒否の基底型 | — | スロー可能なファミリー | Substrate. 各サブタイプが httpStatusCode() を宣言 |
public function checkQuota(TenantContext $tenant, SaaSPlan $plan, float $currentCu): voidpublic function evaluate( TenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCu,): arraypublic function enforce(?TenantContext $tenant, string $featureKey, float $amount = 1.0): QuotaDecisionpublic function tryConsume( string $tenantId, string $featureKey, DateTimeImmutable $periodStart, DateTimeImmutable $periodEnd, float $amount, float $limit,): ?UsageCounter;QuotaEnforcementGuard::enforce の拒否分類
| 例外 | HTTP ステータス | 発生条件 |
|---|---|---|
MissingTenantContextException | 401 | SaaS モードで認証済みテナントコンテキストがない場合 |
NoPlanForTenantException | 402 | リゾルバーがテナントに割り当てられたプランを見つけられない場合 |
UnknownFeatureException | 402 | 解決済みプランが機能キーに対するポリシーを定義していない場合 |
UsageStoreUnavailableException | 503 | 利用状況ストアを読み取れない、またはアトミックに更新できない場合。非正の $amount に対しても発生 |
QuotaExceededException | 402(SaaS) / 403(オンプレム) | ブロッキングポリシーのクォータを超過した場合、または並行する予約が最後の余裕を消費した場合 |
挙動コントラクト
「挙動コントラクト」という見出しのセクション- デフォルトのレジストリは、増加する CU クォータと機能セットを持つ 3 つのティア(Standard / Advanced / High Control)を同梱します。未登録のプランへのリクエストは、明示的な
InvalidArgumentExceptionで失敗します。 QuotaManager::checkQuota()は、両方の条件が成り立つ場合にのみ発生します。ポリシーがブロッキングであり、かつ現在の利用状況が包含クォータを厳密に上回っている場合です。バジェットアラートポリシーは決して発生させません。超過はアラートを通じて通知されます。remainingQuota()とusagePercentage()は純粋な読み取りであり、決してブロックしません。残りクォータは超過時にマイナスになり、利用率は超過時に1.0を超えます。- アラートはしきい値の昇順で評価されます。80% 警告、100% 警告(critical)、続いてバジェット超過(critical)です。バジェット超過は厳密な超過でゲートされます。ちょうど 100% の利用状況は、バジェット超過ではなく 100% 警告を発火します。
- 各アラートタイプは、テナントごと、課金期間ごとに最大 1 回発火します。発火状態は
AlertStateRepositoryInterfaceを通じて記録されるため、重複排除は選択された実装と同程度に耐久性があります。 - 重複排除キーは UTC
YYYY-MM期間を埋め込んでいます。したがって、新しい暦月はすべてのアラートタイプを自動的に再アームします。ロールオーバーの再アームにクリア呼び出しは不要です。clearAlerts()は現在の期間をクリアし、たとえばプランのアップグレード後など、期間の途中でアラートを再アームします。 evaluate()のプラン不一致ガードは、供給されたプランとプラン定義が食い違う呼び出しを拒否し、テナントのプランとは異なるティアの定義から保護します。- すべての期間演算は UTC に固定されています。クォータ超過のリセット時刻は、翌暦月の初日の UTC 真夜中です。ソフトストップレスポンスは、これを再試行の期限として通知すべきです。
QuotaEnforcementGuardは SaaS モードでフェイルクローズドです。テナントの欠落、プランの欠落、未知の機能、ストア障害、クォータ超過はすべて拒否します。暗黙の許可へすり抜けるものはありません。非 SaaS のデプロイメントは、非 SaaS のDeploymentModeでガードを構築することによってのみオプトアウトします。- ブロッキングポリシーは、アトミックな比較設定(compare-and-set)である
UsageCounterStoreInterface::tryConsumeを通じて利用状況を予約します。並行するリクエストが集合的に利用状況を上限を超えて押し上げることはできません。競合に敗れた側は、事前チェックを通過していてもQuotaExceededExceptionを受け取ります。 - バジェットアラートポリシーの下では、ガードは消費をベストエフォートで記録し、決して拒否しません。ソフト上限を超えた予約でも、行を上限で記録します。
QuotaExceededExceptionはデプロイメントモードを認識します。SaaS の拒否は spec コードSPEC-BILLING-003を伴う HTTP 402 にマッピングされ、再試行可能とマークされます。オンプレムの拒否はSPEC-LIC-001を伴う HTTP 403 にマッピングされます。- ライブラリ自体は HTTP レスポンスを発行しません。宣言されたステータスコードはエッジレイヤーのためのコントラクトであり、エッジレイヤーはスローされた拒否をレスポンスにマッピングし、課金対象ハンドラーを呼び出してはなりません。
エッジケースと障害モード
「エッジケースと障害モード」という見出しのセクション- 非正の包含クォータ。
usagePercentage()、evaluate()、OverageCalculator::calculate()はいずれも、ゼロ除算の代わりに0.0の利用率を生じます。その場合、しきい値アラートは利用率のみからは決して発火しません。 - バジェットアラートに加えて大きな超過。 マネージャーとガードはいずれも許可結果を返します。例外がないことを、クォータ内であることの証拠として扱わないでください。
OverageResultまたはアラートストリームを参照してください。 - 上限にちょうど到達。
currentCu == includedCuQuotaにおけるcheckQuota()は通過します。BudgetExceededは厳密な超過を要求します。UsageCounter::wouldExceed()も同様に厳密です。 MonthlyCapReached。 enum はこの 4 番目のアラートタイプを宣言していますが、BillingAlertService::evaluate()はこれを決して発行しません。その候補リストは 3 つのしきい値アラートのみを対象とします。これは、このモジュールの外部にある上限追跡のエミッター向けに予約されています。- 重複するティア定義。
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 プロトコルの適合性または認証の主張を一切行いません。 OveragePolicy::httpStatusCode() が宣言する 402 / 429 / 200 のマッピングと、ガードの 401 / 402 / 503 の拒否コードは、上記の条項に沿った製品上の慣例です。RFC 9110 は 402 を予約しているため、ここでの支払い拒否としての用法は、IETF が定義したセマンティクスではなく、業界で一般的な慣例です。ソフトストップの再試行の期限(resetsAt)は、エッジレイヤーが Retry-After のガイダンスとして表出すべき値です。実際の HTTP レスポンス、ヘッダー、キャッシュ挙動の発行は、ホスティングアプリケーションの責任です。
開発上の注記
「開発上の注記」という見出しのセクション- モデルは
PlanRegistry::defaultRegistry()、1 つのOveragePolicy、QuotaManagerから構成します。アラート機能には、耐久性のあるAlertStateRepositoryInterface実装を伴うBillingAlertServiceを追加します。 QuotaEnforcementGuardは、リクエストパイプラインのテナント認証の後、課金対象ハンドラーの前にマウントします。エッジでQuotaEnforcementExceptionと課金のQuotaExceededExceptionを捕捉し、httpStatusCode()をレスポンスにマッピングします。- このモジュール内のプラン定義は、課金の単一の信頼できるソースです。デプロイメント内の他の場所で並行する課金定義を維持しないでください。
- インメモリ実装により、サーフェス全体が I/O なしでユニットテスト可能になります。推奨される境界テストは、クォータちょうどの利用状況、1 単位上、0.8 と 1.0 の利用率しきい値、プラン不一致ガード、CAS 競合(最後の余裕 1 単位に対する 2 つの予約)、そしてストア障害の拒否です。
- コアモデルのクラスは
@since 2.2.0を持ち、substrate は@since 2.3.0を持ちます。現在のパッケージラインは 3.1.0 です。 - オペレーターは、アラート状態リポジトリと利用状況ストアの実装、それらのレプリカ間の耐久性、そして
clearAlerts()を通じた期間途中のアラート再アームを所有します。
公開の境界
「公開の境界」という見出しのセクションこのページは、外部から観測できる挙動とサポートされているパブリック API サーフェスのみを文書化します。内部の名前空間パス、ヘルパークラス、メカニズムテーブル、ランブックのファイル名、チケットプレフィックスは対象外です。