Pricing Catalog
At a glance
Pricing Catalog is the platform's commercial price book: 14 SKUs split between 7 self-service and 7 contract tiers, versioned through immutable PricingCatalogVersion documents, seeded from market analysis with idempotent script v2026.01, exposed read-only on a cached public pricing endpoint for the marketing site and gated admin endpoints on the platform console, with national tier auto-classification by licence count (XS, S, M, L) feeding the contract sales process.
How it works
The data model is built around four entities. TenantBillingType is a 14-value enum identifying every SKU. BillingMode distinguishes self_service from contract.
PricingCatalogVersion is a versioned, immutable wrapper — once published, a version never changes — carrying a publication date and human label such as v2026.01. PricingTier is a per-version, per-billing-type row holding the Decimal EUR price and i18n display, description and includes fields for English, French, Spanish and Swedish, plus the licence-count range and Stripe price ID for the corresponding subscription product. The seed script seed_pricing_catalog_v2026_01 is idempotent: running it on an empty catalog creates the initial 14 tiers with exact prices from the market analysis A2 reference; running it again is a no-op.
The admin API exposes /admin/pricing-catalog/current returning the 14 tiers of the active version, /admin/pricing-catalog/versions for history, /admin/pricing-catalog/versions/{id} for a specific snapshot and /admin/pricing-catalog/tiers/{billing_type} for a single SKU; all endpoints require platform-admin authentication. The public surface is /public/pricing returning the seven self-service tiers with no auth and Cache-Control max-age=300 so the marketing site renders pricing fast. Admin UI lives under Platform > Pricing Catalog as read-only list and detail views: 14 tiers in a table, detail view with i18n fields, Stripe IDs and licence range.
National tier auto-classification at resolve_national_tier_from_license_count maps 0-999 to XS, 1000-2999 to S, 3000-9999 to M and 10000+ to L, used by the contract sales process to suggest the right tier. Subscription documents reference pricing_version so price-lock at original signing is preserved across renewals.
Key capabilities
- 14-SKU catalog (7 self-service plus 7 contract) versioned as immutable PricingCatalogVersion
- Idempotent seed script v2026.01 with exact prices from market analysis
- i18n display, description and includes per tier across English, French, Spanish and Swedish
- Public /public/pricing endpoint with 5-minute cache for the marketing site
- Admin pricing-catalog API and read-only UI under Platform > Pricing Catalog
- National tier auto-classification by licence count (XS/S/M/L)
- Subscription pricing_version reference enabling price-lock guarantee on renewal
In practice
A platform sales engineer prepares a contract for a federation with 6 200 licensed players. He runs resolve_national_tier_from_license_count which suggests tier M (3000-9999). He opens /admin/pricing-catalog/current, finds the contract M tier, sees the EUR price and i18n description in the federation's primary language, and pulls the Stripe price ID for the contract document.
The marketing site keeps showing fresh prices because /public/pricing is hit by the static-site build and cached for five minutes. Two months later the platform publishes v2026.04 with one self-service tier slightly repriced; the new PricingCatalogVersion is immutable, the old subscriptions keep referencing v2026.01 (price-lock), and only new sign-ups land on v2026.04.
Features in this subsystem
11| ID | Status | Features |
|---|---|---|
| F08.10.01 | Shipped | 14-SKU pricing catalog data model — TenantBillingType (14 enums), BillingMode (self_service/contract), PricingCatalogVersion (versioned, immutable), PricingTier (per-version, per-billing-type, Decimal EUR price, i18n display/description/includes). ✅ PL-T011 |
| F08.10.02 | Shipped | Seed data v2026.01 — Idempotent seed script skapar initial katalog med exakta priser från market-analysis.md A2. 7 self-service + 7 contract tiers. i18n för en/fr/es/sv. ✅ PL-T011 |
| F08.10.03 | Shipped | Admin pricing catalog API — GET /admin/pricing-catalog/current (14 tiers), /versions (historik), /versions/{id}, /tiers/{billing_type}. Platform-admin auth. ✅ PL-T011 |
| F08.10.04 | Shipped | Public pricing API — GET /public/pricing (7 self-service tiers, no auth, Cache-Control max-age=300). ✅ PL-T011 |
| F08.10.05 | Shipped | Admin pricing catalog UI — Read-only list- och detaljvy i admin under Platform > Pricing Catalog. 14 tiers i tabell, detaljvy med i18n-fält, Stripe-ID:n, license-range. ✅ PL-T011 |
| F08.10.06 | Shipped | National tier auto-classification — resolve_national_tier_from_license_count: 0–999→XS, 1000–2999→S, 3000–9999→M, ≥10000→L. ✅ PL-T011 |
| F08.10.07 | Shipped | Publicera ny version (write-UI i admin) ✅ PL-F0810b |
| F08.10.08 | Shipped | Prishistorik per kund (koppling via Subscription) ✅ PL-F0810b |
| F08.10.09 | Shipped | Tenant-typ-bryggad pricing-katalog (PL-T226) — PricingTier utökas med tenant_type (broad till TenantType) + denormaliserade domain_count / subsystem_count / feature_count från TENANT_TIER_MATRIX. TENANT_TYPE_FOR_BILLING_TYPE ger reverse-lookup för Stripe-webhook + drift-job. Migration tools/migrations/pl-t226-tenant-type-pricing.py är idempotent. ✅ PL-T226 |
| F08.10.10 | Shipped | Public detail-endpoint för tier-jämförelser (PL-T226) — GET /public/pricing-tiers (full 14-rad-katalog med ?include_legacy=true för 15) + GET /public/pricing-tiers/{tenant_type} (deep-link med included_subsystems / excluded_subsystems från matrisen). 5 min cache. Service tier_for_tenant_type() routar FEDERATION mot national_* via license-count. ✅ PL-T226 |
| F08.10.11 | Shipped | Pricing tier sync-check job (PL-T226) — nattlig jämförelse av PricingTier-tabellen mot www/src/data/tenant-tiers.ts. SEV2 platform-event vid divergens. ✅ PL-T226 |