Enterprise 에디션
Billing — 심층 참조
한눈에 보기
섹션 제목: “한눈에 보기”이 페이지는 NextPDF Enterprise 청구 표면에 대한 심층 참조입니다. 이 표면은 두 계층으로 구성됩니다. NextPDF\Enterprise\Billing의 청구 모델은 플랜 등급, 할당량, 초과 정책, 중복 제거된 사용량 알림을 정의합니다. NextPDF\Enterprise\Billing\Substrate의 강제 substrate는 그 모델을 라이브 요청 경로에 fail-closed 및 동시성 안전하게 배치합니다. 진입점은 PlanRegistry, QuotaManager, OverageCalculator, BillingAlertService, QuotaEnforcementGuard입니다. 워크플로 수준 가이드는 Billing 기능 페이지를 참조하십시오.
가용성 및 라이선싱
섹션 제목: “가용성 및 라이선싱”이 기능은 NextPDF Enterprise(nextpdf/enterprise)에 포함되며 Enterprise 등급 라이선스 엔벨로프로 활성화됩니다. 해당 자격이 없는 배포는 이 기능의 클래스를 로드하지 않습니다. 에디션 비교 및 라이선스 받기.
Billing은 별도의 기능별 플래그가 없는 기본 Enterprise 기능입니다. Enterprise 패키지가 Core 패키지 옆에 설치되면 사용할 수 있습니다. NextPDF Core(Apache-2.0)와 NextPDF Pro에는 플랜, 할당량, 초과 모델이 없습니다. 이 표면에는 하위 등급 등가물이 없습니다. 플랜 포함 사항, 할당량, 상업 약관은 런타임 강제가 아니라 라이선스 계약에 의해 규율됩니다. 이 참조는 법률 또는 계약 의견이 아닙니다.
공개 API 표면
섹션 제목: “공개 API 표면”모든 심볼은 NextPDF\Enterprise\Billing 아래에 있습니다. substrate로 표시된 행은 NextPDF\Enterprise\Billing\Substrate 아래에 있습니다. TenantContext는 NextPDF\Enterprise\SaaS의 인증된 테넌트 타입입니다.
| 심볼 | 매개변수 | 기본 동작 | 반환 | 예외 또는 실패 조건 | 참고 |
|---|---|---|---|---|---|
SaaSPlan (enum) | — | 문자열 기반 플랜 등급: standard, advanced, high_control | — | 예외를 던지지 않음 | label()은 표시 이름을 반환합니다 |
PlanDefinition::__construct | SaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncluded | 불변 플랜 값 객체; 입력을 주어진 대로 저장합니다 | 새 인스턴스 | 예외를 던지지 않음 | final readonly; 승격된 public 프로퍼티 |
PlanDefinition::includesCapability | CapabilityCode $capability | 엄격한 동일성 멤버십 검사 | bool | 예외를 던지지 않음 | — |
PlanRegistry::__construct | list<PlanDefinition> $definitions | 정의를 등급별로 인덱싱합니다; 등급당 마지막 정의가 우선합니다 | 새 레지스트리 | 예외를 던지지 않음 | 테스트 및 화이트 라벨 플랜 집합용 |
PlanRegistry::get | SaaSPlan $plan | 정규 플랜 조회 | PlanDefinition | 플랜이 등록되지 않은 경우 InvalidArgumentException | — |
PlanRegistry::has | SaaSPlan $plan | 등록 여부 확인 | bool | 예외를 던지지 않음 | — |
PlanRegistry::defaultRegistry (static) | — | 프로덕션 기본값: Standard 1,000 CU; Advanced 5,000 CU 및 Intelligence Pack; High Control 20,000 CU 및 Intelligence·Privacy Pack | PlanRegistry | 예외를 던지지 않음 | 계약 약관이 사용자 정의 정의를 요구하지 않는 한 사용하십시오 |
OveragePolicy (enum) | — | hard_stop, soft_stop, budget_alert | — | 예외를 던지지 않음 | httpStatusCode()는 402 / 429 / 200으로 매핑합니다; isBlocking()은 hard 및 soft stop에서만 true입니다 |
QuotaManager::__construct | PlanRegistry $planRegistry, OveragePolicy $overagePolicy | 레지스트리를 하나의 정책에 바인딩합니다 | 새 인스턴스 | 예외를 던지지 않음 | — |
QuotaManager::checkQuota | TenantContext $tenant, SaaSPlan $plan, float $currentCu | 할당량 이하이거나 비차단 정책 하에서는 조용히 반환합니다 | void | 차단 정책 하에서 엄격한 초과 시 QuotaExceededException; 등록되지 않은 플랜에 대해 레지스트리에서 InvalidArgumentException | resetsAt = 다음 달 첫째 날, 자정 UTC |
QuotaManager::remainingQuota | SaaSPlan $plan, float $currentCu | 순수 읽기; 절대 차단하지 않음 | float | 레지스트리 InvalidArgumentException | 초과 시 음수 |
QuotaManager::usagePercentage | SaaSPlan $plan, float $currentCu | 순수 읽기; 절대 차단하지 않음 | float | 레지스트리 InvalidArgumentException | 포함 할당량이 양수가 아니면 0.0; 초과 시 1.0 초과 |
OverageCalculator::calculate | PlanDefinition $plan, float $currentCu | 불변 초과 스냅샷을 계산합니다 | OverageResult | 예외를 던지지 않음 | final readonly, 무상태 |
OverageResult | includedCu, usedCu, overageCu, usageRatio, isOverage | 불변 계산 결과 | — | 예외를 던지지 않음 | overageCu = max(0, used - included); isOverage는 엄격한 초과를 요구합니다 |
BillingAlertType (enum) | — | quota_warning_80, quota_warning_100, budget_exceeded, monthly_cap_reached | — | 예외를 던지지 않음 | threshold() 0.8 / 1.0 / 1.0 / 1.0; severity() warning / critical / critical / critical |
BillingAlertService::__construct | AlertStateRepositoryInterface $alertState | 중복 제거 저장소를 바인딩합니다 | 새 인스턴스 | 예외를 던지지 않음 | — |
BillingAlertService::evaluate | TenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCu | 아직 발생하지 않은 알림을 임계값 오름차순으로 발생시키고 기록합니다 | list<BillingAlertType> | 플랜/정의 불일치 시 InvalidArgumentException | 중복 제거 키: 테넌트, 유형, UTC YYYY-MM 기간 |
BillingAlertService::clearAlerts | TenantContext $tenant | 현재 UTC 기간에 대한 테넌트의 발생 상태를 클리어합니다 | void | 리포지토리가 정의한 실패가 전파됩니다 | 동일 기간 내에서 알림을 재무장합니다 |
AlertStateRepositoryInterface | hasAlertFired(), markAlertFired(), clearForPeriod() | 지속적 알림 중복 제거 지속성 계약 | 메서드별 | 구현 정의 | 운영자가 복제본 전반의 지속성을 소유합니다 |
InMemoryAlertStateRepository | — | 배열 기반 발생 상태 | 인터페이스별 | 예외를 던지지 않음 | 단일 요청 생명주기 및 테스트 전용 |
QuotaExceededException | Readonly currentCu, limitCu, resetsAt, tenantId, isSaaS | 배포 모드 인식 할당량 거부 | — | 던져지는 예외 | httpStatusCode() 402 SaaS / 403 온프레미스; specCode() SPEC-BILLING-003 / SPEC-LIC-001; toErrorEnvelope()는 구조화된 오류 본문을 산출합니다 |
DeploymentMode (enum) | — | saas, self_hosted_oss, local_development | — | 예외를 던지지 않음 | Substrate. enforcesQuota()는 Saas에서만 true입니다; 옵트아웃은 항상 명시적입니다 |
QuotaEnforcementGuard::__construct | DeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterface | 라이브 할당량 게이트를 조립합니다 | 새 인스턴스 | 예외를 던지지 않음 | Substrate. final readonly |
QuotaEnforcementGuard::enforce | ?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0 | 원자적 예약을 갖춘 fail-closed 할당량 게이트 | QuotaDecision (허용 결과만) | 아래 거부 분류 참조 | Substrate. 테넌트 인증 후, 청구 대상 핸들러 앞에 마운트하십시오 |
PlanResolverInterface::resolve | TenantContext $tenant | 테넌트를 그 플랜 및 기능별 정책으로 해석합니다 | ResolvedPlan | NoPlanForTenantException | Substrate. 알 수 없는 테넌트에 대한 기본 플랜 폴백은 결함입니다 |
RegistryPlanResolver | array<non-empty-string, ResolvedPlan> $plansByTenant | 맵 기반 리졸버 | ResolvedPlan | 매핑되지 않은 테넌트에 대해 NoPlanForTenantException | Substrate. 구성상 fail-closed |
ResolvedPlan::policyFor | non-empty-string $featureKey | 해석된 플랜에서의 정책 조회 | ?QuotaPolicy | 예외를 던지지 않음 | Substrate. null은 알 수 없는 기능을 의미합니다; 가드가 이를 거부합니다 |
QuotaPolicy | non-empty-string $featureKey, float $limit, OveragePolicy $overagePolicy | 기능별 한도 및 위반 정책 | — | 예외를 던지지 않음 | Substrate. UNLIMITED = -1.0; 0.0 한도는 무제한이 아니라 제로 허용량입니다; isUnlimited(), isBlocking() |
QuotaDecision | 정적 메서드 bypassed(), unlimited(), consumed() | 허용 결과 값 객체 | QuotaDecision | 예외를 던지지 않음 | Substrate. isAllowed()는 항상 true입니다; 모든 거부는 대신 예외를 던집니다 |
UsageCounter | 행 스냅샷: 테넌트, 기능, 기간 경계, used, limit, updatedAt | 불변 사용량 행 | — | 예외를 던지지 않음 | Substrate. remaining()은 음수일 수 있습니다; wouldExceed()는 엄격합니다 |
UsageCounterStoreInterface::get | 테넌트, 기능, 기간 경계, float $limit | 사용량 행을 읽고, 없으면 used = 0으로 생성합니다 | UsageCounter | UsageStoreUnavailableException | Substrate. 백엔드 실패 시 절대 falsy 값을 반환하지 않습니다 |
UsageCounterStoreInterface::tryConsume | 테넌트, 기능, 기간 경계, float $amount, float $limit | 한도 내에서의 원자적 compare-and-set 예약 | ?UsageCounter (예약이 한도를 위반하면 null) | UsageStoreUnavailableException | Substrate. 백킹 저장소에 대한 단일 원자적 작업이어야 합니다 |
InMemoryUsageCounterStore | — | 저장소 계약의 인프로세스 참조 구현 | 인터페이스별 | 인터페이스별 | Substrate. 단일 프로세스 전용; 원자성 불변식을 문서화합니다 |
QuotaEnforcementException (abstract) | — | 모든 substrate 거부의 기본 유형 | — | 던져지는 예외 계열 | Substrate. 각 하위 유형이 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;QuotaEnforcementGuard::enforce의 거부 분류
| 예외 | HTTP 상태 | 발생 조건 |
|---|---|---|
MissingTenantContextException | 401 | SaaS 모드에서 인증된 테넌트 컨텍스트가 없음 |
NoPlanForTenantException | 402 | 리졸버가 테넌트에 할당된 플랜을 찾지 못함 |
UnknownFeatureException | 402 | 해석된 플랜이 기능 키에 대한 정책을 정의하지 않음 |
UsageStoreUnavailableException | 503 | 사용량 저장소를 읽거나 원자적으로 업데이트할 수 없음; 양수가 아닌 $amount에 대해서도 발생 |
QuotaExceededException | 402 (SaaS) / 403 (온프레미스) | 차단 정책의 할당량이 초과되었거나, 동시 예약이 마지막 여유분을 소진함 |
동작 계약
섹션 제목: “동작 계약”- 기본 레지스트리는 증가하는 CU 할당량과 기능 집합을 갖춘 세 가지 등급(Standard / Advanced / High Control)을 제공합니다. 등록되지 않은 플랜 요청은 명시적
InvalidArgumentException으로 실패합니다. QuotaManager::checkQuota()는 두 조건이 모두 성립할 때만 예외를 발생시킵니다. 정책이 차단형이고, 현재 사용량이 포함 할당량을 엄격히 초과하는 경우입니다. 예산 알림 정책은 절대 발생시키지 않습니다; 초과는 알림을 통해 신호됩니다.remainingQuota()와usagePercentage()는 순수 읽기이며 절대 차단하지 않습니다. 초과 시 남은 할당량은 음수가 되고, 사용 백분율은1.0을 초과합니다.- 알림은 임계값 오름차순으로 평가됩니다. 80% 경고, 100% 경고(critical), 그다음 예산 초과(critical)입니다. 예산 초과는 엄격한 초과에 게이트됩니다; 정확히 100% 사용량은 예산 초과가 아니라 100% 경고를 발생시킵니다.
- 각 알림 유형은 테넌트당 청구 기간당 최대 한 번 발생합니다. 발생 상태는
AlertStateRepositoryInterface를 통해 기록되므로, 중복 제거는 선택한 구현만큼 지속됩니다. - 중복 제거 키는 UTC
YYYY-MM기간을 포함합니다. 따라서 새 달력 월은 모든 알림 유형을 자동으로 재무장합니다; 롤오버 재무장에는 클리어 호출이 필요하지 않습니다.clearAlerts()는 현재 기간을 클리어하여, 예를 들어 플랜 업그레이드 후처럼 기간 중간에 알림을 재무장합니다. evaluate()의 플랜 불일치 가드는 제공된 플랜과 플랜 정의가 일치하지 않는 호출을 거부하여, 테넌트의 플랜과 다른 등급의 정의로부터 보호합니다.- 모든 기간 계산은 UTC에 고정됩니다. 할당량 초과 재설정 시점은 다음 달력 월의 첫째 날 자정 UTC입니다; 소프트 스톱 응답은 이를 재시도 기한으로 광고해야 합니다.
QuotaEnforcementGuard는 SaaS 모드에서 fail-closed입니다. 누락된 테넌트, 누락된 플랜, 알 수 없는 기능, 저장소 장애, 할당량 위반이 모두 거부됩니다; 어떤 것도 암묵적 허용으로 넘어가지 않습니다. 비SaaS 배포는 오직 비SaaSDeploymentMode로 가드를 구성함으로써만 옵트아웃합니다.- 차단 정책은 원자적 compare-and-set인
UsageCounterStoreInterface::tryConsume를 통해 사용량을 예약합니다. 동시 요청은 집합적으로 사용량을 한도 너머로 밀어낼 수 없습니다; 경쟁에서 진 쪽은 사전 검사가 통과했더라도QuotaExceededException을 받습니다. - 예산 알림 정책 하에서 가드는 소비를 최선 노력으로 기록하며 절대 거부하지 않습니다; 소프트 상한을 넘는 예약도 여전히 한도에서 행을 기록합니다.
QuotaExceededException은 배포 모드를 인식합니다. SaaS 거부는 사양 코드SPEC-BILLING-003과 함께 HTTP 402로 매핑되며 재시도 가능으로 표시됩니다; 온프레미스 거부는SPEC-LIC-001과 함께 HTTP 403으로 매핑됩니다.- 라이브러리 자체는 HTTP 응답을 방출하지 않습니다. 선언된 상태 코드는 엣지 계층을 위한 계약이며, 엣지 계층은 던져진 거부를 응답으로 매핑하고 청구 대상 핸들러를 호출해서는 안 됩니다.
엣지 케이스 및 실패 모드
섹션 제목: “엣지 케이스 및 실패 모드”- 양수가 아닌 포함 할당량.
usagePercentage(),evaluate(),OverageCalculator::calculate()는 모두 0으로 나누는 대신0.0사용 비율을 산출합니다. 그러면 임계값 알림은 비율만으로는 절대 발생하지 않습니다. - 예산 알림과 큰 초과. 관리자와 가드 모두 허용 결과를 반환합니다. 예외의 부재를 할당량 내에 있다는 증거로 취급하지 마십시오;
OverageResult또는 알림 스트림을 참조하십시오. - 정확히 한도에서.
currentCu == includedCuQuota에서의checkQuota()는 통과합니다.BudgetExceeded는 엄격한 초과를 요구합니다.UsageCounter::wouldExceed()역시 엄격합니다. MonthlyCapReached. enum은 이 네 번째 알림 유형을 선언하지만,BillingAlertService::evaluate()는 절대 이를 방출하지 않습니다; 그 후보 목록은 세 가지 임계값 알림만 포함합니다. 이는 이 모듈 외부의 상한 추적 방출기를 위해 예약되어 있습니다.- 중복 등급 정의.
PlanRegistry는 등급 값으로 인덱싱합니다; 등급에 대한 마지막 정의가 이전 정의를 조용히 대체합니다. 중복 제거된 목록으로 레지스트리를 구성하십시오. - 제로 허용량 대 무제한.
QuotaPolicy한도0.0은 기간 내 모든 소비가 초과임을 의미합니다. 오직 음수UNLIMITED센티널만 계량을 비활성화합니다;isUnlimited()는 절대 차단하지 않습니다. - 양수가 아닌 예약 양.
enforce()는 양수가 아닌$amount를UsageStoreUnavailableException(503)으로 fail-closed 거부합니다. 이는 저장소 장애가 아니라 호출자 결함입니다. - 저장소 장애. 모든 읽기 또는 예약 실패는
UsageStoreUnavailableException으로 표면화되어 거부됩니다. 가드는 계량기가 다운된 동안 계량되지 않은 작업을 절대 허용하지 않습니다. - 인메모리 구현.
InMemoryAlertStateRepository와InMemoryUsageCounterStore는 오직 하나의 PHP 프로세스 내에서만 정확합니다. 다중 복제본 배포는 실제 원자성을 갖춘 데이터스토어로 뒷받침되는 구현을 제공해야 합니다; read-then-write 저장소는 부하 하에서 할당량 초과를 허용하는 결함입니다. - FIPS 모드. 청구는 자체적으로 어떤 암호화 작업도 수행하지 않으며 FIPS 고유의 동작이 없습니다. 청구가 소비하는 테넌트 신원은 그 FIPS 태세가 SaaS 표면과 함께 문서화된 인증된 컨텍스트에서 비롯되어야 합니다.
적합성
섹션 제목: “적합성”| 주장 | 표준 | 절 |
|---|---|---|
| 402 상태 코드는 향후 사용을 위해 예약되어 있습니다; 자체적인 규범적 요청 시맨틱을 담지 않습니다. | RFC 9110 | §15.5.3 |
| 429는 클라이언트가 주어진 시간 내에 너무 많은 요청을 보냈음을 나타냅니다(“속도 제한”). | RFC 6585 | §4 |
| Retry-After는 사용자 에이전트가 후속 요청을 하기 전에 얼마나 기다려야 하는지를 나타냅니다. | RFC 9110 | §10.2.3 |
모든 절은 의역되었습니다; NextPDF는 규범적 텍스트를 재현하지 않습니다. NextPDF는 이 표면에 대해 어떤 HTTP 프로토콜 적합성 또는 인증 주장도 하지 않습니다. OveragePolicy::httpStatusCode()가 선언하는 402 / 429 / 200 매핑과 가드의 401 / 402 / 503 거부 코드는 위 절에 정렬된 제품 관례입니다. RFC 9110은 402를 예약하므로, 여기서의 결제 거부 용도는 IETF가 정의한 시맨틱이 아니라 일반적인 업계 관례입니다. 소프트 스톱 재시도 기한(resetsAt)은 엣지 계층이 Retry-After 안내로 표면화해야 하는 값입니다. 실제 HTTP 응답, 헤더, 캐싱 동작을 방출하는 것은 호스팅 애플리케이션의 책임입니다.
개발 참고
섹션 제목: “개발 참고”PlanRegistry::defaultRegistry(), 하나의OveragePolicy,QuotaManager로 모델을 구성하십시오; 알림을 위해 지속적AlertStateRepositoryInterface구현과 함께BillingAlertService를 추가하십시오.- 테넌트 인증 후, 청구 대상 핸들러 앞의 요청 파이프라인에
QuotaEnforcementGuard를 마운트하십시오. 엣지에서QuotaEnforcementException과 청구QuotaExceededException을 잡아httpStatusCode()를 응답으로 매핑하십시오. - 이 모듈의 플랜 정의는 청구의 단일 진실 공급원입니다; 배포의 다른 곳에서 병렬 청구 정의를 유지하지 마십시오.
- 인메모리 구현은 전체 표면을 I/O 없이 단위 테스트 가능하게 만듭니다. 권장 경계 테스트: 정확히 할당량에서의 사용량, 한 단위 초과, 0.8 및 1.0에서의 비율 임계값, 플랜 불일치 가드, CAS 경쟁(마지막 여유 단위에 대한 두 예약), 저장소 장애 거부.
- 코어 모델 클래스는
@since 2.2.0을 지니고; substrate는@since 2.3.0을 지닙니다. 현재 패키지 라인은 3.1.0입니다. - 운영자는 알림 상태 리포지토리 및 사용량 저장소 구현, 복제본 전반의 지속성, 그리고
clearAlerts()를 통한 모든 기간 중간 알림 재무장을 소유합니다.
게시 경계
섹션 제목: “게시 경계”이 페이지는 외부에서 관찰 가능한 동작과 지원되는 공개 API 표면만 문서화합니다. 내부 네임스페이스 경로, 헬퍼 클래스, 메커니즘 표, 런북 파일명, 티켓 접두사는 범위 밖입니다.