Enterprise edisi
Billing — Referensi Mendalam
Sekilas
Bagian berjudul “Sekilas”Halaman ini adalah referensi mendalam untuk permukaan billing NextPDF Enterprise. Permukaan ini memiliki dua lapisan. Model billing di NextPDF\Enterprise\Billing mendefinisikan tier plan, kuota, kebijakan overage, dan alert penggunaan yang terdeduplikasi. Substrat penegakan di NextPDF\Enterprise\Billing\Substrate menempatkan model tersebut pada jalur permintaan langsung, secara fail-closed dan aman terhadap konkurensi. Titik masuknya adalah PlanRegistry, QuotaManager, OverageCalculator, BillingAlertService, dan QuotaEnforcementGuard. Untuk panduan tingkat alur kerja, lihat halaman kapabilitas Billing.
Ketersediaan & lisensi
Bagian berjudul “Ketersediaan & lisensi”Kapabilitas ini hadir dalam NextPDF Enterprise (nextpdf/enterprise) dan aktif dengan envelope lisensi tier Enterprise. Deployment tanpa entitlement tersebut tidak memuat kelas-kelas kapabilitas ini. Bandingkan edisi dan dapatkan lisensi.
Billing adalah kapabilitas dasar Enterprise tanpa flag per-fitur terpisah; ia tersedia begitu paket Enterprise terpasang berdampingan dengan paket Core. NextPDF Core (Apache-2.0) dan NextPDF Pro tidak memiliki model plan, kuota, atau overage; permukaan ini tidak memiliki padanan di tier yang lebih rendah. Inklusi plan, kuota, dan ketentuan komersial diatur oleh perjanjian lisensi, bukan oleh penegakan runtime; referensi ini bukan opini hukum atau kontraktual.
Permukaan API publik
Bagian berjudul “Permukaan API publik”Semua simbol berada di bawah NextPDF\Enterprise\Billing. Baris yang ditandai substrate berada di bawah NextPDF\Enterprise\Billing\Substrate. TenantContext adalah tipe tenant terautentikasi dari NextPDF\Enterprise\SaaS.
| Simbol | Parameter | Perilaku default | Mengembalikan | Melempar atau gagal dengan | Catatan |
|---|---|---|---|---|---|
SaaSPlan (enum) | — | Tier plan berbasis string: standard, advanced, high_control | — | Tidak melempar | label() mengembalikan nama tampilan |
PlanDefinition::__construct | SaaSPlan $plan, float $includedCuQuota, list<CapabilityCode> $capabilities, non-empty-string $priceTier, bool $intelligencePackIncluded, bool $privacyPackIncluded | Objek nilai plan yang immutable; menyimpan input apa adanya | Instance baru | Tidak melempar | final readonly; properti publik yang dipromosikan |
PlanDefinition::includesCapability | CapabilityCode $capability | Pengecekan keanggotaan berbasis identitas ketat | bool | Tidak melempar | — |
PlanRegistry::__construct | list<PlanDefinition> $definitions | Mengindeks definisi berdasarkan tier; definisi terakhir per tier yang menang | Registry baru | Tidak melempar | Untuk pengujian dan set plan white-label |
PlanRegistry::get | SaaSPlan $plan | Pencarian plan kanonik | PlanDefinition | InvalidArgumentException ketika plan tidak terdaftar | — |
PlanRegistry::has | SaaSPlan $plan | Pemeriksaan registrasi | bool | Tidak melempar | — |
PlanRegistry::defaultRegistry (statis) | — | Default produksi: Standard 1.000 CU; Advanced 5.000 CU plus Intelligence Pack; High Control 20.000 CU plus Intelligence dan Privacy Pack | PlanRegistry | Tidak melempar | Gunakan kecuali ketentuan kontraktual memerlukan definisi kustom |
OveragePolicy (enum) | — | hard_stop, soft_stop, budget_alert | — | Tidak melempar | httpStatusCode() memetakan 402 / 429 / 200; isBlocking() bernilai true hanya untuk hard dan soft stop |
QuotaManager::__construct | PlanRegistry $planRegistry, OveragePolicy $overagePolicy | Mengikat registry ke satu kebijakan | Instance baru | Tidak melempar | — |
QuotaManager::checkQuota | TenantContext $tenant, SaaSPlan $plan, float $currentCu | Kembali secara diam-diam pada atau di bawah kuota, atau di bawah kebijakan non-blocking | void | QuotaExceededException pada overage ketat di bawah kebijakan blocking; InvalidArgumentException dari registry pada plan yang tidak terdaftar | resetsAt = hari pertama bulan berikutnya, tengah malam UTC |
QuotaManager::remainingQuota | SaaSPlan $plan, float $currentCu | Pembacaan murni; tidak pernah memblokir | float | InvalidArgumentException dari registry | Negatif saat overage |
QuotaManager::usagePercentage | SaaSPlan $plan, float $currentCu | Pembacaan murni; tidak pernah memblokir | float | InvalidArgumentException dari registry | 0.0 ketika kuota yang disertakan non-positif; di atas 1.0 saat overage |
OverageCalculator::calculate | PlanDefinition $plan, float $currentCu | Menghitung snapshot overage yang immutable | OverageResult | Tidak melempar | final readonly, stateless |
OverageResult | includedCu, usedCu, overageCu, usageRatio, isOverage | Hasil perhitungan yang immutable | — | Tidak melempar | overageCu = max(0, used - included); isOverage memerlukan overage ketat |
BillingAlertType (enum) | — | quota_warning_80, quota_warning_100, budget_exceeded, monthly_cap_reached | — | Tidak melempar | threshold() 0.8 / 1.0 / 1.0 / 1.0; severity() warning / critical / critical / critical |
BillingAlertService::__construct | AlertStateRepositoryInterface $alertState | Mengikat penyimpanan deduplikasi | Instance baru | Tidak melempar | — |
BillingAlertService::evaluate | TenantContext $tenant, SaaSPlan $plan, PlanDefinition $planDef, float $currentCu | Memicu alert yang belum terpicu dalam urutan ambang menaik dan mencatatnya | list<BillingAlertType> | InvalidArgumentException pada ketidakcocokan plan/definisi | Kunci dedup: tenant, tipe, periode YYYY-MM UTC |
BillingAlertService::clearAlerts | TenantContext $tenant | Membersihkan status-terpicu tenant untuk periode UTC saat ini | void | Kegagalan yang didefinisikan repository merambat | Mengaktifkan kembali alert dalam periode yang sama |
AlertStateRepositoryInterface | hasAlertFired(), markAlertFired(), clearForPeriod() | Kontrak persistensi deduplikasi-alert yang durable | Per metode | Didefinisikan implementasi | Operator memiliki durabilitas lintas replika |
InMemoryAlertStateRepository | — | Status-terpicu berbasis array | Per antarmuka | Tidak melempar | Hanya untuk siklus hidup single-request dan pengujian |
QuotaExceededException | currentCu, limitCu, resetsAt, tenantId, isSaaS yang readonly | Penolakan kuota yang sadar mode deployment | — | Adalah objek yang dilempar | httpStatusCode() 402 SaaS / 403 on-prem; specCode() SPEC-BILLING-003 / SPEC-LIC-001; toErrorEnvelope() menghasilkan body error terstruktur |
DeploymentMode (enum) | — | saas, self_hosted_oss, local_development | — | Tidak melempar | Substrate. enforcesQuota() bernilai true hanya untuk Saas; opt-out selalu eksplisit |
QuotaEnforcementGuard::__construct | DeploymentMode, PlanResolverInterface, QuotaManager, UsageCounterStoreInterface | Menyusun gerbang kuota langsung | Instance baru | Tidak melempar | Substrate. final readonly |
QuotaEnforcementGuard::enforce | ?TenantContext $tenant, non-empty-string $featureKey, float $amount = 1.0 | Gerbang kuota fail-closed dengan reservasi atomik | QuotaDecision (hanya hasil yang diizinkan) | Lihat taksonomi penolakan di bawah | Substrate. Pasang setelah autentikasi tenant, sebelum handler yang ditagih |
PlanResolverInterface::resolve | TenantContext $tenant | Menyelesaikan tenant ke plan dan kebijakan per-fiturnya | ResolvedPlan | NoPlanForTenantException | Substrate. Fallback plan-default untuk tenant tak dikenal adalah cacat |
RegistryPlanResolver | array<non-empty-string, ResolvedPlan> $plansByTenant | Resolver berbasis map | ResolvedPlan | NoPlanForTenantException untuk tenant yang tidak dipetakan | Substrate. Fail-closed secara konstruksi |
ResolvedPlan::policyFor | non-empty-string $featureKey | Pencarian kebijakan pada plan yang telah diselesaikan | ?QuotaPolicy | Tidak melempar | Substrate. null berarti fitur tak dikenal; gerbang menolaknya |
QuotaPolicy | non-empty-string $featureKey, float $limit, OveragePolicy $overagePolicy | Batas per-fitur dan kebijakan pelanggaran | — | Tidak melempar | Substrate. UNLIMITED = -1.0; batas 0.0 adalah nol izin, bukan tak terbatas; isUnlimited(), isBlocking() |
QuotaDecision | Statik bypassed(), unlimited(), consumed() | Objek nilai hasil-yang-diizinkan | QuotaDecision | Tidak melempar | Substrate. isAllowed() selalu true; setiap penolakan melempar sebagai gantinya |
UsageCounter | Snapshot baris: tenant, fitur, batas periode, used, limit, updatedAt | Baris penggunaan yang immutable | — | Tidak melempar | Substrate. remaining() dapat negatif; wouldExceed() bersifat ketat |
UsageCounterStoreInterface::get | Tenant, fitur, batas periode, float $limit | Membaca baris penggunaan, membuatnya dengan used = 0 ketika tidak ada | UsageCounter | UsageStoreUnavailableException | Substrate. Tidak pernah mengembalikan nilai falsy pada kegagalan backend |
UsageCounterStoreInterface::tryConsume | Tenant, fitur, batas periode, float $amount, float $limit | Reservasi compare-and-set atomik dalam batas | ?UsageCounter (null ketika reservasi akan melanggar batas) | UsageStoreUnavailableException | Substrate. Harus berupa satu operasi atomik terhadap penyimpanan pendukung |
InMemoryUsageCounterStore | — | Implementasi referensi in-process dari kontrak penyimpanan | Per antarmuka | Per antarmuka | Substrate. Hanya satu proses; mendokumentasikan invarian atomisitas |
QuotaEnforcementException (abstrak) | — | Tipe dasar dari setiap penolakan substrate | — | Adalah keluarga objek yang dilempar | Substrate. Setiap subtipe mendeklarasikan 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;Taksonomi penolakan QuotaEnforcementGuard::enforce
| Exception | Status HTTP | Diangkat ketika |
|---|---|---|
MissingTenantContextException | 401 | Mode SaaS tanpa konteks tenant yang terautentikasi |
NoPlanForTenantException | 402 | Resolver tidak menemukan plan yang ditetapkan untuk tenant |
UnknownFeatureException | 402 | Plan yang diselesaikan tidak mendefinisikan kebijakan untuk kunci fitur |
UsageStoreUnavailableException | 503 | Penyimpanan penggunaan tidak dapat dibaca atau diperbarui secara atomik; juga diangkat untuk $amount non-positif |
QuotaExceededException | 402 (SaaS) / 403 (on-prem) | Kuota kebijakan blocking terlampaui, atau reservasi konkuren menghabiskan sisa headroom terakhir |
Kontrak perilaku
Bagian berjudul “Kontrak perilaku”- Registry default menghadirkan tiga tier (Standard / Advanced / High Control) dengan kuota CU dan set kapabilitas yang meningkat. Permintaan plan yang tidak terdaftar gagal dengan
InvalidArgumentExceptioneksplisit. QuotaManager::checkQuota()mengangkat exception hanya ketika kedua kondisi terpenuhi: kebijakan bersifat blocking, dan penggunaan saat ini secara ketat di atas kuota yang disertakan. Kebijakan budget-alert tidak pernah mengangkat exception; overage disinyalkan melalui alert.remainingQuota()danusagePercentage()adalah pembacaan murni dan tidak pernah memblokir. Sisa kuota menjadi negatif saat overage; persentase penggunaan melampaui1.0saat overage.- Alert dievaluasi dalam urutan ambang menaik: warning 80%, warning 100% (critical), lalu budget-exceeded (critical). Budget-exceeded digerbangi oleh overage ketat; penggunaan tepat 100% memicu warning 100%, bukan budget-exceeded.
- Setiap tipe alert terpicu paling banyak sekali per tenant per periode billing. Status-terpicu dicatat melalui
AlertStateRepositoryInterface, sehingga deduplikasi sedurable implementasi yang dipilih. - Kunci deduplikasi menyematkan periode
YYYY-MMUTC. Bulan kalender baru karenanya mengaktifkan kembali setiap tipe alert secara otomatis; tidak diperlukan panggilan clear untuk pengaktifan-ulang rollover.clearAlerts()membersihkan periode saat ini, yang mengaktifkan kembali alert di tengah periode, misalnya setelah upgrade plan. - Pengaman ketidakcocokan-plan dalam
evaluate()menolak panggilan di mana plan yang diberikan dan definisi plan tidak sesuai, melindungi dari definisi tier yang berbeda dari plan tenant. - Semua aritmetika periode berjangkar ke UTC. Instan reset kuota-terlampaui adalah hari pertama bulan kalender berikutnya pada tengah malam UTC; respons soft-stop sebaiknya mengiklankannya sebagai horizon retry.
QuotaEnforcementGuardbersifat fail-closed dalam mode SaaS. Tenant hilang, plan hilang, fitur tak dikenal, penyimpanan mati, dan pelanggaran kuota semuanya menolak; tidak ada yang lolos ke izin implisit. Deployment non-SaaS hanya opt-out dengan menyusun gerbang menggunakanDeploymentModenon-SaaS.- Kebijakan blocking mereservasi penggunaan melalui
UsageCounterStoreInterface::tryConsume, sebuah compare-and-set atomik. Permintaan konkuren tidak dapat secara kolektif mendorong penggunaan melampaui batas; pihak yang kalah dalam balapan menerimaQuotaExceededExceptionmeski pra-pengecekan lolos. - Di bawah kebijakan budget-alert, gerbang mencatat konsumsi secara best-effort dan tidak pernah menolak; reservasi yang melewati batas lunak tetap mencatat baris pada batasnya.
QuotaExceededExceptionsadar mode deployment: penolakan SaaS memetakan ke HTTP 402 dengan kode spesifikasiSPEC-BILLING-003dan ditandai dapat dicoba ulang; penolakan on-prem memetakan ke HTTP 403 denganSPEC-LIC-001.- Library tidak memancarkan respons HTTP sendiri. Kode status yang dideklarasikan adalah kontrak untuk lapisan edge, yang memetakan penolakan yang dilempar ke respons dan tidak boleh memanggil handler yang ditagih.
Kasus tepi & mode kegagalan
Bagian berjudul “Kasus tepi & mode kegagalan”- Kuota disertakan non-positif.
usagePercentage(),evaluate(), danOverageCalculator::calculate()semuanya menghasilkan rasio penggunaan0.0alih-alih membagi dengan nol. Alert ambang kemudian tidak pernah terpicu dari rasio semata. - Budget-alert plus overage besar. Manager dan gerbang keduanya mengembalikan hasil yang diizinkan. Jangan perlakukan ketiadaan exception sebagai bukti berada dalam kuota; konsultasikan
OverageResultatau aliran alert. - Tepat pada batas.
checkQuota()padacurrentCu == includedCuQuotalolos.BudgetExceededmemerlukan overage ketat.UsageCounter::wouldExceed()juga bersifat ketat. MonthlyCapReached. Enum mendeklarasikan tipe alert keempat ini, tetapiBillingAlertService::evaluate()tidak pernah memancarkannya; daftar kandidatnya hanya mencakup tiga alert ambang. Ia dicadangkan untuk emitter pelacakan-cap di luar modul ini.- Definisi tier duplikat.
PlanRegistrymengindeks berdasarkan nilai tier; definisi terakhir untuk sebuah tier secara diam-diam menggantikan yang sebelumnya. Bangun registry dari daftar yang terdeduplikasi. - Nol-izin versus tak terbatas. Batas
QuotaPolicysebesar0.0berarti setiap konsumsi dalam periode adalah overage. Hanya sentinelUNLIMITEDyang negatif yang menonaktifkan metering;isUnlimited()tidak pernah memblokir. - Jumlah reservasi non-positif.
enforce()menolak$amountnon-positif secara fail-closed denganUsageStoreUnavailableException(503). Ini adalah cacat pemanggil, bukan penyimpanan mati. - Penyimpanan mati. Setiap kegagalan pembacaan atau reservasi muncul sebagai
UsageStoreUnavailableExceptiondan menolak. Gerbang tidak pernah mengizinkan pekerjaan yang tak-termeter saat meter mati. - Implementasi in-memory.
InMemoryAlertStateRepositorydanInMemoryUsageCounterStorehanya benar dalam satu proses PHP. Deployment multi-replika harus menyediakan implementasi yang didukung datastore dengan atomisitas nyata; penyimpanan read-then-write adalah cacat yang memperbolehkan over-quota di bawah beban. - Mode FIPS. Billing tidak melakukan operasi kriptografis sendiri dan tidak memiliki perilaku khusus FIPS. Identitas tenant yang dikonsumsinya harus berasal dari konteks terautentikasi yang postur FIPS-nya didokumentasikan bersama permukaan SaaS.
Konformitas
Bagian berjudul “Konformitas”| Klaim | Standar | Klausul |
|---|---|---|
| Kode status 402 dicadangkan untuk penggunaan mendatang; ia tidak membawa semantik permintaan normatif tersendiri. | RFC 9110 | §15.5.3 |
| 429 menandakan klien telah mengirim terlalu banyak permintaan dalam rentang waktu tertentu (“rate limiting”). | RFC 6585 | §4 |
| Retry-After menunjukkan berapa lama user agent sebaiknya menunggu sebelum membuat permintaan lanjutan. | RFC 9110 | §10.2.3 |
Semua klausul diparafrasekan; NextPDF tidak mereproduksi teks normatif. NextPDF tidak membuat klaim konformitas atau sertifikasi protokol-HTTP untuk permukaan ini. Pemetaan 402 / 429 / 200 yang dideklarasikan oleh OveragePolicy::httpStatusCode() dan kode penolakan 401 / 402 / 503 dari gerbang adalah konvensi produk yang selaras dengan klausul di atas: RFC 9110 mencadangkan 402, sehingga penggunaannya sebagai penolakan-pembayaran di sini adalah konvensi industri yang umum, bukan semantik yang didefinisikan IETF. Horizon retry soft-stop (resetsAt) adalah nilai yang sebaiknya ditampilkan lapisan edge sebagai panduan Retry-After. Memancarkan respons HTTP aktual, header, dan perilaku caching adalah tanggung jawab aplikasi host.
Catatan pengembangan
Bagian berjudul “Catatan pengembangan”- Susun modelnya dari
PlanRegistry::defaultRegistry(), satuOveragePolicy, dan sebuahQuotaManager; tambahkanBillingAlertServicedengan implementasiAlertStateRepositoryInterfaceyang durable untuk pemberian alert. - Pasang
QuotaEnforcementGuarddalam pipeline permintaan setelah autentikasi tenant dan sebelum handler yang ditagih. TangkapQuotaEnforcementExceptiondanQuotaExceededExceptionbilling di edge dan petakanhttpStatusCode()ke respons. - Definisi plan dalam modul ini adalah satu-satunya sumber kebenaran untuk billing; jangan memelihara definisi billing paralel di tempat lain dalam deployment Anda.
- Implementasi in-memory membuat seluruh permukaan dapat diuji-unit tanpa I/O. Pengujian batas yang direkomendasikan: penggunaan tepat pada kuota, satu unit di atasnya, ambang rasio pada 0.8 dan 1.0, pengaman ketidakcocokan-plan, balapan CAS (dua reservasi terhadap unit headroom terakhir), dan penolakan penyimpanan-mati.
- Kelas model inti membawa
@since 2.2.0; substrate membawa@since 2.3.0. Lini paket saat ini adalah 3.1.0. - Operator memiliki implementasi alert-state repository dan usage-store, durabilitasnya lintas replika, dan setiap pengaktifan-ulang alert di tengah periode melalui
clearAlerts().
Batas publikasi
Bagian berjudul “Batas publikasi”Halaman ini hanya mendokumentasikan perilaku yang dapat diamati secara eksternal dan permukaan API publik yang didukung. Path namespace internal, kelas helper, tabel mekanisme, nama file runbook, dan prefiks tiket berada di luar cakupan.