# Tabula, the `subscription` module (plan IA §5, plan AF slice A3, plan TB)

Who holds what, where it runs, and what it costs. No column on `tenant` names any of it, and a test holds that.

## The tables

| Table | What a row says | Platform or scoped |
|---|---|---|
| `product` | What can be subscribed to. Its `code` is what webhooks and servers use; its rows start from a migration. Its `billing_state` is `complimentary` as shipped, until billing is switched on for it (TB-13); its `colour` is its app colour, seeded from the brand layer (`config/brand.php`, `product_colours`), for the pages that show it | Platform |
| `offering` | A product's Free or Standard, with its `price_month` and `price_year` in pesewas (none: not sold that way; 0: free; TB-1, TB-5). One offering a product `is_default`: where an organisation starts and what an unpaid one falls back to (TB-3), held by a partial unique index; it can't be taken off sale | Platform |
| `allowance` (`alw_…`) | A product's own word for a `limit` (members, with its `unit`) or a `feature` (timetables) (TB-10). Audited | Platform |
| `offering_allowance` | An offering's share of an allowance: a limit's `amount` (none: no limit), or a feature it includes. Tied to the product on both sides by composite keys | Platform |
| `entitlement` (`ent_…`) | This organisation holds this offering, in this state, with any agreed terms, for this term. One live row per product, by a partial unique index. Paid for, it also says how (`period`), whether a card renews it (`auto_renew`), and a smaller plan or shorter period chosen for its next period (`next_offering_id`, `next_period`, an offering of the same product by a composite key) | Scoped to the organisation |
| `instance` (`ins_…`) | This entitlement runs as this tenant on this host. One per entitlement; one live row per host | Scoped to the organisation |
| `entitlement_usage` | The count a product last reported against one of its limits (`used`, `reported_at`) | Scoped to the organisation |
| `agreed_price` (`agp_…`) | A price agreed with one organisation for one offering, `until` a date, with a note (TB-1, TB-4). After the date, the list price. Audited | Platform data naming its organisation, set by the Service |
| `tax_rate` | A tax on a subscription from a day: NHIL 2.5%, GETFund 2.5% and VAT 15%, each on the price before tax (TB-5). Dated, so a change in the law is a new row from its day. Seeded from Act 1151, to be confirmed before billing is switched on | Platform |
| `billing_payment` (`pay_…`) | A payment for a plan (TB2): the entitlement and offering it buys, monthly or yearly, card or mobile money, the `quote` it was started from kept as quoted (price, credit, each tax with its rate, period), the `amount` in pesewas, and its `status`: `pending`, `paid`, `failed` or `abandoned`. Its `reference` is Paystack's key for it, unique everywhere. What Paystack reported is kept encrypted (`verified`) | Scoped to the organisation |
| `billing_notice` | What an organisation was told about a period (TB3): a reminder so many `days` before it `ends_on` (`due`), the start of its grace days (`unpaid`), or its move to Free (`free`). One row each, so each is told once; the Billing page draws them on the ribbon | Scoped to the organisation |
| `billing_invoice` (`inv_…`) | The paper for a paid payment (TB4), or since TB8 a credit note against one (`kind` `credit`, `credits_id`, `refund_id`, its own `lines`; see Money back): `number` (`INV-2026-0001`, from `billing_invoice_counter`, a gapless count a year, platform-unique), `issued_on`, `parties` (the seller and the organisation as they stood, kept as issued), `what`; its lines are the payment's quote. `evat_state` `waiting` (a receipt) or `cleared` (a tax invoice, with the GRA's `evat_number`, `evat_signature`, `evat_qr`, `evat_sdc`, `cleared_at`); `tries`, `last_error`, `service_told_at` | Scoped to the organisation |
| `billing_withholding` (`wht_…`) | What a school withheld from a payment (TB-15): `due_on` (`subscription.certificate_days`, 30), the certificate (`file_id`, `received_at`, `received_by`), `lapsed_at` when the Service was told. The rate and amount are on the payment's quote | Scoped to the organisation |
| `billing_launch` (`lch_…`) | Billing switched on for a product (TB5, TB-13), one a product: `announced_on`, `charges_from` (at least `subscription.notice_days`, 60, ahead), `founding_percent`, `announced_by`, `started_at` (the products told on the day). Audited | Platform data |
| `tax_rate` (since TB4) | Also `kind` (`charge` or `withholding`) and `over` (a withholding offered only on a price above it): `wht` 7.5% over GH₵ 2,000, `wvat` 7% | Platform data |
| `paid_period` (`per_…`) | One per paid payment: from when to when it ran (`starts_on`, `ends_on`, up to but not including the end), and how it ended (`changed` when moved up from, `renewed` and `lapsed` with TB3, `refunded` with TB7: ended, or never run, because its payment was refunded). What was bought is its payment's | Scoped to the organisation |
| `billing_refund` (`rfd_…`) | Money going back on a paid payment (TB7; TB-17 to TB-21), part or all, never more than is left of it: its `reference` (ours, `rf_…`, platform-unique: Paystack takes a reference once) and Paystack's own number (`gateway_id`); `origin` (`atrium`, asked from the console with a `reason` from Input Fields, or `gateway`, made in Paystack's dashboard and found, with `chosen_at` empty until the Service chooses); its `effect` on the plan (`keep`, `end` or `undo`, code); its `status` (`asked` until Paystack has taken it, then Paystack's: `pending`, `processing`, `attention` when it needs the payer's details, `processed`, `failed`); a payout sent by hand (`payout_channel` from Input Fields, `payout_account`, `payout_transaction`, `payout_on`, `payout_by`); `told_at` and `service_told_at`, so each is told once | Scoped to the organisation |
| `billing_dispute` (`dsp_…`) | A bank's dispute of a payment (TB9): Paystack's number, kind, reason, amount, when it must be answered, Atrium's status (`open`, `answered`, `won`, `lost`), the answer and who gave it, and the refund it led to when lost (see Money back) | Scoped to the organisation |
| `billing_card` (`crd_…`) | A card Paystack says can be charged again, kept for renewals (TB3): its authorization encrypted and never shown, its brand and last four digits. One active an organisation | Scoped to the organisation |
| `billing_term` (`btm_…`) | An organisation's own billing on a product (TB-16): `charged` from a day, with `choose_by`, or `complimentary` for good, whatever the product does; `agreed_with` is required. No row: it goes as its product. Audited | Platform data naming its organisation, set by the Service |

