Aller au contenu
getnextpdf.com

Enterprise édition

Facturation — Référence détaillée

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.

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.

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.

SymboleParamètresComportement par défautRetourneLève ou échoue avecNotes
SaaSPlan (enum)Paliers de plans à base de chaînes : standard, advanced, high_controlNe lève paslabel() retourne le nom d’affichage
PlanDefinition::__constructSaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncludedObjet-valeur de plan immuable ; stocke les entrées telles quellesNouvelle instanceNe lève pasfinal readonly ; propriétés publiques promues
PlanDefinition::includesCapabilityCapabilityCode $capabilityVérification stricte d’appartenance par identitéboolNe lève pas
PlanRegistry::__constructlist<PlanDefinition> $definitionsIndexe les définitions par palier ; la dernière définition par palier l’emporteNouveau registreNe lève pasPour les tests et les jeux de plans en marque blanche
PlanRegistry::getSaaSPlan $planRecherche canonique de planPlanDefinitionInvalidArgumentException lorsque le plan n’est pas enregistré
PlanRegistry::hasSaaSPlan $planSonde d’enregistrementboolNe 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 PacksPlanRegistryNe lève pasÀ utiliser sauf si les conditions contractuelles exigent des définitions personnalisées
OveragePolicy (enum)hard_stop, soft_stop, budget_alertNe lève pashttpStatusCode() mappe 402 / 429 / 200 ; isBlocking() est vrai uniquement pour hard stop et soft stop
QuotaManager::__constructPlanRegistry $planRegistry, OveragePolicy $overagePolicyLie le registre à une politiqueNouvelle instanceNe lève pas
QuotaManager::checkQuotaTenantContext $tenant, SaaSPlan $plan, float $currentCuRetourne silencieusement au niveau ou en dessous du quota, ou sous une politique non bloquantevoidQuotaExceededException 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::remainingQuotaSaaSPlan $plan, float $currentCuLecture pure ; ne bloque jamaisfloatInvalidArgumentException du registreNégatif en cas de dépassement
QuotaManager::usagePercentageSaaSPlan $plan, float $currentCuLecture pure ; ne bloque jamaisfloatInvalidArgumentException du registre0.0 lorsque le quota inclus est non positif ; supérieur à 1.0 en cas de dépassement
OverageCalculator::calculatePlanDefinition $plan, float $currentCuCalcule un instantané de dépassement immuableOverageResultNe lève pasfinal readonly, sans état
OverageResultincludedCu, usedCu, overageCu, usageRatio, isOverageRésultat de calcul immuableNe lève pasoverageCu = max(0, used - included) ; isOverage exige un dépassement strict
BillingAlertType (enum)quota_warning_80, quota_warning_100, budget_exceeded, monthly_cap_reachedNe lève pasthreshold() 0.8 / 1.0 / 1.0 / 1.0 ; severity() warning / critical / critical / critical
BillingAlertService::__constructAlertStateRepositoryInterface $alertStateLie le magasin de déduplicationNouvelle instanceNe lève pas
BillingAlertService::evaluateTenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCuDéclenche les alertes non encore déclenchées par ordre de seuil croissant et les enregistrelist<BillingAlertType>InvalidArgumentException en cas de non-correspondance plan/définitionClé de déduplication : locataire, type, période UTC YYYY-MM
BillingAlertService::clearAlertsTenantContext $tenantEfface l’état déclenché du locataire pour la période UTC courantevoidLes échecs définis par le dépôt se propagentRéarme les alertes dans la même période
AlertStateRepositoryInterfacehasAlertFired(), markAlertFired(), clearForPeriod()Contrat de persistance durable de déduplication d’alertesSelon la méthodeDéfini par l’implémentationL’opérateur est responsable de la durabilité entre réplicas
InMemoryAlertStateRepositoryÉtat déclenché adossé à un tableauSelon l’interfaceNe lève pasCycles de vie à requête unique et tests uniquement
QuotaExceededExceptioncurrentCu, limitCu, resetsAt, tenantId, isSaaS en lecture seuleRefus de quota tenant compte du mode de déploiementEst le throwablehttpStatusCode() 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_developmentNe lève pasSubstrate. enforcesQuota() est vrai uniquement pour Saas ; le retrait est toujours explicite
QuotaEnforcementGuard::__constructDeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterfaceAssemble le portail de quota en directNouvelle instanceNe lève pasSubstrate. final readonly
QuotaEnforcementGuard::enforce?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0Portail de quota fail-closed avec réservation atomiqueQuotaDecision (issues autorisées uniquement)Voir la taxonomie des refus ci-dessousSubstrate. À monter après l’authentification du locataire, avant le gestionnaire facturable
PlanResolverInterface::resolveTenantContext $tenantRésout un locataire vers son plan et ses politiques par fonctionnalitéResolvedPlanNoPlanForTenantExceptionSubstrate. Un repli sur un plan par défaut pour les locataires inconnus est un défaut
RegistryPlanResolverarray<non-empty-string, ResolvedPlan> $plansByTenantRésolveur adossé à une mapResolvedPlanNoPlanForTenantException pour les locataires non mappésSubstrate. Fail-closed par construction
ResolvedPlan::policyFornon-empty-string $featureKeyRecherche de politique sur le plan résolu?QuotaPolicyNe lève pasSubstrate. null signifie fonctionnalité inconnue ; le portail la refuse
QuotaPolicynon-empty-string $featureKey, float $limit, OveragePolicy $overagePolicyLimite par fonctionnalité et politique de dépassementNe lève pasSubstrate. UNLIMITED = -1.0 ; une limite de 0.0 est une allocation nulle, non illimitée ; isUnlimited(), isBlocking()
QuotaDecisionStatics bypassed(), unlimited(), consumed()Objet-valeur d’issue autoriséeQuotaDecisionNe lève pasSubstrate. isAllowed() est toujours vrai ; chaque refus lève à la place
UsageCounterInstantané de ligne : locataire, fonctionnalité, bornes de période, used, limit, updatedAtLigne d’usage immuableNe lève pasSubstrate. remaining() peut être négatif ; wouldExceed() est strict
UsageCounterStoreInterface::getLocataire, fonctionnalité, bornes de période, float $limitLit la ligne d’usage, la créant avec used = 0 si absenteUsageCounterUsageStoreUnavailableExceptionSubstrate. Ne retourne jamais une valeur falsy en cas d’échec du backend
UsageCounterStoreInterface::tryConsumeLocataire, fonctionnalité, bornes de période, float $amount, float $limitRéservation atomique compare-and-set dans la limite?UsageCounter (null lorsque la réservation dépasserait la limite)UsageStoreUnavailableExceptionSubstrate. Doit être une opération atomique unique contre le magasin sous-jacent
InMemoryUsageCounterStoreImplémentation de référence en processus du contrat de magasinSelon l’interfaceSelon l’interfaceSubstrate. Un seul processus ; documente l’invariant d’atomicité
QuotaEnforcementException (abstract)Type de base de chaque refus du substratEst la famille de throwablesSubstrate. Chaque sous-type déclare httpStatusCode()
public function checkQuota(TenantContext $tenant, SaaSPlan $plan, float $currentCu): void
public function evaluate(
TenantContext $tenant,
SaaSPlan $plan,
PlanDefinition $planDef,
float $currentCu,
): array
public function enforce(?TenantContext $tenant, string $featureKey, float $amount = 1.0): QuotaDecision
public function tryConsume(
string $tenantId,
string $featureKey,
DateTimeImmutable $periodStart,
DateTimeImmutable $periodEnd,
float $amount,
float $limit,
): ?UsageCounter;

