Enterprise Edition
Abrechnung — Ausführliche Referenz
Auf einen Blick
Abschnitt betitelt „Auf einen Blick“Diese Seite ist die ausführliche Referenz für die Abrechnungsfläche von NextPDF Enterprise. Die Fläche hat zwei Schichten. Das Abrechnungsmodell in NextPDF\Enterprise\Billing definiert Tarifstufen, Kontingente, Überschreitungsrichtlinien und deduplizierte Nutzungsalarme. Das Durchsetzungssubstrat in NextPDF\Enterprise\Billing\Substrate platziert dieses Modell auf dem aktiven Anfragepfad, ausfallsicher schließend und nebenläufigkeitssicher. Einstiegspunkte sind PlanRegistry, QuotaManager, OverageCalculator, BillingAlertService und QuotaEnforcementGuard. Für die Anleitung auf Workflow-Ebene siehe die Seite zur Abrechnungsfunktion.
Verfügbarkeit & Lizenzierung
Abschnitt betitelt „Verfügbarkeit & Lizenzierung“Diese Funktion wird mit NextPDF Enterprise (nextpdf/enterprise) ausgeliefert und aktiviert sich mit einer Lizenzhülle der Enterprise-Stufe. Eine Bereitstellung ohne diese Berechtigung lädt die Klassen der Funktion nicht. Editionen vergleichen und eine Lizenz erwerben.
Abrechnung ist eine grundlegende Enterprise-Funktion ohne separaten Schalter je Feature; sie ist verfügbar, sobald das Enterprise-Paket neben dem Core-Paket installiert ist. NextPDF Core (Apache-2.0) und NextPDF Pro haben kein Tarif-, Kontingent- oder Überschreitungsmodell; für diese Fläche gibt es keine Entsprechung in einer niedrigeren Stufe. Tarifinhalte, Kontingente und Geschäftsbedingungen unterliegen dem Lizenzvertrag, nicht der Laufzeitdurchsetzung; diese Referenz ist keine rechtliche oder vertragliche Stellungnahme.
Öffentliche API-Fläche
Abschnitt betitelt „Öffentliche API-Fläche“Alle Symbole leben unter NextPDF\Enterprise\Billing. Mit Substrat gekennzeichnete Zeilen leben unter NextPDF\Enterprise\Billing\Substrate. TenantContext ist der authentifizierte Mandantentyp aus NextPDF\Enterprise\SaaS.
| Symbol | Parameter | Standardverhalten | Rückgabe | Wirft oder scheitert mit | Hinweise |
|---|---|---|---|---|---|
SaaSPlan (enum) | — | String-gestützte Tarifstufen: standard, advanced, high_control | — | Wirft nicht | label() gibt den Anzeigenamen zurück |
PlanDefinition::__construct | SaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncluded | Unveränderliches Tarif-Wertobjekt; speichert die Eingaben wie übergeben | Neue Instanz | Wirft nicht | final readonly; hochgezogene öffentliche Eigenschaften |
PlanDefinition::includesCapability | CapabilityCode $capability | Strikte Identitätsprüfung der Zugehörigkeit | bool | Wirft nicht | — |
PlanRegistry::__construct | list<PlanDefinition> $definitions | Indiziert Definitionen nach Stufe; die letzte Definition je Stufe gewinnt | Neues Register | Wirft nicht | Für Tests und White-Label-Tarifsätze |
PlanRegistry::get | SaaSPlan $plan | Kanonische Tarifsuche | PlanDefinition | InvalidArgumentException, wenn der Tarif nicht registriert ist | — |
PlanRegistry::has | SaaSPlan $plan | Registrierungsprüfung | bool | Wirft nicht | — |
PlanRegistry::defaultRegistry (static) | — | Produktionsstandards: Standard 1.000 CU; Advanced 5.000 CU plus Intelligence Pack; High Control 20.000 CU plus Intelligence- und Privacy-Pack | PlanRegistry | Wirft nicht | Verwenden Sie dies, sofern vertragliche Bedingungen keine eigenen Definitionen erfordern |
OveragePolicy (enum) | — | hard_stop, soft_stop, budget_alert | — | Wirft nicht | httpStatusCode() bildet auf 402 / 429 / 200 ab; isBlocking() ist nur bei Hard- und Soft-Stop wahr |
QuotaManager::__construct | PlanRegistry $planRegistry, OveragePolicy $overagePolicy | Bindet das Register an eine Richtlinie | Neue Instanz | Wirft nicht | — |
QuotaManager::checkQuota | TenantContext $tenant, SaaSPlan $plan, float $currentCu | Kehrt bei oder unter dem Kontingent oder unter einer nicht blockierenden Richtlinie stillschweigend zurück | void | QuotaExceededException bei strikter Überschreitung unter einer blockierenden Richtlinie; InvalidArgumentException aus dem Register bei einem nicht registrierten Tarif | resetsAt = erster Tag des nächsten Monats, Mitternacht UTC |
QuotaManager::remainingQuota | SaaSPlan $plan, float $currentCu | Reine Lesung; blockiert nie | float | InvalidArgumentException des Registers | Negativ bei Überschreitung |
QuotaManager::usagePercentage | SaaSPlan $plan, float $currentCu | Reine Lesung; blockiert nie | float | InvalidArgumentException des Registers | 0.0, wenn das enthaltene Kontingent nicht positiv ist; über 1.0 bei Überschreitung |
OverageCalculator::calculate | PlanDefinition $plan, float $currentCu | Berechnet eine unveränderliche Überschreitungs-Momentaufnahme | OverageResult | Wirft nicht | final readonly, zustandslos |
OverageResult | includedCu, usedCu, overageCu, usageRatio, isOverage | Unveränderliches Berechnungsergebnis | — | Wirft nicht | overageCu = max(0, used - included); isOverage erfordert strikte Überschreitung |
BillingAlertType (enum) | — | quota_warning_80, quota_warning_100, budget_exceeded, monthly_cap_reached | — | Wirft nicht | threshold() 0.8 / 1.0 / 1.0 / 1.0; severity() warning / critical / critical / critical |
BillingAlertService::__construct | AlertStateRepositoryInterface $alertState | Bindet den Deduplizierungsspeicher | Neue Instanz | Wirft nicht | — |
BillingAlertService::evaluate | TenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCu | Feuert noch nicht ausgelöste Alarme in aufsteigender Schwellenreihenfolge und protokolliert sie | list<BillingAlertType> | InvalidArgumentException bei Tarif-/Definitionsdiskrepanz | Dedup-Schlüssel: Mandant, Typ, UTC-Periode YYYY-MM |
BillingAlertService::clearAlerts | TenantContext $tenant | Setzt den Auslösezustand des Mandanten für die aktuelle UTC-Periode zurück | void | Repository-definierte Fehler werden weitergereicht | Schärft Alarme innerhalb derselben Periode neu |
AlertStateRepositoryInterface | hasAlertFired(), markAlertFired(), clearForPeriod() | Vertrag für die dauerhafte Alarmdeduplizierungs-Persistenz | Je Methode | Implementierungsdefiniert | Der Operator verantwortet die Haltbarkeit über Replikate hinweg |
InMemoryAlertStateRepository | — | Array-gestützter Auslösezustand | Je Schnittstelle | Wirft nicht | Nur für Einzelanfrage-Lebenszyklen und Tests |
QuotaExceededException | Readonly currentCu, limitCu, resetsAt, tenantId, isSaaS | Bereitstellungsmodus-bewusste Kontingentverweigerung | — | Ist der Werfbare | httpStatusCode() 402 SaaS / 403 On-Prem; specCode() SPEC-BILLING-003 / SPEC-LIC-001; toErrorEnvelope() liefert einen strukturierten Fehlerkörper |
DeploymentMode (enum) | — | saas, self_hosted_oss, local_development | — | Wirft nicht | Substrat. enforcesQuota() ist nur für Saas wahr; das Abwählen ist stets explizit |
QuotaEnforcementGuard::__construct | DeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterface | Fügt das aktive Kontingenttor zusammen | Neue Instanz | Wirft nicht | Substrat. final readonly |
QuotaEnforcementGuard::enforce | ?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0 | Ausfallsicher schließendes Kontingenttor mit atomarer Reservierung | QuotaDecision (nur erlaubte Ergebnisse) | Siehe die Verweigerungstaxonomie unten | Substrat. Nach der Mandantenauthentifizierung, vor dem abrechenbaren Handler einhängen |
PlanResolverInterface::resolve | TenantContext $tenant | Löst einen Mandanten zu seinem Tarif und den Richtlinien je Feature auf | ResolvedPlan | NoPlanForTenantException | Substrat. Ein Standardtarif-Rückfall für unbekannte Mandanten ist ein Defekt |
RegistryPlanResolver | array<non-empty-string, ResolvedPlan> $plansByTenant | Map-gestützter Resolver | ResolvedPlan | NoPlanForTenantException für nicht zugeordnete Mandanten | Substrat. Ausfallsicher schließend per Konstruktion |
ResolvedPlan::policyFor | non-empty-string $featureKey | Richtliniensuche auf dem aufgelösten Tarif | ?QuotaPolicy | Wirft nicht | Substrat. null bedeutet unbekanntes Feature; das Tor verweigert es |
QuotaPolicy | non-empty-string $featureKey, float $limit, OveragePolicy $overagePolicy | Limit und Verletzungsrichtlinie je Feature | — | Wirft nicht | Substrat. UNLIMITED = -1.0; ein Limit von 0.0 ist kein Kontingent, nicht unbegrenzt; isUnlimited(), isBlocking() |
QuotaDecision | Statisch bypassed(), unlimited(), consumed() | Wertobjekt für erlaubte Ergebnisse | QuotaDecision | Wirft nicht | Substrat. isAllowed() ist stets wahr; jede Verweigerung wirft stattdessen |
UsageCounter | Zeilen-Momentaufnahme: Mandant, Feature, Periodengrenzen, used, limit, updatedAt | Unveränderliche Nutzungszeile | — | Wirft nicht | Substrat. remaining() kann negativ sein; wouldExceed() ist strikt |
UsageCounterStoreInterface::get | Mandant, Feature, Periodengrenzen, float $limit | Liest die Nutzungszeile und legt sie bei Abwesenheit mit used = 0 an | UsageCounter | UsageStoreUnavailableException | Substrat. Gibt bei Backend-Ausfall nie einen falsy-Wert zurück |
UsageCounterStoreInterface::tryConsume | Mandant, Feature, Periodengrenzen, float $amount, float $limit | Atomare Compare-and-Set-Reservierung innerhalb des Limits | ?UsageCounter (null, wenn die Reservierung das Limit verletzen würde) | UsageStoreUnavailableException | Substrat. Muss eine einzelne atomare Operation gegen den zugrunde liegenden Speicher sein |
InMemoryUsageCounterStore | — | In-Prozess-Referenzimplementierung des Speichervertrags | Je Schnittstelle | Je Schnittstelle | Substrat. Nur ein einzelner Prozess; dokumentiert die Atomaritätsinvariante |
QuotaEnforcementException (abstract) | — | Basistyp jeder Substratverweigerung | — | Ist die Werfbaren-Familie | Substrat. Jeder Untertyp deklariert 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;Verweigerungstaxonomie von QuotaEnforcementGuard::enforce
| Ausnahme | HTTP-Status | Ausgelöst bei |
|---|---|---|
MissingTenantContextException | 401 | SaaS-Modus ohne authentifizierten Mandantenkontext |
NoPlanForTenantException | 402 | Der Resolver findet keinen dem Mandanten zugewiesenen Tarif |
UnknownFeatureException | 402 | Der aufgelöste Tarif definiert keine Richtlinie für den Feature-Schlüssel |
UsageStoreUnavailableException | 503 | Der Nutzungsspeicher kann nicht gelesen oder atomar aktualisiert werden; wird auch bei einem nicht positiven $amount ausgelöst |
QuotaExceededException | 402 (SaaS) / 403 (On-Prem) | Das Kontingent einer blockierenden Richtlinie ist überschritten oder eine nebenläufige Reservierung hat den letzten Spielraum verbraucht |
Verhaltensvertrag
Abschnitt betitelt „Verhaltensvertrag“- Das Standardregister liefert drei Stufen (Standard / Advanced / High Control) mit steigenden CU-Kontingenten und Fähigkeitssätzen. Eine Anfrage nach einem nicht registrierten Tarif scheitert mit einer expliziten
InvalidArgumentException. QuotaManager::checkQuota()wirft nur, wenn beide Bedingungen gelten: Die Richtlinie ist blockierend und die aktuelle Nutzung liegt strikt über dem enthaltenen Kontingent. Eine Budget-Alarm-Richtlinie wirft nie; Überschreitung wird über Alarme signalisiert.remainingQuota()undusagePercentage()sind reine Lesungen und blockieren nie. Das verbleibende Kontingent wird bei Überschreitung negativ; der Nutzungsprozentsatz überschreitet bei Überschreitung1.0.- Alarme werden in aufsteigender Schwellenreihenfolge ausgewertet: 80%-Warnung, 100%-Warnung (kritisch), dann Budget-überschritten (kritisch). Budget-überschritten ist an strikte Überschreitung gebunden; eine Nutzung von exakt 100% löst die 100%-Warnung aus, nicht Budget-überschritten.
- Jeder Alarmtyp feuert höchstens einmal je Mandant je Abrechnungsperiode. Der Auslösezustand wird über
AlertStateRepositoryInterfaceprotokolliert, sodass die Deduplizierung so dauerhaft ist wie die gewählte Implementierung. - Der Deduplizierungsschlüssel bettet die UTC-Periode
YYYY-MMein. Ein neuer Kalendermonat schärft daher automatisch jeden Alarmtyp neu; für die Übergangs-Neuschärfung ist kein Clear-Aufruf erforderlich.clearAlerts()setzt die aktuelle Periode zurück, was Alarme mitten in der Periode neu schärft, etwa nach einem Tarif-Upgrade. - Ein Tarifdiskrepanz-Wächter in
evaluate()weist einen Aufruf zurück, bei dem der übergebene Tarif und die Tarifdefinition nicht übereinstimmen, und schützt so vor einer Definition aus einer anderen Stufe als dem Tarif des Mandanten. - Sämtliche Periodenarithmetik ist an UTC verankert. Der Rücksetzzeitpunkt bei Kontingentüberschreitung ist der erste Tag des nächsten Kalendermonats um Mitternacht UTC; eine Soft-Stop-Antwort sollte ihn als Wiederholungshorizont ausweisen.
QuotaEnforcementGuardschließt im SaaS-Modus ausfallsicher. Fehlender Mandant, fehlender Tarif, unbekanntes Feature, Speicherausfall und Kontingentverletzung verweigern allesamt; nichts fällt in ein implizites Erlauben durch. Nicht-SaaS-Bereitstellungen wählen sich nur ab, indem der Wächter mit einem Nicht-SaaS-DeploymentModekonstruiert wird.- Blockierende Richtlinien reservieren Nutzung über
UsageCounterStoreInterface::tryConsume, ein atomares Compare-and-Set. Nebenläufige Anfragen können die Nutzung nicht gemeinsam über das Limit treiben; der Verlierer des Wettlaufs erhältQuotaExceededException, obwohl die Vorprüfung bestanden wurde. - Unter einer Budget-Alarm-Richtlinie protokolliert der Wächter den Verbrauch nach bestem Bemühen und verweigert nie; eine Reservierung über die weiche Obergrenze hinaus protokolliert die Zeile dennoch am Limit.
QuotaExceededExceptionist bereitstellungsmodus-bewusst: SaaS-Verweigerungen bilden auf HTTP 402 mit Spec-CodeSPEC-BILLING-003ab und sind als wiederholbar markiert; On-Prem-Verweigerungen bilden auf HTTP 403 mitSPEC-LIC-001ab.- Die Bibliothek gibt selbst keine HTTP-Antworten aus. Die deklarierten Statuscodes sind der Vertrag für die Edge-Schicht, die eine geworfene Verweigerung auf eine Antwort abbildet und den abrechenbaren Handler nicht aufrufen darf.
Grenzfälle & Fehlermodi
Abschnitt betitelt „Grenzfälle & Fehlermodi“- Nicht positives enthaltenes Kontingent.
usagePercentage(),evaluate()undOverageCalculator::calculate()liefern allesamt ein Nutzungsverhältnis von0.0, statt durch null zu teilen. Schwellenalarme feuern dann nie allein aus dem Verhältnis. - Budget-Alarm plus große Überschreitung. Der Manager und der Wächter geben beide erlaubte Ergebnisse zurück. Behandeln Sie das Ausbleiben einer Ausnahme nicht als Beleg dafür, innerhalb des Kontingents zu liegen; ziehen Sie
OverageResultoder den Alarmstrom heran. - Exakt am Limit.
checkQuota()beicurrentCu == includedCuQuotabesteht.BudgetExceedederfordert strikte Überschreitung.UsageCounter::wouldExceed()ist ebenfalls strikt. MonthlyCapReached. Das Enum deklariert diesen vierten Alarmtyp, aberBillingAlertService::evaluate()gibt ihn nie aus; seine Kandidatenliste deckt nur die drei Schwellenalarme ab. Er ist für Cap-Tracking-Emitter außerhalb dieses Moduls reserviert.- Doppelte Stufendefinitionen.
PlanRegistryindiziert nach Stufenwert; die letzte Definition einer Stufe ersetzt frühere stillschweigend. Konstruieren Sie Register aus einer deduplizierten Liste. - Kein Kontingent versus unbegrenzt. Ein
QuotaPolicy-Limit von0.0bedeutet, dass jeder Verbrauch in der Periode Überschreitung ist. Nur das negativeUNLIMITED-Sentinel deaktiviert die Messung;isUnlimited()blockiert nie. - Nicht positive Reservierungsmenge.
enforce()verweigert einen nicht positiven$amountausfallsicher mitUsageStoreUnavailableException(503). Dies ist ein Aufruferdefekt, kein Speicherausfall. - Speicherausfall. Jeder Lese- oder Reservierungsfehler tritt als
UsageStoreUnavailableExceptionzutage und verweigert. Der Wächter erlaubt nie ungemessene Arbeit, während der Zähler ausgefallen ist. - In-Memory-Implementierungen.
InMemoryAlertStateRepositoryundInMemoryUsageCounterStoresind nur innerhalb eines PHP-Prozesses korrekt. Bereitstellungen mit mehreren Replikaten müssen Implementierungen bereitstellen, die von einem Datenspeicher mit echter Atomarität gestützt sind; ein Read-then-Write-Speicher ist ein Defekt, der unter Last eine Überschreitung des Kontingents erlaubt. - FIPS-Modus. Abrechnung führt keine eigenen kryptografischen Operationen durch und hat kein FIPS-spezifisches Verhalten. Die Mandantenidentität, die sie verbraucht, muss aus einem authentifizierten Kontext stammen, dessen FIPS-Haltung mit der SaaS-Fläche dokumentiert ist.
Konformität
Abschnitt betitelt „Konformität“| Aussage | Standard | Klausel |
|---|---|---|
| Der Statuscode 402 ist für die künftige Verwendung reserviert; er trägt keine eigene normative Anfragesemantik. | RFC 9110 | §15.5.3 |
| 429 zeigt an, dass der Client in einem gegebenen Zeitraum zu viele Anfragen gesendet hat („Rate-Limiting”). | RFC 6585 | §4 |
| Retry-After gibt an, wie lange der User-Agent vor einer Folgeanfrage warten sollte. | RFC 9110 | §10.2.3 |
Alle Klauseln sind paraphrasiert; NextPDF gibt keinen normativen Text wieder. NextPDF erhebt für diese Fläche keinen Anspruch auf HTTP-Protokollkonformität oder -Zertifizierung. Die von OveragePolicy::httpStatusCode() deklarierte Abbildung 402 / 429 / 200 und die Verweigerungscodes 401 / 402 / 503 des Wächters sind eine mit den obigen Klauseln abgestimmte Produktkonvention: RFC 9110 reserviert 402, sodass dessen Verwendung hier für Zahlungsverweigerung die übliche Branchenkonvention ist, nicht eine von der IETF definierte Semantik. Der Soft-Stop-Wiederholungshorizont (resetsAt) ist der Wert, den eine Edge-Schicht als Retry-After-Hinweis ausweisen sollte. Das Ausgeben tatsächlicher HTTP-Antworten, Header und des Caching-Verhaltens liegt in der Verantwortung der hostenden Anwendung.
Entwicklungshinweise
Abschnitt betitelt „Entwicklungshinweise“- Setzen Sie das Modell aus
PlanRegistry::defaultRegistry(), einerOveragePolicyund einemQuotaManagerzusammen; ergänzen SieBillingAlertServicemit einer dauerhaftenAlertStateRepositoryInterface-Implementierung für die Alarmierung. - Hängen Sie
QuotaEnforcementGuardin der Anfragepipeline nach der Mandantenauthentifizierung und vor dem abrechenbaren Handler ein. Fangen SieQuotaEnforcementExceptionund die Abrechnungs-QuotaExceededExceptionan der Edge ab und bilden SiehttpStatusCode()auf die Antwort ab. - Tarifdefinitionen in diesem Modul sind die alleinige Quelle der Wahrheit für die Abrechnung; führen Sie in Ihrer Bereitstellung keine parallele Abrechnungsdefinition an anderer Stelle.
- Die In-Memory-Implementierungen machen die gesamte Fläche ohne I/O unit-testbar. Empfohlene Grenzfalltests: Nutzung exakt am Kontingent, eine Einheit darüber, Verhältnisschwellen bei 0.8 und 1.0, der Tarifdiskrepanz-Wächter, der CAS-Wettlauf (zwei Reservierungen gegen die letzte Einheit Spielraum) und die Verweigerung bei Speicherausfall.
- Die Kernmodellklassen tragen
@since 2.2.0; das Substrat trägt@since 2.3.0. Die aktuelle Paketlinie ist 3.1.0. - Der Operator verantwortet die Implementierungen des Alarmzustands-Repositorys und des Nutzungsspeichers, ihre Haltbarkeit über Replikate hinweg und jede Neuschärfung von Alarmen mitten in der Periode über
clearAlerts().
Publikationsgrenze
Abschnitt betitelt „Publikationsgrenze“Diese Seite dokumentiert nur extern beobachtbares Verhalten und die unterstützte öffentliche API-Fläche. Interne Namespace-Pfade, Hilfsklassen, Mechanismustabellen, Runbook-Dateinamen und Ticket-Präfixe sind nicht im Umfang.