- Production
- Commerce Platform
Billing Control Panel
Commercial control center replacing WHMCS: billing, provisioning, and customer portals for PhotonSpark
Commercial control center that replaces WHMCS for PhotonSpark. It owns customers, catalog, orders, subscriptions, invoices, payments, entitlements, provisioning orchestration, and audit across web, game, and email hosting. A Go modular monolith (separate API and worker processes) runs on PostgreSQL with a transactional outbox and job queue; Stripe is the recurring payment rail behind an internal ledger, with Romanian invoices and the asynchronous RO e-Factura workflow. Idempotent connectors provision the actual hosting platforms, and LIVE/TEST/DEMO stay separate data dimensions end to end.
- Go
- PostgreSQL
- Next.js
- React
- TypeScript
- MUI
- Keycloak
- Stripe
- Stalwart
- MinIO
- Docker
Trace the systems, boundaries and handoffs
Choose a workflow to see its path through the system. Highlighted services own a step of it; hover, focus or tap any service to see its direct dependencies.
A commercial order becomes an active hosting entitlement
The synchronous request validates identity, billing profile, catalog version, price, accepted policies, and environment. Payment confirmation then releases an outbox-backed provisioning job; the connector’s external resource identifier is written back before the service is considered active.
- Order, profile snapshot, policy snapshot, invoice intent, and outbox event commit together in PostgreSQL.
- No hosting API is called inside checkout; workers execute external side effects after durable commit.
- The control center owns money, entitlement, lifecycle, and environment—not the provider connector.
- Provider resource ID, connector result, job attempt, service state, and audit actor are persisted.
- Public storefront → OIDC BFF → Customer Keycloak
- Public storefront → OIDC BFF → Go API → Commerce domains → Billing ledger → Stripe
- Stripe → Webhook intake → Go API → Billing ledger → Outbox + job queue → Job worker → Web hosting
- Go API → PostgreSQL
- Job worker → Audit history
A Stripe event is verified, deduplicated, and reconciled
Webhook delivery is treated as an untrusted, repeatable message. The intake verifies Stripe’s signature, stores the provider event identity, and applies an idempotent ledger transition. Duplicate or reordered delivery cannot issue a second entitlement or invoice effect.
- Signature, event type, account context, object identity, and environment are checked before mutation.
- Stripe event and provider object IDs prevent duplicate financial transitions across retries.
- Stripe confirms payment movement; the internal ledger remains the application’s commercial source of truth.
- Only a committed paid or renewed decision creates the outbox work consumed by the provisioning worker.
- Stripe → Webhook intake → Go API → Billing ledger
- Go API → PostgreSQL
- Billing ledger → Outbox + job queue → Job worker
- Job worker → Audit history
One durable job contract drives every hosting backend
The worker claims a PostgreSQL job, loads the current entitlement and immutable order inputs, and selects a typed connector. Web, game, email, and manual services share lifecycle semantics while each provider adapter owns its API-specific request and read-back verification.
- Leases, attempt counters, deadlines, and retry policy prevent two workers from owning the same side effect.
- Provision, inspect, suspend, resume, terminate, and rebuild return normalized evidence and external IDs.
- Idempotency is provider-aware; ambiguous timeouts trigger inspection before another create request.
- The provider owns the actual resource; the control center stores the verified commercial and runtime linkage.
- Customer portal → OIDC BFF → Go API → Commerce domains
- OIDC BFF → Customer Keycloak
- Go API → Outbox + job queue → Job worker
- Job worker → Web hosting
- Job worker → Game hosting
- Job worker → Email hosting
- Job worker → Manual services
- Job worker → PostgreSQL
- Job worker → Audit history
Lifecycle state follows payment evidence and paid-through time
The scheduler finds due subscriptions, reconciles Stripe intent and ledger state, and emits lifecycle work only after durable decisions. Cancellation stops future renewal but preserves service until paid-through time; reprovisioning creates a fresh runtime instead of reviving stale provider state.
- Due timestamps, subscription state, environment, provider linkage, and previous attempts come from PostgreSQL.
- Cancel-at-period-end and paid-through timestamps are explicit; provider deletion is not inferred from UI state.
- Payment uncertainty remains reconcilable and does not silently suspend or duplicate the runtime.
- A returning entitlement provisions a fresh provider resource with a new linkage and full audit history.
- Scheduler → PostgreSQL
- Scheduler → Billing ledger → Stripe
- Scheduler → Outbox + job queue → Job worker
- Job worker → Web hosting
- Job worker → Game hosting
- Job worker → Email hosting
- Job worker → Audit history
Fiscal issuance is immutable; ANAF exchange is asynchronous
The ledger assigns a valid LIVE number series, renders and stores the immutable invoice artifact, then hands ANAF submission to a retryable worker. Submission, polling, acceptance, rejection, and response documents are tracked separately from whether the invoice was issued.
- TEST documents are non-fiscal and can never consume or submit against a LIVE fiscal series.
- Rendered invoice and exchange artifacts are immutable objects with database metadata and audit links.
- ANAF availability cannot block the transaction that issued the invoice; retries and polling are durable jobs.
- Issued, queued, submitted, accepted, and rejected are distinct states with timestamps and response evidence.
- Billing ledger → MinIO documents
- Billing ledger → Outbox + job queue → Job worker → ANAF e-Factura
- Job worker → PostgreSQL
- Job worker → Audit history
- OIDC BFF
Nothing.
- OIDC BFF
Nothing.
- OIDC BFF
Nothing.
- Customer Keycloak
- Staff Keycloak
- Go API
- Public storefront
- Customer portal
- Staff console
Nothing.
- OIDC BFF
Nothing.
- OIDC BFF
- Go API
- Stripe
- Commerce domains
- Billing ledger
- PostgreSQL
- Outbox + job queue
- Audit history
- OIDC BFF
- Webhook intake
- Billing ledger
- Go API
- Stripe
- MinIO documents
- Outbox + job queue
- Go API
- Commerce domains
- Scheduler
- ANAF e-Factura
- Web hosting
- Game hosting
- Email hosting
- Manual services
- PostgreSQL
- Audit history
- Outbox + job queue
- Billing ledger
- PostgreSQL
- Outbox + job queue
Nothing.
- Webhook intake
- Billing ledger
Nothing.
- Job worker
Nothing.
- Job worker
Nothing.
- Job worker
Nothing.
- Job worker
Nothing.
- Job worker
Nothing.
- Go API
- Scheduler
- Job worker
- Job worker
- Go API
- Billing ledger
- Scheduler
Nothing.
- Billing ledger
Nothing.
- Go API
- Job worker
Money stays internal
Stripe moves funds and reports provider state. Subscriptions, invoices, payments, entitlements, paid-through time, and fiscal identity belong to the internal ledger.
Commit before calling out
External APIs are never part of the database transaction. Desired work enters the transactional outbox and becomes retryable only after commercial state is durable.
Providers own resources
Web hosting, Pterodactyl, and Stalwart remain authoritative for runtime existence and health; the control center owns the verified linkage and lifecycle intent.
Passwords stay in Keycloak
Customer and staff realms are separate. The BFF handles sessions and the application receives scoped identity without owning passwords, MFA secrets, or card data.
LIVE / TEST / DEMO never blur
Environment is a first-class dimension on orders, invoices, payments, jobs, services, events, documents, and reports—not a UI filter.
Every retry explains itself
Jobs retain attempts, leases, errors, connector evidence, external IDs, actor and request context, and read-back results so recovery is observable rather than hopeful.
Why it exists
WHMCS owned billing but none of the real provisioning, so every product launch meant glue scripts and manual reconciliation.
One system of record for commerce: hosting platforms stay the source of truth for runtime resources, the control center owns money, customers, and orchestration.
Capabilities
- Customer registration through Keycloak
- the application never stores passwords, MFA secrets, or card data.
- Quotes and manually provisioned custom services alongside the automated catalog.
- Separate customer and staff OIDC realms with role-scoped admin and finance views.
- LIVE, TEST, and DEMO as first-class data dimensions across orders, invoices, payments, services, jobs, events, artifacts, and reports.
- Full audit trail across commerce and provisioning actions.
Components
Billing engine
- Internal subscription, invoice, and payment ledger with Stripe as the payment rail only.
- Romanian fiscal invoices with an asynchronous RO e-Factura (ANAF) submission workflow.
- Number series discipline: TEST documents are non-fiscal and never touch a LIVE series.
- Immutable generated documents stored in S3/MinIO-compatible storage.
Order and lifecycle flow
- Billing profile must be complete at the transaction boundary before checkout (HTTP 428 PROFILE_REQUIRED).
- Accepted profile and policy versions are copied into each order as immutable snapshots.
- Order-wide cancellation with Stripe-confirmed renewal intent and paid-through termination.
- Explicit fresh-runtime reprovisioning instead of resurrecting stale resources.
Provisioning connectors
- Idempotent connectors for the web-hosting platform, the Pterodactyl game-hosting fork, Stalwart email, and manually provisioned services.
- Worker and scheduler drain a PostgreSQL transactional outbox and job queue — no message broker to babysit.
Architecture
- Public catalog and checkout
- Customer portal
- Admin and finance console (Next.js / React / MUI)
- Go modular monolith: API, OIDC BFF, webhooks
- Customers · Catalog · Orders · Billing · Services · Audit
- Worker and scheduler on a PostgreSQL transactional outbox + job queue
- Internal subscription / invoice / payment ledger
- Stripe as the recurring payment rail
- Romanian invoices + async RO e-Factura (ANAF) workflow
- Immutable documents in S3/MinIO storage
- Separate customer and staff Keycloak OIDC realms
- The app never owns passwords or MFA
- Immutable policy-acceptance snapshots copied into each order
- Idempotent connectors: web hosting, Pterodactyl game hosting, Stalwart email, manual services
- Stripe-confirmed renewal intent, paid-through termination, fresh-runtime reprovisioning
- LIVE / TEST / DEMO data dimensions across orders, invoices, jobs, and reports