Skip to main content
Petanque Life

Pricing Catalog

F08.10 11 features Planned

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