Enterprise edición
Facturación — Referencia detallada
Resumen
Sección titulada «Resumen»Esta página es la referencia detallada de la superficie de facturación de NextPDF Enterprise. La superficie tiene dos capas. El modelo de facturación en NextPDF\Enterprise\Billing define niveles de plan, cuotas, políticas de excedente y alertas de uso deduplicadas. El sustrato de aplicación en NextPDF\Enterprise\Billing\Substrate sitúa ese modelo en la ruta de solicitud en vivo, fail-closed y seguro ante concurrencia. Los puntos de entrada son PlanRegistry, QuotaManager, OverageCalculator, BillingAlertService y QuotaEnforcementGuard. Para la guía a nivel de flujo de trabajo, consulte la página de la funcionalidad de Facturación.
Disponibilidad y licencias
Sección titulada «Disponibilidad y licencias»Esta funcionalidad se incluye en NextPDF Enterprise (nextpdf/enterprise) y se activa con un sobre de licencia de nivel Enterprise. Un despliegue sin ese derecho no carga las clases de la funcionalidad. Comparar ediciones y obtener una licencia.
La facturación es una funcionalidad base de Enterprise sin indicador por característica independiente; está disponible una vez que el paquete Enterprise se instala junto al paquete Core. NextPDF Core (Apache-2.0) y NextPDF Pro no tienen modelo de plan, cuota ni excedente; esta superficie no tiene equivalente en niveles inferiores. Las inclusiones de plan, las cuotas y los términos comerciales se rigen por el acuerdo de licencia, no por la aplicación en tiempo de ejecución; esta referencia no es una opinión legal ni contractual.
Superficie de API pública
Sección titulada «Superficie de API pública»Todos los símbolos residen en NextPDF\Enterprise\Billing. Las filas marcadas como sustrato residen en NextPDF\Enterprise\Billing\Substrate. TenantContext es el tipo de inquilino autenticado de NextPDF\Enterprise\SaaS.
| Símbolo | Parámetros | Comportamiento predeterminado | Devuelve | Lanza o falla con | Notas |
|---|---|---|---|---|---|
SaaSPlan (enum) | — | Niveles de plan respaldados por cadenas: standard, advanced, high_control | — | No lanza | label() devuelve el nombre para mostrar |
PlanDefinition::__construct | SaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncluded | Objeto de valor de plan inmutable; almacena las entradas tal cual | Nueva instancia | No lanza | final readonly; propiedades públicas promovidas |
PlanDefinition::includesCapability | CapabilityCode $capability | Comprobación de pertenencia por identidad estricta | bool | No lanza | — |
PlanRegistry::__construct | list<PlanDefinition> $definitions | Indexa las definiciones por nivel; gana la última definición por nivel | Nuevo registro | No lanza | Para pruebas y conjuntos de planes de marca blanca |
PlanRegistry::get | SaaSPlan $plan | Búsqueda canónica de plan | PlanDefinition | InvalidArgumentException cuando el plan no está registrado | — |
PlanRegistry::has | SaaSPlan $plan | Sondeo de registro | bool | No lanza | — |
PlanRegistry::defaultRegistry (static) | — | Valores predeterminados de producción: Standard 1,000 CU; Advanced 5,000 CU más Intelligence Pack; High Control 20,000 CU más Intelligence y Privacy Packs | PlanRegistry | No lanza | Utilícelo salvo que los términos contractuales exijan definiciones personalizadas |
OveragePolicy (enum) | — | hard_stop, soft_stop, budget_alert | — | No lanza | httpStatusCode() asigna 402 / 429 / 200; isBlocking() es verdadero solo para hard stop y soft stop |
QuotaManager::__construct | PlanRegistry $planRegistry, OveragePolicy $overagePolicy | Vincula el registro a una política | Nueva instancia | No lanza | — |
QuotaManager::checkQuota | TenantContext $tenant, SaaSPlan $plan, float $currentCu | Retorna silenciosamente al alcanzar o estar por debajo de la cuota, o bajo una política no bloqueante | void | QuotaExceededException en excedente estricto bajo una política bloqueante; InvalidArgumentException del registro ante un plan no registrado | resetsAt = primer día del mes siguiente, medianoche UTC |
QuotaManager::remainingQuota | SaaSPlan $plan, float $currentCu | Lectura pura; nunca bloquea | float | InvalidArgumentException del registro | Negativa en excedente |
QuotaManager::usagePercentage | SaaSPlan $plan, float $currentCu | Lectura pura; nunca bloquea | float | InvalidArgumentException del registro | 0.0 cuando la cuota incluida es no positiva; por encima de 1.0 en excedente |
OverageCalculator::calculate | PlanDefinition $plan, float $currentCu | Calcula una instantánea de excedente inmutable | OverageResult | No lanza | final readonly, sin estado |
OverageResult | includedCu, usedCu, overageCu, usageRatio, isOverage | Resultado de cálculo inmutable | — | No lanza | overageCu = max(0, used - included); isOverage requiere excedente estricto |
BillingAlertType (enum) | — | quota_warning_80, quota_warning_100, budget_exceeded, monthly_cap_reached | — | No lanza | threshold() 0.8 / 1.0 / 1.0 / 1.0; severity() warning / critical / critical / critical |
BillingAlertService::__construct | AlertStateRepositoryInterface $alertState | Vincula el almacén de deduplicación | Nueva instancia | No lanza | — |
BillingAlertService::evaluate | TenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCu | Dispara las alertas aún no disparadas en orden ascendente de umbral y las registra | list<BillingAlertType> | InvalidArgumentException ante discrepancia de plan/definición | Clave de deduplicación: inquilino, tipo, periodo UTC YYYY-MM |
BillingAlertService::clearAlerts | TenantContext $tenant | Borra el estado de disparo del inquilino para el periodo UTC actual | void | Los fallos definidos por el repositorio se propagan | Rearma las alertas dentro del mismo periodo |
AlertStateRepositoryInterface | hasAlertFired(), markAlertFired(), clearForPeriod() | Contrato de persistencia duradera para la deduplicación de alertas | Según el método | Definido por la implementación | El operador es responsable de la durabilidad entre réplicas |
InMemoryAlertStateRepository | — | Estado de disparo respaldado por array | Según la interfaz | No lanza | Solo para ciclos de vida de una sola solicitud y pruebas |
QuotaExceededException | currentCu, limitCu, resetsAt, tenantId, isSaaS de solo lectura | Denegación de cuota consciente del modo de despliegue | — | Es el lanzable | httpStatusCode() 402 SaaS / 403 on-prem; specCode() SPEC-BILLING-003 / SPEC-LIC-001; toErrorEnvelope() produce un cuerpo de error estructurado |
DeploymentMode (enum) | — | saas, self_hosted_oss, local_development | — | No lanza | Sustrato. enforcesQuota() es verdadero solo para Saas; la exclusión es siempre explícita |
QuotaEnforcementGuard::__construct | DeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterface | Ensambla la compuerta de cuota en vivo | Nueva instancia | No lanza | Sustrato. final readonly |
QuotaEnforcementGuard::enforce | ?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0 | Compuerta de cuota fail-closed con reserva atómica | QuotaDecision (solo resultados permitidos) | Véase la taxonomía de denegación más abajo | Sustrato. Móntela tras la autenticación del inquilino, antes del controlador facturable |
PlanResolverInterface::resolve | TenantContext $tenant | Resuelve un inquilino a su plan y políticas por característica | ResolvedPlan | NoPlanForTenantException | Sustrato. Un plan de reserva predeterminado para inquilinos desconocidos es un defecto |
RegistryPlanResolver | array<non-empty-string, ResolvedPlan> $plansByTenant | Resolutor respaldado por mapa | ResolvedPlan | NoPlanForTenantException para inquilinos no mapeados | Sustrato. Fail-closed por construcción |
ResolvedPlan::policyFor | non-empty-string $featureKey | Búsqueda de política en el plan resuelto | ?QuotaPolicy | No lanza | Sustrato. null significa característica desconocida; la compuerta la deniega |
QuotaPolicy | non-empty-string $featureKey, float $limit, OveragePolicy $overagePolicy | Límite por característica y política de incumplimiento | — | No lanza | Sustrato. UNLIMITED = -1.0; un límite 0.0 es asignación cero, no ilimitado; isUnlimited(), isBlocking() |
QuotaDecision | Estáticos bypassed(), unlimited(), consumed() | Objeto de valor de resultado permitido | QuotaDecision | No lanza | Sustrato. isAllowed() es siempre verdadero; toda denegación lanza en su lugar |
UsageCounter | Instantánea de fila: inquilino, característica, límites del periodo, used, limit, updatedAt | Fila de uso inmutable | — | No lanza | Sustrato. remaining() puede ser negativo; wouldExceed() es estricto |
UsageCounterStoreInterface::get | Inquilino, característica, límites del periodo, float $limit | Lee la fila de uso y la crea con used = 0 cuando no existe | UsageCounter | UsageStoreUnavailableException | Sustrato. Nunca devuelve un valor falsy ante un fallo del backend |
UsageCounterStoreInterface::tryConsume | Inquilino, característica, límites del periodo, float $amount, float $limit | Reserva atómica de comparar y establecer dentro del límite | ?UsageCounter (null cuando la reserva incumpliría el límite) | UsageStoreUnavailableException | Sustrato. Debe ser una única operación atómica contra el almacén subyacente |
InMemoryUsageCounterStore | — | Implementación de referencia en proceso del contrato del almacén | Según la interfaz | Según la interfaz | Sustrato. Solo un proceso; documenta el invariante de atomicidad |
QuotaEnforcementException (abstract) | — | Tipo base de toda denegación del sustrato | — | Es la familia de lanzables | Sustrato. Cada subtipo declara 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;Taxonomía de denegación de QuotaEnforcementGuard::enforce
| Excepción | Estado HTTP | Se lanza cuando |
|---|---|---|
MissingTenantContextException | 401 | Modo SaaS sin contexto de inquilino autenticado |
NoPlanForTenantException | 402 | El resolutor no encuentra ningún plan asignado al inquilino |
UnknownFeatureException | 402 | El plan resuelto no define ninguna política para la clave de característica |
UsageStoreUnavailableException | 503 | El almacén de uso no puede leerse ni actualizarse atómicamente; también se lanza ante un $amount no positivo |
QuotaExceededException | 402 (SaaS) / 403 (on-prem) | Se supera la cuota de una política bloqueante, o una reserva concurrente consumió el último margen |
Contrato de comportamiento
Sección titulada «Contrato de comportamiento»- El registro predeterminado incluye tres niveles (Standard / Advanced / High Control) con cuotas de CU y conjuntos de capacidades crecientes. Una solicitud de plan no registrado falla con un
InvalidArgumentExceptionexplícito. QuotaManager::checkQuota()lanza solo cuando se cumplen ambas condiciones: la política es bloqueante y el uso actual está estrictamente por encima de la cuota incluida. Una política de alerta de presupuesto nunca lanza; el excedente se señaliza mediante alertas.remainingQuota()yusagePercentage()son lecturas puras y nunca bloquean. La cuota restante se vuelve negativa en excedente; el porcentaje de uso supera1.0en excedente.- Las alertas se evalúan en orden ascendente de umbral: aviso al 80%, aviso al 100% (crítico) y luego presupuesto superado (crítico). El presupuesto superado depende de un excedente estricto; un uso de exactamente el 100% dispara el aviso del 100%, no el de presupuesto superado.
- Cada tipo de alerta se dispara como máximo una vez por inquilino y por periodo de facturación. El estado de disparo se registra mediante
AlertStateRepositoryInterface, por lo que la deduplicación es tan duradera como la implementación elegida. - La clave de deduplicación incorpora el periodo UTC
YYYY-MM. Por tanto, un nuevo mes natural rearma automáticamente cada tipo de alerta; no se requiere ninguna llamada de borrado para el rearmado por renovación.clearAlerts()borra el periodo actual, lo que rearma las alertas a mitad del periodo, por ejemplo tras una mejora de plan. - Un protector de discrepancia de plan en
evaluate()rechaza una llamada en la que el plan proporcionado y la definición de plan no coinciden, protegiendo frente a una definición de un nivel distinto al del plan del inquilino. - Toda la aritmética de periodos está anclada a UTC. El instante de restablecimiento de cuota superada es el primer día del mes natural siguiente a medianoche UTC; una respuesta de soft-stop debería anunciarlo como el horizonte de reintento.
QuotaEnforcementGuardes fail-closed en modo SaaS. Inquilino ausente, plan ausente, característica desconocida, caída del almacén e incumplimiento de cuota deniegan todos; nada se filtra hacia una autorización implícita. Los despliegues no SaaS se excluyen únicamente construyendo la compuerta con unDeploymentModeno SaaS.- Las políticas bloqueantes reservan el uso mediante
UsageCounterStoreInterface::tryConsume, un comparar y establecer atómico. Las solicitudes concurrentes no pueden empujar colectivamente el uso más allá del límite; el perdedor de la carrera recibeQuotaExceededExceptionaunque la comprobación previa haya pasado. - Bajo una política de alerta de presupuesto, la compuerta registra el consumo con el mejor esfuerzo y nunca deniega; una reserva por encima del techo flexible aún registra la fila en el límite.
QuotaExceededExceptiones consciente del modo de despliegue: las denegaciones SaaS se asignan a HTTP 402 con el código de especificaciónSPEC-BILLING-003y se marcan como reintentables; las denegaciones on-prem se asignan a HTTP 403 conSPEC-LIC-001.- La biblioteca no emite respuestas HTTP por sí misma. Los códigos de estado declarados son el contrato para la capa de borde, que asigna una denegación lanzada a una respuesta y no debe invocar el controlador facturable.
Casos límite y modos de fallo
Sección titulada «Casos límite y modos de fallo»- Cuota incluida no positiva.
usagePercentage(),evaluate()yOverageCalculator::calculate()producen todos una relación de uso0.0en lugar de dividir por cero. Las alertas por umbral no se disparan entonces solo por la relación. - Alerta de presupuesto con gran excedente. Tanto el gestor como la compuerta devuelven resultados permitidos. No trate la ausencia de una excepción como prueba de estar dentro de la cuota; consulte
OverageResulto el flujo de alertas. - Justo en el límite.
checkQuota()concurrentCu == includedCuQuotapasa.BudgetExceededrequiere excedente estricto.UsageCounter::wouldExceed()también es estricto. MonthlyCapReached. El enum declara este cuarto tipo de alerta, peroBillingAlertService::evaluate()nunca lo emite; su lista de candidatos cubre solo las tres alertas por umbral. Está reservado para emisores de seguimiento de topes fuera de este módulo.- Definiciones de nivel duplicadas.
PlanRegistryindexa por valor de nivel; la última definición de un nivel reemplaza silenciosamente a las anteriores. Construya los registros a partir de una lista deduplicada. - Asignación cero frente a ilimitado. Un límite de
QuotaPolicyde0.0significa que todo consumo en el periodo es excedente. Solo el centinela negativoUNLIMITEDdesactiva la medición;isUnlimited()nunca bloquea. - Importe de reserva no positivo.
enforce()deniega un$amountno positivo en modo fail-closed conUsageStoreUnavailableException(503). Esto es un defecto del llamante, no una caída del almacén. - Caída del almacén. Cualquier fallo de lectura o reserva se manifiesta como
UsageStoreUnavailableExceptiony deniega. La compuerta nunca permite trabajo sin medir mientras el medidor está caído. - Implementaciones en memoria.
InMemoryAlertStateRepositoryeInMemoryUsageCounterStoreson correctas únicamente dentro de un solo proceso PHP. Los despliegues multirréplica deben proporcionar implementaciones respaldadas por un almacén de datos con atomicidad real; un almacén de leer-luego-escribir es un defecto que permite superar la cuota bajo carga. - Modo FIPS. La facturación no realiza operaciones criptográficas propias y no tiene comportamiento específico de FIPS. La identidad de inquilino que consume debe originarse en un contexto autenticado cuya postura FIPS se documenta con la superficie SaaS.
Conformidad
Sección titulada «Conformidad»| Afirmación | Estándar | Cláusula |
|---|---|---|
| El código de estado 402 está reservado para uso futuro; no conlleva semántica normativa de solicitud propia. | RFC 9110 | §15.5.3 |
| 429 indica que el cliente ha enviado demasiadas solicitudes en un periodo de tiempo determinado («limitación de tasa»). | RFC 6585 | §4 |
| Retry-After indica cuánto tiempo debería esperar el agente de usuario antes de realizar una solicitud de seguimiento. | RFC 9110 | §10.2.3 |
Todas las cláusulas están parafraseadas; NextPDF no reproduce texto normativo. NextPDF no formula ninguna afirmación de conformidad ni certificación de protocolo HTTP para esta superficie. La asignación 402 / 429 / 200 declarada por OveragePolicy::httpStatusCode() y los códigos de denegación 401 / 402 / 503 de la compuerta son una convención de producto alineada con las cláusulas anteriores: RFC 9110 reserva el 402, por lo que su uso aquí para denegación de pago es la convención habitual del sector, no una semántica definida por el IETF. El horizonte de reintento de soft-stop (resetsAt) es el valor que una capa de borde debería exponer como orientación de Retry-After. Emitir las respuestas HTTP reales, las cabeceras y el comportamiento de caché es responsabilidad de la aplicación anfitriona.
Notas de desarrollo
Sección titulada «Notas de desarrollo»- Componga el modelo a partir de
PlanRegistry::defaultRegistry(), unaOveragePolicyy unQuotaManager; añadaBillingAlertServicecon una implementación duradera deAlertStateRepositoryInterfacepara las alertas. - Monte
QuotaEnforcementGuarden la canalización de solicitudes tras la autenticación del inquilino y antes del controlador facturable. CaptureQuotaEnforcementExceptiony laQuotaExceededExceptionde facturación en el borde y asignehttpStatusCode()a la respuesta. - Las definiciones de plan de este módulo son la única fuente de verdad para la facturación; no mantenga una definición de facturación paralela en otro lugar de su despliegue.
- Las implementaciones en memoria hacen que toda la superficie sea comprobable con pruebas unitarias sin E/S. Pruebas de límite recomendadas: uso exactamente en la cuota, una unidad por encima, umbrales de relación en 0.8 y 1.0, el protector de discrepancia de plan, la carrera CAS (dos reservas contra la última unidad de margen) y la denegación por caída del almacén.
- Las clases del modelo central llevan
@since 2.2.0; el sustrato lleva@since 2.3.0. La línea de paquete actual es 3.1.0. - El operador es responsable de las implementaciones del repositorio de estado de alertas y del almacén de uso, de su durabilidad entre réplicas y de cualquier rearmado de alertas a mitad de periodo mediante
clearAlerts().
Límite de publicación
Sección titulada «Límite de publicación»Esta página documenta únicamente el comportamiento observable externamente y la superficie de API pública admitida. Las rutas de espacio de nombres internas, las clases auxiliares, las tablas de mecanismos, los nombres de archivo de runbook y los prefijos de tique quedan fuera de alcance.