Enterprise edizione
Billing — Riferimento approfondito
In breve
Sezione intitolata “In breve”Questa pagina è il riferimento approfondito per la superficie di billing di NextPDF Enterprise. La superficie ha due layer. Il modello di billing in NextPDF\Enterprise\Billing definisce i tier di piano, le quote, le politiche di overage e gli avvisi di utilizzo deduplicati. Il substrate di applicazione in NextPDF\Enterprise\Billing\Substrate colloca quel modello sul percorso di richiesta attivo, fail-closed e sicuro per la concorrenza. I punti d’ingresso sono PlanRegistry, QuotaManager, OverageCalculator, BillingAlertService e QuotaEnforcementGuard. Per la guida a livello di workflow, vedere la pagina della capability Billing.
Disponibilità e licenza
Sezione intitolata “Disponibilità e licenza”Questa capability è distribuita in NextPDF Enterprise (nextpdf/enterprise) e si attiva con un envelope di licenza di livello Enterprise. Una distribuzione priva di tale entitlement non carica le classi della capability. Confronta le edizioni e ottieni una licenza.
Il billing è una capability Enterprise di base senza un flag per-feature separato; è disponibile una volta installato il pacchetto Enterprise accanto al pacchetto Core. NextPDF Core (Apache-2.0) e NextPDF Pro non dispongono di alcun modello di piano, quota o overage; questa superficie non ha un equivalente di livello inferiore. Le inclusioni dei piani, le quote e i termini commerciali sono regolati dal contratto di licenza, non dall’applicazione in fase di esecuzione; questo riferimento non è un parere legale o contrattuale.
Superficie dell’API pubblica
Sezione intitolata “Superficie dell’API pubblica”Tutti i simboli risiedono sotto NextPDF\Enterprise\Billing. Le righe contrassegnate substrate risiedono sotto NextPDF\Enterprise\Billing\Substrate. TenantContext è il tipo tenant autenticato di NextPDF\Enterprise\SaaS.
| Simbolo | Parametri | Comportamento predefinito | Restituisce | Solleva o fallisce con | Note |
|---|---|---|---|---|---|
SaaSPlan (enum) | — | Tier di piano basati su stringa: standard, advanced, high_control | — | Non solleva | label() restituisce il nome visualizzato |
PlanDefinition::__construct | SaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncluded | Value object immutabile del piano; memorizza gli input così come forniti | Nuova istanza | Non solleva | final readonly; proprietà pubbliche promosse |
PlanDefinition::includesCapability | CapabilityCode $capability | Controllo di appartenenza per identità stretta | bool | Non solleva | — |
PlanRegistry::__construct | list<PlanDefinition> $definitions | Indicizza le definizioni per tier; vince l’ultima definizione per tier | Nuovo registry | Non solleva | Per i test e gli insiemi di piani white-label |
PlanRegistry::get | SaaSPlan $plan | Ricerca canonica del piano | PlanDefinition | InvalidArgumentException quando il piano non è registrato | — |
PlanRegistry::has | SaaSPlan $plan | Sonda di registrazione | bool | Non solleva | — |
PlanRegistry::defaultRegistry (static) | — | Impostazioni predefinite di produzione: Standard 1,000 CU; Advanced 5,000 CU più Intelligence Pack; High Control 20,000 CU più Intelligence e Privacy Pack | PlanRegistry | Non solleva | Da usare a meno che i termini contrattuali non richiedano definizioni personalizzate |
OveragePolicy (enum) | — | hard_stop, soft_stop, budget_alert | — | Non solleva | httpStatusCode() mappa 402 / 429 / 200; isBlocking() è true solo per hard e soft stop |
QuotaManager::__construct | PlanRegistry $planRegistry, OveragePolicy $overagePolicy | Lega il registry a una politica | Nuova istanza | Non solleva | — |
QuotaManager::checkQuota | TenantContext $tenant, SaaSPlan $plan, float $currentCu | Ritorna in silenzio a quota o sotto quota, oppure sotto una politica non bloccante | void | QuotaExceededException in caso di overage stretto sotto una politica bloccante; InvalidArgumentException dal registry per un piano non registrato | resetsAt = primo giorno del mese successivo, mezzanotte UTC |
QuotaManager::remainingQuota | SaaSPlan $plan, float $currentCu | Lettura pura; non blocca mai | float | InvalidArgumentException dal registry | Negativa in overage |
QuotaManager::usagePercentage | SaaSPlan $plan, float $currentCu | Lettura pura; non blocca mai | float | InvalidArgumentException dal registry | 0.0 quando la quota inclusa è non positiva; superiore a 1.0 in overage |
OverageCalculator::calculate | PlanDefinition $plan, float $currentCu | Calcola uno snapshot immutabile dell’overage | OverageResult | Non solleva | final readonly, senza stato |
OverageResult | includedCu, usedCu, overageCu, usageRatio, isOverage | Risultato di calcolo immutabile | — | Non solleva | overageCu = max(0, used - included); isOverage richiede un overage stretto |
BillingAlertType (enum) | — | quota_warning_80, quota_warning_100, budget_exceeded, monthly_cap_reached | — | Non solleva | threshold() 0.8 / 1.0 / 1.0 / 1.0; severity() warning / critical / critical / critical |
BillingAlertService::__construct | AlertStateRepositoryInterface $alertState | Lega lo store di deduplicazione | Nuova istanza | Non solleva | — |
BillingAlertService::evaluate | TenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCu | Attiva gli avvisi non ancora attivati in ordine crescente di soglia e li registra | list<BillingAlertType> | InvalidArgumentException in caso di mismatch tra piano e definizione | Chiave di dedup: tenant, tipo, periodo UTC YYYY-MM |
BillingAlertService::clearAlerts | TenantContext $tenant | Azzera lo stato di attivazione del tenant per il periodo UTC corrente | void | I guasti definiti dal repository si propagano | Ri-arma gli avvisi nello stesso periodo |
AlertStateRepositoryInterface | hasAlertFired(), markAlertFired(), clearForPeriod() | Contratto di persistenza durevole per la deduplicazione degli avvisi | Per metodo | Definito dall’implementazione | L’operatore è responsabile della durabilità tra le repliche |
InMemoryAlertStateRepository | — | Stato di attivazione basato su array | Per interfaccia | Non solleva | Solo cicli di vita a richiesta singola e test |
QuotaExceededException | In sola lettura: currentCu, limitCu, resetsAt, tenantId, isSaaS | Rifiuto di quota consapevole della modalità di distribuzione | — | È il throwable | httpStatusCode() 402 SaaS / 403 on-prem; specCode() SPEC-BILLING-003 / SPEC-LIC-001; toErrorEnvelope() produce un corpo di errore strutturato |
DeploymentMode (enum) | — | saas, self_hosted_oss, local_development | — | Non solleva | Substrate. enforcesQuota() è true solo per Saas; l’opt-out è sempre esplicito |
QuotaEnforcementGuard::__construct | DeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterface | Assembla il gate di quota attivo | Nuova istanza | Non solleva | Substrate. final readonly |
QuotaEnforcementGuard::enforce | ?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0 | Gate di quota fail-closed con prenotazione atomica | QuotaDecision (solo esiti consentiti) | Vedere la tassonomia dei rifiuti più sotto | Substrate. Da montare dopo l’autenticazione del tenant, prima dell’handler fatturabile |
PlanResolverInterface::resolve | TenantContext $tenant | Risolve un tenant nel suo piano e nelle politiche per-feature | ResolvedPlan | NoPlanForTenantException | Substrate. Un fallback a piano predefinito per tenant sconosciuti è un difetto |
RegistryPlanResolver | array<non-empty-string, ResolvedPlan> $plansByTenant | Resolver basato su mappa | ResolvedPlan | NoPlanForTenantException per tenant non mappati | Substrate. Fail-closed per costruzione |
ResolvedPlan::policyFor | non-empty-string $featureKey | Ricerca della politica sul piano risolto | ?QuotaPolicy | Non solleva | Substrate. null indica una feature sconosciuta; la guardia la rifiuta |
QuotaPolicy | non-empty-string $featureKey, float $limit, OveragePolicy $overagePolicy | Limite per-feature e politica di violazione | — | Non solleva | Substrate. UNLIMITED = -1.0; un limite 0.0 è zero allowance, non illimitato; isUnlimited(), isBlocking() |
QuotaDecision | Statici bypassed(), unlimited(), consumed() | Value object di esito consentito | QuotaDecision | Non solleva | Substrate. isAllowed() è sempre true; ogni rifiuto solleva invece un’eccezione |
UsageCounter | Snapshot di riga: tenant, feature, limiti del periodo, used, limit, updatedAt | Riga di utilizzo immutabile | — | Non solleva | Substrate. remaining() può essere negativo; wouldExceed() è stretto |
UsageCounterStoreInterface::get | Tenant, feature, limiti del periodo, float $limit | Legge la riga di utilizzo, creandola con used = 0 quando assente | UsageCounter | UsageStoreUnavailableException | Substrate. Non restituisce mai un valore falsy in caso di guasto del backend |
UsageCounterStoreInterface::tryConsume | Tenant, feature, limiti del periodo, float $amount, float $limit | Prenotazione atomica compare-and-set entro il limite | ?UsageCounter (null quando la prenotazione violerebbe il limite) | UsageStoreUnavailableException | Substrate. Deve essere una singola operazione atomica sullo store di supporto |
InMemoryUsageCounterStore | — | Implementazione di riferimento in-process del contratto dello store | Per interfaccia | Per interfaccia | Substrate. Solo processo singolo; documenta l’invariante di atomicità |
QuotaEnforcementException (abstract) | — | Tipo base di ogni rifiuto del substrate | — | È la famiglia di throwable | Substrate. Ogni sottotipo dichiara 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;Tassonomia dei rifiuti di QuotaEnforcementGuard::enforce
| Eccezione | Stato HTTP | Sollevata quando |
|---|---|---|
MissingTenantContextException | 401 | Modalità SaaS senza contesto tenant autenticato |
NoPlanForTenantException | 402 | Il resolver non trova alcun piano assegnato al tenant |
UnknownFeatureException | 402 | Il piano risolto non definisce alcuna politica per la chiave di feature |
UsageStoreUnavailableException | 503 | Lo store di utilizzo non può essere letto o aggiornato atomicamente; sollevata anche per un $amount non positivo |
QuotaExceededException | 402 (SaaS) / 403 (on-prem) | La quota di una politica bloccante è superata, oppure una prenotazione concorrente ha consumato l’ultimo margine |
Contratto di comportamento
Sezione intitolata “Contratto di comportamento”- Il registry predefinito include tre tier (Standard / Advanced / High Control) con quote CU e insiemi di capability crescenti. Una richiesta per un piano non registrato fallisce con un
InvalidArgumentExceptionesplicito. QuotaManager::checkQuota()solleva solo quando valgono entrambe le condizioni: la politica è bloccante e l’utilizzo corrente è strettamente superiore alla quota inclusa. Una politica budget-alert non solleva mai; l’overage è segnalato tramite avvisi.remainingQuota()eusagePercentage()sono letture pure e non bloccano mai. La quota rimanente diventa negativa in overage; la percentuale di utilizzo supera1.0in overage.- Gli avvisi sono valutati in ordine crescente di soglia: warning all’80%, warning al 100% (critical), poi budget-exceeded (critical). Budget-exceeded è subordinato all’overage stretto; un utilizzo esattamente al 100% attiva il warning al 100%, non budget-exceeded.
- Ogni tipo di avviso si attiva al massimo una volta per tenant per periodo di fatturazione. Lo stato di attivazione è registrato tramite
AlertStateRepositoryInterface, così che la deduplicazione sia durevole quanto l’implementazione scelta. - La chiave di deduplicazione incorpora il periodo UTC
YYYY-MM. Un nuovo mese di calendario ri-arma quindi automaticamente ogni tipo di avviso; nessuna chiamata di clear è necessaria per il ri-armamento al rollover.clearAlerts()azzera il periodo corrente, ri-armando gli avvisi a metà periodo, per esempio dopo un upgrade di piano. - Una guardia di mismatch del piano in
evaluate()rifiuta una chiamata in cui il piano fornito e la definizione del piano non concordano, proteggendo dal caso di una definizione appartenente a un tier diverso dal piano del tenant. - Tutta l’aritmetica dei periodi è ancorata a UTC. L’istante di reset della quota superata è il primo giorno del mese di calendario successivo a mezzanotte UTC; una risposta soft-stop dovrebbe pubblicizzarlo come orizzonte di ritentativo.
QuotaEnforcementGuardè fail-closed in modalità SaaS. Tenant mancante, piano mancante, feature sconosciuta, indisponibilità dello store e violazione di quota rifiutano tutti; nulla ricade in un consenso implicito. Le distribuzioni non-SaaS si escludono solo costruendo la guardia con unDeploymentModenon-SaaS.- Le politiche bloccanti prenotano l’utilizzo tramite
UsageCounterStoreInterface::tryConsume, un compare-and-set atomico. Le richieste concorrenti non possono spingere collettivamente l’utilizzo oltre il limite; chi perde la corsa riceveQuotaExceededExceptionanche se il pre-controllo era passato. - Sotto una politica budget-alert la guardia registra il consumo best-effort e non rifiuta mai; una prenotazione oltre il tetto morbido registra comunque la riga al limite.
QuotaExceededExceptionè consapevole della modalità di distribuzione: i rifiuti SaaS mappano su HTTP 402 con spec codeSPEC-BILLING-003e sono contrassegnati come ritentabili; i rifiuti on-prem mappano su HTTP 403 conSPEC-LIC-001.- La libreria non emette essa stessa risposte HTTP. I codici di stato dichiarati sono il contratto per il layer di edge, che mappa un rifiuto sollevato su una risposta e non deve invocare l’handler fatturabile.
Casi limite e modalità di guasto
Sezione intitolata “Casi limite e modalità di guasto”- Quota inclusa non positiva.
usagePercentage(),evaluate()eOverageCalculator::calculate()producono tutti un rapporto di utilizzo di0.0anziché una divisione per zero. Gli avvisi di soglia non si attivano quindi mai dal solo rapporto. - Budget-alert più un overage elevato. Sia il manager sia la guardia restituiscono esiti consentiti. Non interpretare l’assenza di un’eccezione come prova di essere entro la quota; consultare
OverageResulto lo stream degli avvisi. - Esattamente al limite.
checkQuota()concurrentCu == includedCuQuotapassa.BudgetExceededrichiede un overage stretto. AncheUsageCounter::wouldExceed()è stretto. MonthlyCapReached. L’enum dichiara questo quarto tipo di avviso, maBillingAlertService::evaluate()non lo emette mai; la sua lista di candidati copre solo i tre avvisi di soglia. È riservato agli emitter di tracciamento del cap esterni a questo modulo.- Definizioni di tier duplicate.
PlanRegistryindicizza per valore di tier; l’ultima definizione per un tier sostituisce silenziosamente quelle precedenti. Costruire i registry a partire da una lista deduplicata. - Zero allowance contro illimitato. Un limite
QuotaPolicydi0.0significa che ogni consumo nel periodo è overage. Solo il sentinel negativoUNLIMITEDdisabilita il metering;isUnlimited()non blocca mai. - Importo di prenotazione non positivo.
enforce()rifiuta un$amountnon positivo in modo fail-closed conUsageStoreUnavailableException(503). Questo è un difetto del chiamante, non un’indisponibilità dello store. - Indisponibilità dello store. Qualsiasi guasto di lettura o prenotazione emerge come
UsageStoreUnavailableExceptione rifiuta. La guardia non consente mai lavoro non misurato mentre il meter è inattivo. - Implementazioni in memoria.
InMemoryAlertStateRepositoryeInMemoryUsageCounterStoresono corretti solo all’interno di un singolo processo PHP. Le distribuzioni multi-replica devono fornire implementazioni supportate da un datastore con atomicità reale; uno store read-then-write è un difetto che permette il superamento della quota sotto carico. - Modalità FIPS. Il billing non esegue alcuna operazione crittografica propria e non ha alcun comportamento specifico FIPS. L’identità del tenant che consuma deve provenire da un contesto autenticato la cui postura FIPS è documentata con la superficie SaaS.
Conformità
Sezione intitolata “Conformità”| Affermazione | Standard | Clausola |
|---|---|---|
| Il codice di stato 402 è riservato per usi futuri; non porta alcuna semantica normativa di richiesta propria. | RFC 9110 | §15.5.3 |
| 429 indica che il client ha inviato troppe richieste in un dato intervallo di tempo (“rate limiting”). | RFC 6585 | §4 |
| Retry-After indica quanto tempo lo user agent dovrebbe attendere prima di effettuare una richiesta successiva. | RFC 9110 | §10.2.3 |
Tutte le clausole sono parafrasate; NextPDF non riproduce testo normativo. NextPDF non avanza alcuna affermazione di conformità o certificazione del protocollo HTTP per questa superficie. La mappatura 402 / 429 / 200 dichiarata da OveragePolicy::httpStatusCode() e i codici di rifiuto 401 / 402 / 503 della guardia sono una convenzione di prodotto allineata con le clausole di cui sopra: la RFC 9110 riserva il 402, quindi il suo uso qui per il rifiuto di pagamento è la comune convenzione di settore, non una semantica definita dall’IETF. L’orizzonte di ritentativo soft-stop (resetsAt) è il valore che un layer di edge dovrebbe esporre come indicazione Retry-After. L’emissione delle effettive risposte HTTP, delle intestazioni e del comportamento di caching è responsabilità dell’applicazione ospitante.
Note di sviluppo
Sezione intitolata “Note di sviluppo”- Comporre il modello a partire da
PlanRegistry::defaultRegistry(), unaOveragePolicye unQuotaManager; aggiungereBillingAlertServicecon un’implementazione durevole diAlertStateRepositoryInterfaceper gli avvisi. - Montare
QuotaEnforcementGuardnella pipeline di richiesta dopo l’autenticazione del tenant e prima dell’handler fatturabile. IntercettareQuotaEnforcementExceptione laQuotaExceededExceptiondel billing all’edge e mapparehttpStatusCode()sulla risposta. - Le definizioni dei piani in questo modulo sono l’unica fonte di verità per il billing; non mantenere una definizione di billing parallela altrove nella distribuzione.
- Le implementazioni in memoria rendono l’intera superficie testabile a livello unitario senza I/O. Test di confine consigliati: utilizzo esattamente alla quota, un’unità sopra, soglie di rapporto a 0.8 e 1.0, la guardia di mismatch del piano, la corsa CAS (due prenotazioni contro l’ultima unità di margine) e il rifiuto per indisponibilità dello store.
- Le classi del modello core portano
@since 2.2.0; il substrate porta@since 2.3.0. La linea di pacchetto corrente è 3.1.0. - L’operatore è responsabile delle implementazioni del repository dello stato degli avvisi e dello usage-store, della loro durabilità tra le repliche e di qualsiasi ri-armamento degli avvisi a metà periodo tramite
clearAlerts().
Confine di pubblicazione
Sezione intitolata “Confine di pubblicazione”Questa pagina documenta esclusivamente il comportamento osservabile dall’esterno e la superficie dell’API pubblica supportata. I percorsi di namespace interni, le classi helper, le tabelle dei meccanismi, i nomi di file dei runbook e i prefissi dei ticket sono fuori ambito.