Enterprise édition
Facturation — Référence détaillée
En un coup d’œil
Section intitulée « En un coup d’œil »Cette page est la référence détaillée de la surface de facturation de NextPDF Enterprise. Cette surface comporte deux couches. Le modèle de facturation dans NextPDF\Enterprise\Billing définit les paliers de plans, les quotas, les politiques de dépassement et les alertes d’usage dédupliquées. Le substrat d’application dans NextPDF\Enterprise\Billing\Substrate place ce modèle sur le chemin de requête en direct, fail-closed et sûr en concurrence. Les points d’entrée sont PlanRegistry, QuotaManager, OverageCalculator, BillingAlertService et QuotaEnforcementGuard. Pour le guide au niveau du flux de travail, consulte la page de capacité Facturation.
Disponibilité et licence
Section intitulée « Disponibilité et licence »Cette capacité est livrée dans NextPDF Enterprise (nextpdf/enterprise) et s’active avec une enveloppe de licence de palier Enterprise. Un déploiement sans ce droit ne charge pas les classes de la capacité. Compare les éditions et obtiens une licence.
La facturation est une capacité Enterprise de base sans indicateur distinct par fonctionnalité ; elle est disponible dès que le paquet Enterprise est installé à côté du paquet Core. NextPDF Core (Apache-2.0) et NextPDF Pro n’ont aucun modèle de plan, de quota ou de dépassement ; cette surface n’a pas d’équivalent de palier inférieur. Les inclusions de plans, les quotas et les conditions commerciales sont régis par le contrat de licence, non par l’application au moment de l’exécution ; cette référence n’est pas un avis juridique ou contractuel.
Surface d’API publique
Section intitulée « Surface d’API publique »Tous les symboles se trouvent sous NextPDF\Enterprise\Billing. Les lignes marquées substrate se trouvent sous NextPDF\Enterprise\Billing\Substrate. TenantContext est le type de locataire authentifié de NextPDF\Enterprise\SaaS.
| Symbole | Paramètres | Comportement par défaut | Retourne | Lève ou échoue avec | Notes |
|---|---|---|---|---|---|
SaaSPlan (enum) | — | Paliers de plans à base de chaînes : standard, advanced, high_control | — | Ne lève pas | label() retourne le nom d’affichage |
PlanDefinition::__construct | SaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncluded | Objet-valeur de plan immuable ; stocke les entrées telles quelles | Nouvelle instance | Ne lève pas | final readonly ; propriétés publiques promues |
PlanDefinition::includesCapability | CapabilityCode $capability | Vérification stricte d’appartenance par identité | bool | Ne lève pas | — |
PlanRegistry::__construct | list<PlanDefinition> $definitions | Indexe les définitions par palier ; la dernière définition par palier l’emporte | Nouveau registre | Ne lève pas | Pour les tests et les jeux de plans en marque blanche |
PlanRegistry::get | SaaSPlan $plan | Recherche canonique de plan | PlanDefinition | InvalidArgumentException lorsque le plan n’est pas enregistré | — |
PlanRegistry::has | SaaSPlan $plan | Sonde d’enregistrement | bool | Ne lève pas | — |
PlanRegistry::defaultRegistry (static) | — | Valeurs par défaut de production : Standard 1,000 CU ; Advanced 5,000 CU plus Intelligence Pack ; High Control 20,000 CU plus Intelligence et Privacy Packs | PlanRegistry | Ne lève pas | À utiliser sauf si les conditions contractuelles exigent des définitions personnalisées |
OveragePolicy (enum) | — | hard_stop, soft_stop, budget_alert | — | Ne lève pas | httpStatusCode() mappe 402 / 429 / 200 ; isBlocking() est vrai uniquement pour hard stop et soft stop |
QuotaManager::__construct | PlanRegistry $planRegistry, OveragePolicy $overagePolicy | Lie le registre à une politique | Nouvelle instance | Ne lève pas | — |
QuotaManager::checkQuota | TenantContext $tenant, SaaSPlan $plan, float $currentCu | Retourne silencieusement au niveau ou en dessous du quota, ou sous une politique non bloquante | void | QuotaExceededException en cas de dépassement strict sous une politique bloquante ; InvalidArgumentException du registre sur un plan non enregistré | resetsAt = premier jour du mois suivant, minuit UTC |
QuotaManager::remainingQuota | SaaSPlan $plan, float $currentCu | Lecture pure ; ne bloque jamais | float | InvalidArgumentException du registre | Négatif en cas de dépassement |
QuotaManager::usagePercentage | SaaSPlan $plan, float $currentCu | Lecture pure ; ne bloque jamais | float | InvalidArgumentException du registre | 0.0 lorsque le quota inclus est non positif ; supérieur à 1.0 en cas de dépassement |
OverageCalculator::calculate | PlanDefinition $plan, float $currentCu | Calcule un instantané de dépassement immuable | OverageResult | Ne lève pas | final readonly, sans état |
OverageResult | includedCu, usedCu, overageCu, usageRatio, isOverage | Résultat de calcul immuable | — | Ne lève pas | overageCu = max(0, used - included) ; isOverage exige un dépassement strict |
BillingAlertType (enum) | — | quota_warning_80, quota_warning_100, budget_exceeded, monthly_cap_reached | — | Ne lève pas | threshold() 0.8 / 1.0 / 1.0 / 1.0 ; severity() warning / critical / critical / critical |
BillingAlertService::__construct | AlertStateRepositoryInterface $alertState | Lie le magasin de déduplication | Nouvelle instance | Ne lève pas | — |
BillingAlertService::evaluate | TenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCu | Déclenche les alertes non encore déclenchées par ordre de seuil croissant et les enregistre | list<BillingAlertType> | InvalidArgumentException en cas de non-correspondance plan/définition | Clé de déduplication : locataire, type, période UTC YYYY-MM |
BillingAlertService::clearAlerts | TenantContext $tenant | Efface l’état déclenché du locataire pour la période UTC courante | void | Les échecs définis par le dépôt se propagent | Réarme les alertes dans la même période |
AlertStateRepositoryInterface | hasAlertFired(), markAlertFired(), clearForPeriod() | Contrat de persistance durable de déduplication d’alertes | Selon la méthode | Défini par l’implémentation | L’opérateur est responsable de la durabilité entre réplicas |
InMemoryAlertStateRepository | — | État déclenché adossé à un tableau | Selon l’interface | Ne lève pas | Cycles de vie à requête unique et tests uniquement |
QuotaExceededException | currentCu, limitCu, resetsAt, tenantId, isSaaS en lecture seule | Refus de quota tenant compte du mode de déploiement | — | Est le throwable | httpStatusCode() 402 SaaS / 403 on-prem ; specCode() SPEC-BILLING-003 / SPEC-LIC-001 ; toErrorEnvelope() produit un corps d’erreur structuré |
DeploymentMode (enum) | — | saas, self_hosted_oss, local_development | — | Ne lève pas | Substrate. enforcesQuota() est vrai uniquement pour Saas ; le retrait est toujours explicite |
QuotaEnforcementGuard::__construct | DeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterface | Assemble le portail de quota en direct | Nouvelle instance | Ne lève pas | Substrate. final readonly |
QuotaEnforcementGuard::enforce | ?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0 | Portail de quota fail-closed avec réservation atomique | QuotaDecision (issues autorisées uniquement) | Voir la taxonomie des refus ci-dessous | Substrate. À monter après l’authentification du locataire, avant le gestionnaire facturable |
PlanResolverInterface::resolve | TenantContext $tenant | Résout un locataire vers son plan et ses politiques par fonctionnalité | ResolvedPlan | NoPlanForTenantException | Substrate. Un repli sur un plan par défaut pour les locataires inconnus est un défaut |
RegistryPlanResolver | array<non-empty-string, ResolvedPlan> $plansByTenant | Résolveur adossé à une map | ResolvedPlan | NoPlanForTenantException pour les locataires non mappés | Substrate. Fail-closed par construction |
ResolvedPlan::policyFor | non-empty-string $featureKey | Recherche de politique sur le plan résolu | ?QuotaPolicy | Ne lève pas | Substrate. null signifie fonctionnalité inconnue ; le portail la refuse |
QuotaPolicy | non-empty-string $featureKey, float $limit, OveragePolicy $overagePolicy | Limite par fonctionnalité et politique de dépassement | — | Ne lève pas | Substrate. UNLIMITED = -1.0 ; une limite de 0.0 est une allocation nulle, non illimitée ; isUnlimited(), isBlocking() |
QuotaDecision | Statics bypassed(), unlimited(), consumed() | Objet-valeur d’issue autorisée | QuotaDecision | Ne lève pas | Substrate. isAllowed() est toujours vrai ; chaque refus lève à la place |
UsageCounter | Instantané de ligne : locataire, fonctionnalité, bornes de période, used, limit, updatedAt | Ligne d’usage immuable | — | Ne lève pas | Substrate. remaining() peut être négatif ; wouldExceed() est strict |
UsageCounterStoreInterface::get | Locataire, fonctionnalité, bornes de période, float $limit | Lit la ligne d’usage, la créant avec used = 0 si absente | UsageCounter | UsageStoreUnavailableException | Substrate. Ne retourne jamais une valeur falsy en cas d’échec du backend |
UsageCounterStoreInterface::tryConsume | Locataire, fonctionnalité, bornes de période, float $amount, float $limit | Réservation atomique compare-and-set dans la limite | ?UsageCounter (null lorsque la réservation dépasserait la limite) | UsageStoreUnavailableException | Substrate. Doit être une opération atomique unique contre le magasin sous-jacent |
InMemoryUsageCounterStore | — | Implémentation de référence en processus du contrat de magasin | Selon l’interface | Selon l’interface | Substrate. Un seul processus ; documente l’invariant d’atomicité |
QuotaEnforcementException (abstract) | — | Type de base de chaque refus du substrat | — | Est la famille de throwables | Substrate. Chaque sous-type déclare 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;Taxonomie des refus de QuotaEnforcementGuard::enforce
| Exception | Statut HTTP | Levée quand |
|---|---|---|
MissingTenantContextException | 401 | Mode SaaS sans contexte de locataire authentifié |
NoPlanForTenantException | 402 | Le résolveur ne trouve aucun plan assigné au locataire |
UnknownFeatureException | 402 | Le plan résolu ne définit aucune politique pour la clé de fonctionnalité |
UsageStoreUnavailableException | 503 | Le magasin d’usage ne peut être lu ou mis à jour atomiquement ; également levée pour un $amount non positif |
QuotaExceededException | 402 (SaaS) / 403 (on-prem) | Le quota d’une politique bloquante est dépassé, ou une réservation concurrente a consommé la dernière marge |
Contrat de comportement
Section intitulée « Contrat de comportement »- Le registre par défaut livre trois paliers (Standard / Advanced / High Control) avec des quotas de CU et des jeux de capacités croissants. Une requête sur un plan non enregistré échoue avec une
InvalidArgumentExceptionexplicite. QuotaManager::checkQuota()ne lève que lorsque les deux conditions sont réunies : la politique est bloquante, et l’usage courant est strictement supérieur au quota inclus. Une politique budget-alert ne lève jamais ; le dépassement est signalé via les alertes.remainingQuota()etusagePercentage()sont des lectures pures et ne bloquent jamais. Le quota restant devient négatif en cas de dépassement ; le pourcentage d’usage dépasse1.0en cas de dépassement.- Les alertes s’évaluent par ordre de seuil croissant : avertissement à 80%, avertissement à 100% (critical), puis budget-exceeded (critical). Budget-exceeded est conditionné à un dépassement strict ; un usage à exactement 100% déclenche l’avertissement à 100%, pas budget-exceeded.
- Chaque type d’alerte se déclenche au plus une fois par locataire et par période de facturation. L’état déclenché est enregistré via
AlertStateRepositoryInterface, de sorte que la déduplication est aussi durable que l’implémentation choisie. - La clé de déduplication intègre la période UTC
YYYY-MM. Un nouveau mois calendaire réarme donc automatiquement chaque type d’alerte ; aucun appel à clear n’est requis pour le réarmement au passage de période.clearAlerts()efface la période courante, ce qui réarme les alertes en cours de période, par exemple après une montée de plan. - Un garde-fou de non-correspondance de plan dans
evaluate()rejette un appel où le plan fourni et la définition de plan divergent, protégeant contre une définition d’un palier différent de celui du plan du locataire. - Toute l’arithmétique de période est ancrée sur UTC. L’instant de réinitialisation en cas de dépassement de quota est le premier jour du mois calendaire suivant à minuit UTC ; une réponse soft-stop devrait l’annoncer comme horizon de nouvelle tentative.
QuotaEnforcementGuardest fail-closed en mode SaaS. Locataire manquant, plan manquant, fonctionnalité inconnue, panne du magasin et dépassement de quota refusent tous ; rien ne bascule vers une autorisation implicite. Les déploiements non-SaaS ne se désengagent qu’en construisant le portail avec unDeploymentModenon-SaaS.- Les politiques bloquantes réservent l’usage via
UsageCounterStoreInterface::tryConsume, un compare-and-set atomique. Des requêtes concurrentes ne peuvent collectivement pousser l’usage au-delà de la limite ; le perdant de la course reçoitQuotaExceededExceptionmême si la pré-vérification a réussi. - Sous une politique budget-alert, le portail enregistre la consommation au mieux et ne refuse jamais ; une réservation au-delà du plafond souple enregistre tout de même la ligne à la limite.
QuotaExceededExceptiontient compte du mode de déploiement : les refus SaaS mappent vers HTTP 402 avec le code de spécificationSPEC-BILLING-003et sont marqués réessayables ; les refus on-prem mappent vers HTTP 403 avecSPEC-LIC-001.- La bibliothèque n’émet pas elle-même de réponses HTTP. Les codes de statut déclarés sont le contrat pour la couche de bordure, qui mappe un refus levé vers une réponse et ne doit pas invoquer le gestionnaire facturable.
Cas limites et modes de défaillance
Section intitulée « Cas limites et modes de défaillance »- Quota inclus non positif.
usagePercentage(),evaluate()etOverageCalculator::calculate()produisent tous un ratio d’usage de0.0au lieu de diviser par zéro. Les alertes de seuil ne se déclenchent alors jamais à partir du seul ratio. - Budget-alert avec dépassement important. Le gestionnaire et le portail retournent tous deux des issues autorisées. Ne considère pas l’absence d’exception comme une preuve d’être dans le quota ; consulte
OverageResultou le flux d’alertes. - Exactement à la limite.
checkQuota()àcurrentCu == includedCuQuotapasse.BudgetExceededexige un dépassement strict.UsageCounter::wouldExceed()est strict également. MonthlyCapReached. L’enum déclare ce quatrième type d’alerte, maisBillingAlertService::evaluate()ne l’émet jamais ; sa liste de candidats ne couvre que les trois alertes de seuil. Il est réservé aux émetteurs de suivi de plafond en dehors de ce module.- Définitions de palier en double.
PlanRegistryindexe par valeur de palier ; la dernière définition d’un palier remplace silencieusement les précédentes. Construis les registres à partir d’une liste dédupliquée. - Allocation nulle contre illimitée. Une limite de
QuotaPolicyde0.0signifie que chaque consommation de la période est un dépassement. Seule la sentinelle négativeUNLIMITEDdésactive le comptage ;isUnlimited()ne bloque jamais. - Montant de réservation non positif.
enforce()refuse un$amountnon positif en fail-closed avecUsageStoreUnavailableException(503). C’est un défaut de l’appelant, pas une panne du magasin. - Panne du magasin. Tout échec de lecture ou de réservation se manifeste par
UsageStoreUnavailableExceptionet refuse. Le portail n’autorise jamais de travail non comptabilisé pendant que le compteur est hors service. - Implémentations en mémoire.
InMemoryAlertStateRepositoryetInMemoryUsageCounterStorene sont corrects qu’au sein d’un seul processus PHP. Les déploiements multi-réplicas doivent fournir des implémentations adossées à un magasin de données doté d’une atomicité réelle ; un magasin en lecture-puis-écriture est un défaut qui permet le dépassement de quota sous charge. - Mode FIPS. La facturation n’effectue aucune opération cryptographique propre et n’a aucun comportement spécifique à FIPS. L’identité de locataire qu’elle consomme doit provenir d’un contexte authentifié dont la posture FIPS est documentée avec la surface SaaS.
Conformité
Section intitulée « Conformité »| Affirmation | Standard | Clause |
|---|---|---|
| Le code de statut 402 est réservé pour un usage futur ; il ne porte aucune sémantique de requête normative propre. | RFC 9110 | §15.5.3 |
| 429 indique que le client a envoyé trop de requêtes dans un laps de temps donné (« limitation de débit »). | RFC 6585 | §4 |
| Retry-After indique combien de temps l’agent utilisateur devrait attendre avant d’effectuer une requête de suivi. | RFC 9110 | §10.2.3 |
Toutes les clauses sont paraphrasées ; NextPDF ne reproduit pas le texte normatif. NextPDF ne formule aucune revendication de conformité ou de certification au protocole HTTP pour cette surface. Le mappage 402 / 429 / 200 déclaré par OveragePolicy::httpStatusCode() et les codes de refus 401 / 402 / 503 du portail sont une convention produit alignée sur les clauses ci-dessus : la RFC 9110 réserve 402, donc son usage ici pour un refus de paiement est la convention courante de l’industrie, non une sémantique définie par l’IETF. L’horizon de nouvelle tentative du soft-stop (resetsAt) est la valeur qu’une couche de bordure devrait exposer comme indication Retry-After. L’émission des réponses HTTP réelles, des en-têtes et du comportement de cache relève de l’application hôte.
Notes de développement
Section intitulée « Notes de développement »- Compose le modèle à partir de
PlanRegistry::defaultRegistry(), d’uneOveragePolicyet d’unQuotaManager; ajouteBillingAlertServiceavec une implémentation durable deAlertStateRepositoryInterfacepour les alertes. - Monte
QuotaEnforcementGuarddans le pipeline de requête après l’authentification du locataire et avant le gestionnaire facturable. CaptureQuotaEnforcementExceptionet laQuotaExceededExceptionde facturation à la bordure et mappehttpStatusCode()vers la réponse. - Les définitions de plan de ce module sont la source unique de vérité pour la facturation ; ne maintiens pas de définition de facturation parallèle ailleurs dans ton déploiement.
- Les implémentations en mémoire rendent toute la surface testable unitairement sans E/S. Tests aux limites recommandés : usage exactement au quota, une unité au-dessus, seuils de ratio à 0.8 et 1.0, le garde-fou de non-correspondance de plan, la course CAS (deux réservations contre la dernière unité de marge) et le refus pour panne du magasin.
- Les classes du modèle central portent
@since 2.2.0; le substrat porte@since 2.3.0. La ligne de paquet actuelle est 3.1.0. - L’opérateur est responsable des implémentations du dépôt d’état d’alerte et du magasin d’usage, de leur durabilité entre réplicas, et de tout réarmement d’alerte en cours de période via
clearAlerts().
Périmètre de publication
Section intitulée « Périmètre de publication »Cette page documente uniquement le comportement observable de l’extérieur et la surface d’API publique prise en charge. Les chemins de namespace internes, les classes utilitaires, les tables de mécanismes, les noms de fichiers de runbook et les préfixes de ticket sont hors périmètre.