Taxonomie des refus de QuotaEnforcementGuard::enforce

ExceptionStatut HTTPLevée quand
MissingTenantContextException401Mode SaaS sans contexte de locataire authentifié
NoPlanForTenantException402Le résolveur ne trouve aucun plan assigné au locataire
UnknownFeatureException402Le plan résolu ne définit aucune politique pour la clé de fonctionnalité
UsageStoreUnavailableException503Le magasin d’usage ne peut être lu ou mis à jour atomiquement ; également levée pour un $amount non positif
QuotaExceededException402 (SaaS) / 403 (on-prem)Le quota d’une politique bloquante est dépassé, ou une réservation concurrente a consommé la dernière marge
  • 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 InvalidArgumentException explicite.
  • 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() et usagePercentage() sont des lectures pures et ne bloquent jamais. Le quota restant devient négatif en cas de dépassement ; le pourcentage d’usage dépasse 1.0 en 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.
  • QuotaEnforcementGuard est 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 un DeploymentMode non-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çoit QuotaExceededException mê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.
  • QuotaExceededException tient compte du mode de déploiement : les refus SaaS mappent vers HTTP 402 avec le code de spécification SPEC-BILLING-003 et sont marqués réessayables ; les refus on-prem mappent vers HTTP 403 avec SPEC-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.
  • Quota inclus non positif. usagePercentage(), evaluate() et OverageCalculator::calculate() produisent tous un ratio d’usage de 0.0 au 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 OverageResult ou le flux d’alertes.
  • Exactement à la limite. checkQuota() à currentCu == includedCuQuota passe. BudgetExceeded exige un dépassement strict. UsageCounter::wouldExceed() est strict également.
  • MonthlyCapReached. L’enum déclare ce quatrième type d’alerte, mais BillingAlertService::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. PlanRegistry indexe 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 QuotaPolicy de 0.0 signifie que chaque consommation de la période est un dépassement. Seule la sentinelle négative UNLIMITED désactive le comptage ; isUnlimited() ne bloque jamais.
  • Montant de réservation non positif. enforce() refuse un $amount non positif en fail-closed avec UsageStoreUnavailableException (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 UsageStoreUnavailableException et refuse. Le portail n’autorise jamais de travail non comptabilisé pendant que le compteur est hors service.
  • Implémentations en mémoire. InMemoryAlertStateRepository et InMemoryUsageCounterStore ne 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.
AffirmationStandardClause
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.

  • Compose le modèle à partir de PlanRegistry::defaultRegistry(), d’une OveragePolicy et d’un QuotaManager ; ajoute BillingAlertService avec une implémentation durable de AlertStateRepositoryInterface pour les alertes.
  • Monte QuotaEnforcementGuard dans le pipeline de requête après l’authentification du locataire et avant le gestionnaire facturable. Capture QuotaEnforcementException et la QuotaExceededException de facturation à la bordure et mappe httpStatusCode() 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().

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.