## Who pays, and what

`App\Modules\Subscription\Support\Billing` answers it. An organisation's billing on a product is its own terms when it has
them, else the product's: `complimentary` or `charging`. A charged term counts from its `charged_from`. Its price for an offering is
the agreed price while that holds, else the list price.

`EntitlementView` is what a product is told: the offering, `ends_on`, `complimentary`, `choose_by`, `grace_until` (TB3: while a period goes unpaid, the day the plan is kept until), `limits`, `features` and `used`. A product never drops a plan on its own clock: it keeps the last view it had until Atrium tells it otherwise.
Complimentary means all of it: every feature, and no limit.

## Actions

| Action | What it does |
|---|---|
| `GrantEntitlement` | Records the grant on the day, ending after `subscription.term_months` (12 by default; a shorter month's end stays in that month). Refuses a second live offering of one product and an offering switched off. Audited. After commit it raises `entitlement.granted`: the Owners are told, and the product hears by webhook, with the whole view |
| `RecordInstance` | Records where an entitlement runs, once its product has made its tenant. Refused for what's no longer held |
| `SaveOffering` | Makes or changes an offering: name, prices, on sale, the default (moving it from another), and its share of each allowance. Every organisation holding it hears what it allows by webhook. A new price applies from each organisation's next renewal (TB2) |
| `AddAllowance` | Adds a product's limit or feature. A feature has no unit |
| `SaveAgreedPrice` | One agreed price an organisation and offering; saving again changes it |
| `SetBillingTerm` | As the product (the row goes), charged from today with `subscription.grace_days` to choose a plan (7 to start), or complimentary for good. Charged again keeps its first day. The product hears by webhook; an organisation charged from today has its owners and billing people told (`billing.charged`) |

## Paying (TB2)

Paystack takes the payment on its own page, held to card or mobile money (plan IA §6: no provider script on `id`). In
`App\Modules\Subscription\Payment`:

| Class | What it does |
|---|---|
| `Gateway` | The contract: start a checkout, say what became of a transaction (asked server to server), charge a saved card (TB3: paid, failed or unknown, since TB6; only a definite decline fails a try), and say whether payments can be taken. `config/billing.php` picks the driver: `log` everywhere but a server, `paystack` there (`BILLING_GATEWAY`, `PAYSTACK_SECRET_KEY`) |
| `PaystackGateway` | Paystack's `transaction/initialize`, `transaction/verify` and `charge_authorization`. The key is in `.env` alone |
| `LogGateway` | Nothing leaves the machine, and nothing is taken or confirmed in production (TB6). Its checkout is Atrium's own page (`billing.dev.checkout`, a signed link, never in production), where a payment is paid, declined, left waiting on a phone, or left |
| `Quotes` | Prices a choice. Since TB6, any plan paid while a period is unpaid in its grace days continues from where that period ended; a move whose credit is worth more than its price is refused; and a quote with nothing to pay is refused. The plan chosen for the next period, or the same again, from the first reminder before the period ends, or while it's unpaid and not yet lapsed, is a renewal (TB3): it starts where the period it renews ends, at today's price and rates, nothing credited. A bigger plan can't be bought while the next period is paid ahead. A plan where none is paid starts today; a bigger one (dearer by the day, or the same plan yearly instead of monthly) starts today with the unused days of the period it replaces credited before tax, in whole days (TB-12); a smaller one is chosen for the next period instead. Only a product the organisation is charged for, and an offering on sale at a price for that period |
| `Tax` | Each tax on the price less any credit, rounded to the pesewa on its own line, at the rates of the day |
| `Checkout` | Keeps the quote on a pending payment under its own reference, then sends the payer to the gateway's page. Since TB6 it lets go of any other open checkout for a plan change on the entitlement first |
| `Settlement` | Settles a payment once (since TB6: the webhook's report also re-checks a payment closed failed or abandoned, so money taken late is settled; and a payment whose quote no longer holds, its `replaces` not the period running or a period paid ahead since, is kept as paid with nothing applied and the Service told to refund it, `billing.payment_unapplied`): Paystack is asked, never trusted from the request; the amount and currency are checked; the payment's row is locked. Paid: the period running ends (`changed`) and a new one starts today (a renewal continues the period it renews, which ends `renewed`; paid after it lapsed, today), the entitlement moves to the plan until the period's end, a reusable card is kept, the products hear (`entitlement.changed`), and the owners and billing people are told (`billing.paid`). Declined: nothing changes. Still under way: pending |

Three things can settle a payment, and whichever comes first does: the payer's return (`/billing/payment/{reference}`), Paystack's
webhook (`POST https://<HOST_API>/hook/paystack`, signed with the secret key as an HMAC-SHA512 of the body, limited by
`payment-report`), and `billing:check` every ten minutes, which also lets a payment still pending after `billing.pending_hours`
(24) go as `abandoned`. A test sends all three at one payment and finds one period.

## Terms that end

`entitlements:roll`, nightly at 01:10, rolls on an entitlement whose term has ended when its offering is free or its billing is
complimentary: another term from the day the last ended, catching up any it missed, nobody told, and the product hears by webhook
(TB-8). A paid, charged term that ends waits for its renewal (TB3).

## Renewals and lapses (TB3)

`billing:renew`, every morning at 06:15 (`Renewals`), decides each step from what's recorded, so it is safe to run again and a
morning missed is caught up the next. For each active entitlement an organisation is charged for:

| When | What happens |
|---|---|
| A period paid ahead starts | The entitlement follows it: its offering and period (a smaller plan chosen for it, TB-12). The products hear |
| A period paid by hand ends soon | Mobile money, or a card that doesn't renew itself, is reminded `subscription.reminder_days` before (14, 7, 1; TB-9): `billing.renewal_due`, once each. After days Atrium was down only the nearest is sent |
| The period ends, with a card that renews itself | The saved card is charged for the next period (the plan chosen for it, else the same): a `renewal` payment, settled as any other. Declined, it's tried again after `subscription.retry_days` (2), up to `subscription.renewal_tries` (2) tries; one left unanswered and abandoned counts as one |
| The period ends unpaid | `grace_until` is set to today plus `subscription.grace_days` (7, TB-11), counted from the day Atrium tells the organisation (`billing.unpaid`, with the card's reason when it was declined), so time Atrium was down never counts against it. The products hear `grace_until` |
| The grace days end unpaid | Free (TB-3): the period ends `lapsed`, the entitlement holds the product's default offering from today for a term that renews itself, nothing is deleted, the products hear, and `billing.moved_to_free` is sent |
| Free was chosen for the next period | Free on the day, the period ending `changed`; nobody is asked for money or told |
| Charged from today, no plan chosen by `choose_by` | Free once (TB-16), and `billing.moved_to_free` |

A paid entitlement whose product has no free default keeps its plan, and the error is logged.

## Switching billing on (TB5)

`Billing::state` is charging from a launch's day (`Billing::productState` for the product alone). `Launches` (`App\Modules\Subscription\Launch`):

| Piece | What it does |
|---|---|
| `readiness` | The five checks, each with what stands: `prices` (every paid offering on sale priced), `vat` (`billing.seller.tin` and `vat_number`), `evat` (the E-VAT driver's health), `gateway` (the payment gateway's health), `terms` (confirmed on the form) |
| `launch` | Refuses while any check fails, a launch exists, or the day is under the notice; then records it and queues `AnnounceLaunch` |
| `AnnounceLaunch` | On the bulk queue: every active entitlement to the product, except organisations complimentary for good, gets `billing.launched` once (`billing_notice`, kind `launch`) |
| `start` | Run first by `billing:renew` each morning: on the day, every entitlement to the product is told (`entitlement.changed`) and `started_at` set |
| `cancel` | Before its day only |

After the day, `Billing::chooseBy(Entitlement)` gives an organisation that has never paid the grace days after the day to choose, or, on a paid
offering it was given complimentary, the end of its term if later; `Renewals` moves it to Free then (TB3). `Billing::founding` gives the
founding price to an organisation holding the product on the announcement day while none of its paid periods has lapsed; `Billing::price` takes
it off the list price (an agreed price wins), and Plans and Pay show the list price struck through.

The console's Billing shows the money at its top (`BillingController::money`: paid this month with the nine before, periods renewing in 30 days,
invoices waiting on E-VAT, certificates open), and each complimentary product's Switch billing on (`console.billing.launch`, the password asked
again to switch it on or call it off).

## Invoices and withholding (TB4)

In `App\Modules\Subscription\Invoice`:

| Class | What it does |
|---|---|
| `Invoices` | Issues the invoice inside the settlement's transaction, numbered under a lock on the year's count, and opens a `billing_withholding` when the quote withheld something |
| `Evat` | The contract: clear an invoice (the GRA's number, signature, QR, SDC and time) or refuse (`EvatRefused`), and say whether it can. `config('billing.evat')`: `log` (`LogEvat`: clears at once with made-up marks, never in production; `refuse()` in tests) or `gra` (`GraEvat`: refuses until the GRA's access and specification come; written from the specification, not guessed) |
| `Clearance` | Asks E-VAT outside the lock, keeps the answer under it once; refused, counts the try with its words, and after `billing.evat_alert_tries` tells the Service once (`billing.evat_failing`) |
| `ClearInvoice` | The queued job the settlement sends at once, in the invoice's organisation; `billing:clear` sends every waiting invoice again each hour |
| `InvoicePdf` | The paper as the page shows it (`paper()`) and its A4 PDF (dompdf, `resources/views/billing/invoice.blade.php`), with the QR drawn from the GRA's answer |

Withholding (TB-15) is chosen at checkout (`Quotes::withhold`, one of today's `withholding` rates whose `over` the price passes, worked on
the price after any credit and before tax): the quote keeps it, the payment is the total less it, and the period starts on what was paid.
`billing:certificates`, every morning at 07:00, tells the Service once of a certificate past its day; the plan is never taken away.

At `id`, Invoices (`billing.invoice`, `InvoiceController`): the year (invoiced, tax, withheld, month by month, where the money went) and a
slip for each paper; one paper with where its money went, its clearance or the rail while it waits, the certificate's drop and its
countdown, the PDF, and sending it to an address (a signed link, `billing.invoice.shared`, for `billing.shared_days`, limited by
`invoice-send`); a year as a CSV. Pay offers what the school deducts, and the outcome links to the receipt.

## Money back (TB7)

Plan TB §7 (TB-17 to TB-22). `App\Modules\Subscription\Payment\Refunds`:

| Method | What it does |
|---|---|
| `remaining` | What's left to give back: what was paid, less every refund that hasn't failed |
| `options` | The effects open to a payment now, each with the most it may give back. Keep: up to what's left, nothing suggested. End the period now: while its period runs or is still to come, the unused days pro rata of what was paid, or all of it within `subscription.refund_days` (14) of paying or for a period not yet started. Undo: while its period runs or is still to come, or for a payment that bought nothing (set aside, TB6), all that's left |
| `ask` | Records the refund and applies its effect under a lock on the payment, so two people can't give back more than was paid; then asks Paystack, outside the lock |
| `send` | Asks Paystack to take a refund still `asked`, under its own reference: a reference is taken once, so asking again after a timeout finds the refund already made (Paystack's 409) |
| `reconcile` | Brings a payment's refunds in line with what Paystack holds: each status moves on, never back from done and never over a payout recorded by hand; one Atrium didn't ask for is recorded as `gateway`, keeping the plan, and the Service is told |
| `check` | The failsafe, run by `billing:check` every ten minutes: refunds still `asked` are asked again, those under way are asked about, and refunds made in Paystack's dashboard in the last three days are found |
| `choose` | A refund found in Paystack's dashboard: keep, end or undo, once |
| `paidByHand` | A refund that needs the payer's details, or failed, sent by hand and recorded (TB-21) |
| `retry` | A refund that failed, asked again under a new reference |

What an effect does (TB-18): keep changes nothing; end ends the period today (`refunded`) and moves the organisation to Free,
nothing deleted; undo, or ending a period not yet started, ends the period at its start, and the period it replaced (moving up) or
renewed (paid ahead) carries on when it still runs, the organisation's plan put back as it was; otherwise Free. A payment that bought
no period changes nothing. Products hear of the change as of any other. `App\Modules\Subscription\Support\FreeOffering` is the move to
Free that renewals and refunds share.

The gateway (`Gateway`) gained `refund` (Paystack's `POST /refund`, with `transaction`, `amount`, `currency` and Atrium's `reference`),
`refundsOf` (`GET /refund?transaction=`, by the payment's transaction number) and `refundsSince` (`GET /refund?from=`). The stand-in
processes a refund at once; tests set another outcome (`refundOutcome`), move one (`moveRefund`) or add one made in "Paystack's
dashboard" (`dashboardRefund`). Paystack's webhook acts on any `refund.*` event by asking Paystack about the payment it names
(`data.transaction_reference`), never trusting the body.

Told: the organisation's owners and billing people once the money has gone (`billing.refunded`: how much, where to, what became of
the plan); the Service when a refund is stuck (`billing.refund_stuck`) or one made in Paystack's dashboard is found
(`billing.refund_found`), each once. **Chargebacks (TB9; TB-22).** `billing_dispute` (`dsp_…`): a bank's dispute of a payment, by Paystack's number (`gateway_id`,
platform-unique), its `category` and `reason` as Paystack gives them, the `amount`, and `due_at` (16 hours at Paystack, which accepts it by
itself after). Its `status` is Atrium's: `open`, `answered` (with its `answer`, `defend` or `accept`, the `message` to the bank, who and
when), then `won` or `lost`. `App\Modules\Subscription\Payment\Disputes`: `reconcile` follows a payment's disputes from Paystack (the
webhook acts on any `charge.dispute.*` event by asking Paystack about the payment named in `data.transaction.reference`); `check`, run by
`billing:check`, asks about every dispute not yet decided and finds those raised in the last week; `answer` defends (the payment's
invoice as a PDF goes to Paystack's signed upload link, then resolves as `declined` with the file's name; on a fraud claim, who paid and
what for as evidence) or accepts (`merchant-accepted`, the whole payment). A dispute resolved as `merchant-accepted` or `auto-accepted`, or
with money given back and not `declined`, is lost: `Refunds::recordLoss` records the money as a refund of `origin` `dispute`, already
processed, which ends the period while it runs (or keeps the plan when that period was already over), gets its credit note, and isn't told
as a refund; the saved card with the payment's signature is dropped; the organisation hears `billing.dispute_lost` once. The Service hears
`billing.dispute_opened` once. Console: open chargebacks as clocks under Answer first, a drawer with the evidence and the answer, others
listed under Chargebacks (`?dispute=`). Since the second review (TB9): a refund is refused while a bank disputes the payment; a failed
refund recorded as paid by hand, or asked again, must fit in what's left; a lost dispute is credited only what was left, and accepting one
sends only what's left; rows Paystack lists are taken only when they're the payment's own; an outcome Atrium doesn't read is left answered;
the check issues any credit note an error left out.

