Enterprise editie
Billing — Diepe referentie
In één oogopslag
Sectie met titel “In één oogopslag”Deze pagina is de diepe referentie voor het billing-oppervlak van NextPDF Enterprise. Het oppervlak kent twee lagen. Het billing-model in NextPDF\Enterprise\Billing definieert plantiers, quota, overage-beleidsregels en gededupliceerde usage-alerts. Het handhavingssubstraat in NextPDF\Enterprise\Billing\Substrate plaatst dat model op het live request-pad, fail-closed en concurrency-safe. Toegangspunten zijn PlanRegistry, QuotaManager, OverageCalculator, BillingAlertService en QuotaEnforcementGuard. Zie voor de workflow-gerichte gids de Billing-capabilitypagina.
Beschikbaarheid & licentiëring
Sectie met titel “Beschikbaarheid & licentiëring”Deze mogelijkheid wordt geleverd in NextPDF Enterprise (nextpdf/enterprise) en wordt geactiveerd met een licentie-envelop van de Enterprise-tier. Een deployment zonder dat recht laadt de classes van de mogelijkheid niet. Vergelijk edities en vraag een licentie aan.
Billing is een basismogelijkheid van Enterprise zonder aparte vlag per functie; ze is beschikbaar zodra het Enterprise-pakket naast het Core-pakket is geïnstalleerd. NextPDF Core (Apache-2.0) en NextPDF Pro hebben geen plan-, quota- of overage-model; dit oppervlak heeft geen equivalent op een lagere tier. Plan-inclusies, quota en commerciële voorwaarden worden geregeld door de licentieovereenkomst, niet door runtime-handhaving; deze referentie is geen juridisch of contractueel oordeel.
Publiek API-oppervlak
Sectie met titel “Publiek API-oppervlak”Alle symbolen bevinden zich onder NextPDF\Enterprise\Billing. Rijen gemarkeerd als substrate bevinden zich onder NextPDF\Enterprise\Billing\Substrate. TenantContext is het geauthenticeerde tenant-type uit NextPDF\Enterprise\SaaS.
| Symbool | Parameters | Standaardgedrag | Retourneert | Werpt of faalt met | Opmerkingen |
|---|---|---|---|---|---|
SaaSPlan (enum) | — | String-backed plantiers: standard, advanced, high_control | — | Werpt niet | label() retourneert de weergavenaam |
PlanDefinition::__construct | SaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncluded | Immutable plan-value-object; slaat inputs op zoals aangeleverd | Nieuwe instance | Werpt niet | final readonly; gepromote public properties |
PlanDefinition::includesCapability | CapabilityCode $capability | Strikte identity-membershipcheck | bool | Werpt niet | — |
PlanRegistry::__construct | list<PlanDefinition> $definitions | Indexeert definities per tier; de laatste definitie per tier wint | Nieuw register | Werpt niet | Voor tests en white-label-plansets |
PlanRegistry::get | SaaSPlan $plan | Canonieke plan-lookup | PlanDefinition | InvalidArgumentException wanneer het plan niet is geregistreerd | — |
PlanRegistry::has | SaaSPlan $plan | Registratieprobe | bool | Werpt niet | — |
PlanRegistry::defaultRegistry (static) | — | Productie-standaarden: Standard 1,000 CU; Advanced 5,000 CU plus Intelligence Pack; High Control 20,000 CU plus Intelligence- en Privacy Pack | PlanRegistry | Werpt niet | Gebruik dit tenzij contractuele voorwaarden maatwerkdefinities vereisen |
OveragePolicy (enum) | — | hard_stop, soft_stop, budget_alert | — | Werpt niet | httpStatusCode() mapt 402 / 429 / 200; isBlocking() is alleen true voor hard en soft stop |
QuotaManager::__construct | PlanRegistry $planRegistry, OveragePolicy $overagePolicy | Bindt het register aan één beleid | Nieuwe instance | Werpt niet | — |
QuotaManager::checkQuota | TenantContext $tenant, SaaSPlan $plan, float $currentCu | Retourneert stil op of onder quotum, of onder een niet-blokkerend beleid | void | QuotaExceededException bij strikte overage onder een blokkerend beleid; InvalidArgumentException uit het register bij een niet-geregistreerd plan | resetsAt = eerste dag van volgende maand, middernacht UTC |
QuotaManager::remainingQuota | SaaSPlan $plan, float $currentCu | Pure read; blokkeert nooit | float | Register-InvalidArgumentException | Negatief bij overage |
QuotaManager::usagePercentage | SaaSPlan $plan, float $currentCu | Pure read; blokkeert nooit | float | Register-InvalidArgumentException | 0.0 wanneer het inbegrepen quotum niet-positief is; boven 1.0 bij overage |
OverageCalculator::calculate | PlanDefinition $plan, float $currentCu | Berekent een immutable overage-snapshot | OverageResult | Werpt niet | final readonly, stateless |
OverageResult | includedCu, usedCu, overageCu, usageRatio, isOverage | Immutable berekeningsresultaat | — | Werpt niet | overageCu = max(0, used - included); isOverage vereist strikte overage |
BillingAlertType (enum) | — | quota_warning_80, quota_warning_100, budget_exceeded, monthly_cap_reached | — | Werpt niet | threshold() 0.8 / 1.0 / 1.0 / 1.0; severity() warning / critical / critical / critical |
BillingAlertService::__construct | AlertStateRepositoryInterface $alertState | Bindt de deduplicatiestore | Nieuwe instance | Werpt niet | — |
BillingAlertService::evaluate | TenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCu | Vuurt nog-niet-afgevuurde alerts af in oplopende drempelvolgorde en legt ze vast | list<BillingAlertType> | InvalidArgumentException bij plan/definitie-mismatch | Dedup-sleutel: tenant, type, UTC-YYYY-MM-periode |
BillingAlertService::clearAlerts | TenantContext $tenant | Wist de fired-state van de tenant voor de huidige UTC-periode | void | Door de repository gedefinieerde fouten propageren | Herbewapent alerts binnen dezelfde periode |
AlertStateRepositoryInterface | hasAlertFired(), markAlertFired(), clearForPeriod() | Duurzaam persistentiecontract voor alert-deduplicatie | Per methode | Implementatie-gedefinieerd | Operator is eigenaar van duurzaamheid over replica’s heen |
InMemoryAlertStateRepository | — | Array-backed fired-state | Per interface | Werpt niet | Alleen single-request-lifecycles en tests |
QuotaExceededException | Readonly currentCu, limitCu, resetsAt, tenantId, isSaaS | Deployment-mode-bewuste quota-weigering | — | Is de throwable | httpStatusCode() 402 SaaS / 403 on-prem; specCode() SPEC-BILLING-003 / SPEC-LIC-001; toErrorEnvelope() levert een gestructureerd error-body op |
DeploymentMode (enum) | — | saas, self_hosted_oss, local_development | — | Werpt niet | Substrate. enforcesQuota() is alleen true voor Saas; opt-out is altijd expliciet |
QuotaEnforcementGuard::__construct | DeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterface | Stelt de live quota-gate samen | Nieuwe instance | Werpt niet | Substrate. final readonly |
QuotaEnforcementGuard::enforce | ?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0 | Fail-closed quota-gate met atomaire reservering | QuotaDecision (alleen toegestane uitkomsten) | Zie de weigeringstaxonomie hieronder | Substrate. Monteer na tenant-authenticatie, vóór de billable handler |
PlanResolverInterface::resolve | TenantContext $tenant | Resolvet een tenant naar zijn plan en beleidsregels per functie | ResolvedPlan | NoPlanForTenantException | Substrate. Een default-plan-terugval voor onbekende tenants is een defect |
RegistryPlanResolver | array<non-empty-string, ResolvedPlan> $plansByTenant | Map-backed resolver | ResolvedPlan | NoPlanForTenantException voor niet-gemapte tenants | Substrate. Fail-closed by construction |
ResolvedPlan::policyFor | non-empty-string $featureKey | Beleids-lookup op het geresolvede plan | ?QuotaPolicy | Werpt niet | Substrate. null betekent onbekende feature; de guard weigert die |
QuotaPolicy | non-empty-string $featureKey, float $limit, OveragePolicy $overagePolicy | Limiet per functie en breach-beleid | — | Werpt niet | Substrate. UNLIMITED = -1.0; een limiet van 0.0 is nul-toelage, niet unlimited; isUnlimited(), isBlocking() |
QuotaDecision | Statics bypassed(), unlimited(), consumed() | Value-object voor toegestane uitkomst | QuotaDecision | Werpt niet | Substrate. isAllowed() is altijd true; elke weigering werpt in plaats daarvan |
UsageCounter | Rij-snapshot: tenant, feature, periodegrenzen, used, limit, updatedAt | Immutable usage-rij | — | Werpt niet | Substrate. remaining() kan negatief zijn; wouldExceed() is strikt |
UsageCounterStoreInterface::get | Tenant, feature, periodegrenzen, float $limit | Leest de usage-rij en maakt die aan met used = 0 wanneer afwezig | UsageCounter | UsageStoreUnavailableException | Substrate. Retourneert nooit een falsy waarde bij backend-fout |
UsageCounterStoreInterface::tryConsume | Tenant, feature, periodegrenzen, float $amount, float $limit | Atomaire compare-and-set-reservering binnen de limiet | ?UsageCounter (null wanneer de reservering de limiet zou overschrijden) | UsageStoreUnavailableException | Substrate. Moet één enkele atomaire operatie tegen de backing store zijn |
InMemoryUsageCounterStore | — | In-process referentie-implementatie van het store-contract | Per interface | Per interface | Substrate. Alleen single-process; documenteert de atomiciteitsinvariant |
QuotaEnforcementException (abstract) | — | Basistype van elke substrate-weigering | — | Is de throwable-familie | Substrate. Elk subtype declareert 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;Weigeringstaxonomie van QuotaEnforcementGuard::enforce
| Exceptie | HTTP-status | Geworpen wanneer |
|---|---|---|
MissingTenantContextException | 401 | SaaS-modus zonder geauthenticeerde tenant-context |
NoPlanForTenantException | 402 | De resolver vindt geen plan toegewezen aan de tenant |
UnknownFeatureException | 402 | Het geresolvede plan definieert geen beleid voor de feature-key |
UsageStoreUnavailableException | 503 | De usage store kan niet worden gelezen of atomair bijgewerkt; ook geworpen bij een niet-positieve $amount |
QuotaExceededException | 402 (SaaS) / 403 (on-prem) | Het quotum van een blokkerend beleid wordt overschreden, of een gelijktijdige reservering heeft de laatste ruimte verbruikt |
Gedragscontract
Sectie met titel “Gedragscontract”- Het standaardregister levert drie tiers (Standard / Advanced / High Control) met toenemende CU-quota en capabilitysets. Een aanvraag voor een niet-geregistreerd plan faalt met een expliciete
InvalidArgumentException. QuotaManager::checkQuota()werpt alleen wanneer beide condities gelden: het beleid is blokkerend en het huidige gebruik ligt strikt boven het inbegrepen quotum. Een budget-alert-beleid werpt nooit; overage wordt via alerts gesignaleerd.remainingQuota()enusagePercentage()zijn pure reads en blokkeren nooit. Het resterende quotum gaat negatief bij overage; het gebruikspercentage overschrijdt1.0bij overage.- Alerts evalueren in oplopende drempelvolgorde: 80% warning, 100% warning (critical), daarna budget-exceeded (critical). Budget-exceeded is gegate op strikte overage; gebruik op precies 100% vuurt de 100% warning af, niet budget-exceeded.
- Elk alerttype vuurt hoogstens één keer per tenant per billing-periode af. De fired-state wordt vastgelegd via
AlertStateRepositoryInterface, dus deduplicatie is zo duurzaam als de gekozen implementatie. - De deduplicatiesleutel bevat de UTC-
YYYY-MM-periode. Een nieuwe kalendermaand herbewapent daarom automatisch elk alerttype; er is geen clear-aanroep nodig voor het herbewapenen bij rollover.clearAlerts()wist de huidige periode, wat alerts midden in de periode herbewapent, bijvoorbeeld na een plan-upgrade. - Een plan-mismatch-guard in
evaluate()wijst een aanroep af waarbij het aangeleverde plan en de plandefinitie niet overeenkomen, ter bescherming tegen een definitie van een andere tier dan het plan van de tenant. - Alle periode-rekenkunde is verankerd aan UTC. Het quota-exceeded-reset-moment is de eerste dag van de volgende kalendermaand om middernacht UTC; een soft-stop-respons zou dit als retry-horizon moeten adverteren.
QuotaEnforcementGuardis fail-closed in SaaS-modus. Ontbrekende tenant, ontbrekend plan, onbekende feature, store-uitval en quota-breach weigeren allemaal; niets valt door naar een impliciete allow. Non-SaaS-deployments doen alleen opt-out door de guard te construeren met een non-SaaS-DeploymentMode.- Blokkerende beleidsregels reserveren gebruik via
UsageCounterStoreInterface::tryConsume, een atomaire compare-and-set. Gelijktijdige requests kunnen het gebruik niet gezamenlijk voorbij de limiet duwen; de verliezer van de race ontvangtQuotaExceededExceptionook al slaagde de pre-check. - Onder een budget-alert-beleid legt de guard verbruik best-effort vast en weigert nooit; een reservering voorbij het zachte plafond legt de rij nog steeds vast op de limiet.
QuotaExceededExceptionis deployment-mode-bewust: SaaS-weigeringen mappen naar HTTP 402 met spec-codeSPEC-BILLING-003en worden als retryable gemarkeerd; on-prem-weigeringen mappen naar HTTP 403 metSPEC-LIC-001.- De library zendt zelf geen HTTP-responses uit. De gedeclareerde statuscodes zijn het contract voor de edge-laag, die een geworpen weigering naar een respons mapt en de billable handler niet mag aanroepen.
Randgevallen & faalmodi
Sectie met titel “Randgevallen & faalmodi”- Niet-positief inbegrepen quotum.
usagePercentage(),evaluate()enOverageCalculator::calculate()leveren allemaal een0.0-gebruiksratio op in plaats van te delen door nul. Drempelalerts vuren dan nooit af op basis van de ratio alleen. - Budget-alert plus grote overage. De manager en de guard retourneren beide toegestane uitkomsten. Behandel de afwezigheid van een exceptie niet als bewijs dat je binnen het quotum zit; raadpleeg
OverageResultof de alert-stream. - Precies op de limiet.
checkQuota()bijcurrentCu == includedCuQuotaslaagt.BudgetExceededvereist strikte overage.UsageCounter::wouldExceed()is eveneens strikt. MonthlyCapReached. De enum declareert dit vierde alerttype, maarBillingAlertService::evaluate()zendt het nooit uit; de kandidatenlijst dekt alleen de drie drempelalerts. Het is gereserveerd voor cap-tracking-emitters buiten deze module.- Dubbele tierdefinities.
PlanRegistryindexeert op tierwaarde; de laatste definitie voor een tier vervangt eerdere stilzwijgend. Construeer registers vanuit een gededupliceerde lijst. - Nul-toelage versus unlimited. Een
QuotaPolicy-limiet van0.0betekent dat elk verbruik in de periode overage is. Alleen de negatieveUNLIMITED-sentinel schakelt metering uit;isUnlimited()blokkeert nooit. - Niet-positieve reserveringshoeveelheid.
enforce()weigert een niet-positieve$amountfail-closed metUsageStoreUnavailableException(503). Dit is een aanroeperdefect, geen store-uitval. - Store-uitval. Elke lees- of reserveringsfout verschijnt als
UsageStoreUnavailableExceptionen weigert. De guard staat nooit ongemeten werk toe terwijl de meter uit staat. - In-memory-implementaties.
InMemoryAlertStateRepositoryenInMemoryUsageCounterStorezijn alleen correct binnen één PHP-proces. Multi-replica-deployments moeten implementaties leveren die worden ondersteund door een datastore met echte atomiciteit; een read-then-write-store is een defect dat over-quotum onder belasting toelaat. - FIPS-modus. Billing voert geen eigen cryptografische bewerkingen uit en heeft geen FIPS-specifiek gedrag. De tenant-identiteit die het consumeert, moet afkomstig zijn uit een geauthenticeerde context wiens FIPS-positie is gedocumenteerd bij het SaaS-oppervlak.
Conformiteit
Sectie met titel “Conformiteit”| Claim | Standaard | Clausule |
|---|---|---|
| De 402-statuscode is gereserveerd voor toekomstig gebruik; ze draagt zelf geen normatieve request-semantiek. | RFC 9110 | §15.5.3 |
| 429 geeft aan dat de client te veel requests binnen een gegeven tijdspanne heeft verzonden (“rate limiting”). | RFC 6585 | §4 |
| Retry-After geeft aan hoe lang de user agent zou moeten wachten voordat hij een vervolgrequest doet. | RFC 9110 | §10.2.3 |
Alle clausules zijn geparafraseerd; NextPDF reproduceert geen normatieve tekst. NextPDF doet geen enkele HTTP-protocol-conformiteits- of certificeringsclaim voor dit oppervlak. De 402 / 429 / 200-mapping die OveragePolicy::httpStatusCode() declareert en de 401 / 402 / 503-weigeringscodes van de guard zijn een productconventie die is afgestemd op de clausules hierboven: RFC 9110 reserveert 402, dus het gebruik ervan voor betalingsweigering hier is de gangbare industrieconventie, geen door de IETF gedefinieerde semantiek. De soft-stop-retry-horizon (resetsAt) is de waarde die een edge-laag als Retry-After-begeleiding zou moeten tonen. Het uitzenden van daadwerkelijke HTTP-responses, headers en cachegedrag is de verantwoordelijkheid van de hostende applicatie.
Ontwikkelnotities
Sectie met titel “Ontwikkelnotities”- Stel het model samen uit
PlanRegistry::defaultRegistry(), éénOveragePolicyen eenQuotaManager; voegBillingAlertServicetoe met een duurzameAlertStateRepositoryInterface-implementatie voor alerting. - Monteer
QuotaEnforcementGuardin de request-pipeline na tenant-authenticatie en vóór de billable handler. VangQuotaEnforcementExceptionen de billing-QuotaExceededExceptionaf aan de edge en maphttpStatusCode()naar de respons. - Plandefinities in deze module zijn de enige bron van waarheid voor billing; onderhoud nergens anders in je deployment een parallelle billing-definitie.
- De in-memory-implementaties maken het hele oppervlak unit-testbaar zonder I/O. Aanbevolen grenswaardetests: gebruik precies op het quotum, één eenheid erboven, ratiodrempels op 0.8 en 1.0, de plan-mismatch-guard, de CAS-race (twee reserveringen tegen de laatste eenheid ruimte) en store-uitval-weigering.
- De core-modelclasses dragen
@since 2.2.0; het substraat draagt@since 2.3.0. De huidige pakketlijn is 3.1.0. - De operator is eigenaar van de alert-state-repository- en usage-store-implementaties, hun duurzaamheid over replica’s heen, en elk herbewapenen van alerts midden in de periode via
clearAlerts().
Publicatiegrens
Sectie met titel “Publicatiegrens”Deze pagina documenteert alleen extern waarneembaar gedrag en het ondersteunde publieke API-oppervlak. Interne namespace-paden, helper-classes, mechanismetabellen, runbook-bestandsnamen en ticketprefixen vallen buiten de scope.