In progress GHub Creator and maintainer Open core

GPlatform Billing

The commercial half. It owns money, and deliberately owns nothing else.

Accounts, the price catalogue, the Stripe relationship, subscriptions, dunning and usage-based charging, for every product in the family. It never decides what a customer is entitled to: that stays in GPlatform Control, and this side asks.

No companion

There is no self-hosted companion and there never will be one, because the thing it does is take money, and that is ours to do.

What it is

The commercial half of the GPlatform family. It owns accounts, the price catalogue, the Stripe relationship, subscriptions, dunning and usage-based charging, for every product in the family: the self-hosted G-Apps, the G-Apps we host, the standalone GPlatform products, and GControl itself.

No local companion, and there will not be one

Every other product in the line either runs on your infrastructure or has a half that does. This one does not, and that is a position rather than a milestone: the thing it does is take money, and that is ours to do.

It owns money. It does not own grants.

This is the boundary the whole application is built on.

What a customer is entitled to lives in GPlatform Control, and is resolved there by the same code that resolves a licence, for every product that has a Control companion. This side names a tier and a module set and asks. It never computes a grant, never holds a signing key, and never learns what a feature does. For products with no Control companion it keeps a small catalogue and entitlement store of its own and serves them directly.

The split is the answer to the objection that stopped automated billing being built the first time. Pricing logic inside the entitlement resolver would put an opinion about money in the one place that has to be simple enough to be obviously correct. It is not in there. It is in here.

Control never talks to Stripe. Nothing outside this application does.

Its own accounts, on purpose

Signing in here is separate from signing in to Control. Requiring a Control account would force every customer of a standalone cloud product — somebody who touches that control plane never — to register with it. Staff accounts being separate too is the cost of that, and it is a small one next to making every customer enrol upstream.

Three roles, because a billing surface asks exactly three questions: may you end this and change the card, may you change what it costs, and may you read what it cost.

Usage is a ledger, not a counter

One row per subscription item, meter and UTC day, which is exactly what the Stripe idempotency key is derived from. A replayed pull writes nothing new and sends nothing twice, so the daily pull is safe to re-run by hand, safe to retry after a timeout, and safe to schedule twice by accident.

Corrections are additive only. A meter cannot go negative, so a day that needs reducing is a credit note and a conversation, not a row edited quietly.

A day built from too few readings is flagged and still reported, because an undercount charges you less than you used and holding it back would delay an invoice over a number that is only ever too low.

Lapsing takes nothing away

Dunning ends at lapsed, which downgrades to free and removes nothing that already exists. The cap refuses the next thing; it never withdraws the last one.

Prices are retired rather than deleted, because a price row is what an existing subscription points at and removing one would leave a paying customer on terms nothing can describe.

Layout

apps/api is NestJS: accounts, the catalogue, Stripe ingestion, and the client that drives Control over mTLS on the local network. apps/worker is Temporal: the daily usage pull, dunning and the nightly reconciliation. apps/web is Next.js.

Status

In active build. The console has no product screens yet — every one waits on a design bundle.