**Credit notes (TB8; TB-20).** A credit note is a `billing_invoice` of `kind` `credit`: issued by `Invoices::credit` once a refund's
money has gone (Paystack's `processed`, or a payout recorded by hand), numbered `CN-2026-0001` from its own gapless count
(`billing_invoice_counter` is keyed by `kind` and year; `billing.credit_prefix`), against the invoice it adjusts (`credits_id`), one a
refund (`refund_id`), with that invoice's parties. Its `lines` are kept as issued (`Invoices::reversed`): the invoice's price after credit and
each tax, scaled to what's credited; for a school that paid net of withholding, what's credited is the refund plus its share of what was
withheld (`withheld`), so `returned` is the refund exactly; and how and when it went back. A payment with no invoice (set aside, TB6)
gets none. It's sent to E-VAT at once by `ClearInvoice` and every hour by `billing:clear`, as an invoice is, and the Service is told the
same way when it keeps failing. At `id` the invoice carries a banner with the refund's track, a *Refunded* or *Part refunded* stamp, and its
credit notes beneath; a credit note opens on its own page (`/billing/invoice/CN-…`), prints as a PDF, counts in the year (`credited`) and
fills a row of the spreadsheet below nothing. One invoice a payment is now a partial unique index (`kind = 'invoice'`).

## Withholding's review (TB10)

Plan TB §8 (TB-23 to TB-25, decided under the Architect's autonomy grant). `billing_withholding` gained `accepted_at`, `rejected_at` with
`reject_reason` (Input Fields, `certificate_reject_reason`), `reviewed_by`, `told_owed_at`, `balance_payment_id` and `settled_at`; its
`state()` is `waiting`, `received` (to review), `owed` (rejected, or lapsed with no file), or `settled`. `App\Modules\Subscription\Invoice\Withholdings`:
`received` (a certificate sent, or sent again after a rejection or its day), `accept` (settled), `reject` (owed, the organisation told),
`tellOwed` (once each time, also from `billing:certificates` when none came). Paying what's owed is a payment of `purpose` `balance`
(`Checkout::balance`: the withheld amount, no tax, by card or mobile money, back to the invoice with `?paid=`); `Settlement` settles it
once against its withholding, and one paid after the withholding was settled another way is kept and the Service told to refund it
(`billing.payment_unapplied`). While an organisation owes, `Quotes::withhold` refuses and the pay page offers no withholding (TB-25).
Console: Billing → Withholding certificates (`WithholdingController`, `certificate.review` with the password to accept or reject).

## SMS credit bought (plan NU, NU2)

A payment of purpose `texts` buys a pack of SMS credit (`sms_pack`, priced by the Service at `/console/billing/texts`): it names no
entitlement, offering or period, and the database holds that shape (`billing_payment_texts_chk`). `Checkout::texts` quotes the pack's
price and each tax as for a plan (`Tax::lines`); Paystack's page takes it; the return to `/billing/texts?paid=` and the webhook and check
settle it, and `Settlement::textsPaid` credits the texts once (the credit line's unique index on the payment) and issues the invoice
(`5,000 texts of SMS credit`; `Invoices::product` gives its pages, PDF and spreadsheet the product `texts`), then `notify.credit_bought`.
Money back on a texts payment offers only keeping the texts (the Service takes texts back by hand if they should go); a chargeback lost
takes its share of the texts back (`Disputes::lose`, a line with reason `chargeback`). A pack's price is fixed once something was bought
with it: it's taken off sale and a new one added.

## Who sees it

The console's Billing (`offering.view`, `offering.manage`; the Service), and its Money back (TB7; `offering.view` to read, `refund.manage`
to give money back, choose, record a payout or ask again, each with the password again; `MoneyBackController`, the manual's 1.16 to
1.18): each product as a ladder of offerings, an offering beside a
preview of how organisations see it, agreed prices, and an organisation's own billing. Saving an offering, an agreed price or an
organisation's billing asks for the password again. At an organisation, `billing.view` and `billing.manage` belong to its owners and
the billing role, never its admins (CL-16). At `id` (TB2), Billing is their menu item: each product's plans as a staircase, Pay with
its ticket, and the outcome back from Paystack (`routes/billing.php`; the manual's `s21-billing`). Since TB3 the menu item opens the
organisation's Billing (`SubscriptionController`): what it pays in a year, tax included, and what's due next, the most pressing first
(moved to Free, a card declined, the grace days, a plan to choose, a period ending, then the next payment); each product's year as a
ribbon that narrows to the weeks that matter when something is due; the card and mobile money it pays with; and who sees its bills. A
card can stop renewing, and start again (`billing.plan.renewing`). A billing-only person sees their part instead of the people.

The products' calls are in `api.md` (`GET /v1/entitlement`, `PUT /v1/entitlement/usage`).
