# Usage & billing

> How the tunnel wallet works: metered egress, the free tier and per-GB price, ledger kinds and deltas, top-ups, and reconciling debits against usage counters.

The **Usage & billing** screen shows both halves of the tunnel's money
flow on one page: metered egress on the left, the wallet ledger on the
right. Egress is what you consume; the ledger is the record of what that
consumption cost and what you have paid in.

The screen is backed by two procedures:

| Procedure                  | Input          | Feeds                                                        |
| -------------------------- | -------------- | ------------------------------------------------------------ |
| `adminTunnel.overview`     | (none)         | Price, free allowance, billing period, per-account egress.    |
| `adminTunnel.ledger`       | `{ limit }`    | Wallet entries — the console requests the latest 100.         |

## The wallet model

Tunnel billing is **prepaid**. You add credit to a wallet, and metered
egress is debited against it. There is no invoice to settle at the end
of the period; the balance is the running sum of everything the ledger
has recorded.

Two flows move money:

- **In** — top-ups, taken through Razorpay, posted to the ledger as
  positive deltas.
- **Out** — metered egress past the free tier, posted as negative
  deltas.

The ledger table (`rnp_wallet_ledger`) is the authoritative money trail.
The egress panel is a *usage* view: it mirrors the same counters that
the tunnel daemon (`rnpd`) bills from, so what the screen shows is what
gets debited — but if the two ever disagree, the ledger is the record of
account.

All amounts in both procedures are integers in the **minor unit** of the
currency (`delta_minor`, `pricePerGbMinor`). The console formats them
for display. The currency is resolved in this order: the ledger's
`currency`, then the overview's `currency`, then `INR` as a last
fallback — so a ledger response that carries no currency is still
rendered consistently with the header.

## How egress is counted and attributed to an account

Usage is attributed per account. Each row of `overview.topUsers` carries:

| Field         | Meaning                                                        |
| ------------- | -------------------------------------------------------------- |
| `sub`         | The account identifier. The console renders a shortened form.  |
| `email`       | Display label, when known.                                     |
| `handle`      | The account's tunnel handle, shown under the label when set.    |
| `egressBytes` | Egress for this account, in bytes, for the current period.      |

The counters live in Redis and are the same keys `rnpd` reads when it
produces debits. That is the point of the panel: it is not a separate
estimate computed for the UI, so you can read a number here and expect
it to be the basis of the corresponding ledger entry.

The billable-egress table **hides rows whose `egressBytes` is zero**. An
account that has connected but moved no bytes this period will not
appear. If no account has non-zero egress, the panel shows an empty
state instead of a table of zeros.

Egress is scoped to the billing period reported by `overview.period`,
which the console prints in the page subtitle. Counters are per period,
so the panel answers "what has this account used *this* period", not
lifetime usage.

## The free tier and the per-GB price

The overview returns the two numbers that define the price curve:

- `freeGb` — the allowance, in GB, before anything is billable.
- `pricePerGbMinor` — the per-GB price in minor currency units, applied
  past that allowance.

The console renders them together in the subtitle: *price/GB after N GB
free*. Both values come from the control plane on every load, so read
them from the screen rather than assuming a figure — they are not
hardcoded in the UI and this page deliberately does not restate them.

Because the allowance is expressed in GB and the counter in bytes, the
conversion from `egressBytes` to a currency amount happens in `rnpd`
when it writes the debit. The screen does not perform that arithmetic
and does not display a projected charge. If you want to know what an
account has actually been charged, read its ledger entries.

## Ledger entries: kinds, deltas, and references

Each entry in `ledger.entries` has this shape:

| Field         | Meaning                                                                 |
| ------------- | ----------------------------------------------------------------------- |
| `id`          | Entry identifier.                                                       |
| `created_at`  | When the entry posted.                                                  |
| `sub`         | The account the entry belongs to.                                       |
| `kind`        | What caused the entry.                                                  |
| `delta_minor` | Signed amount in minor units. Positive credits, negative debits.        |
| `ref`         | Free-form reference to the cause. Rendered as `—` when empty.           |

Kinds the console styles explicitly:

| Kind     | Direction | Rendered as   |
| -------- | --------- | ------------- |
| `topup`  | Credit    | Success tag   |
| `egress` | Debit     | Warning tag   |
| anything else | either | Neutral tag   |

The kind set is not closed. Any other kind the control plane emits — an
adjustment, a correction — still renders, with a neutral tag, and still
counts toward the balance through its `delta_minor`. Do not infer
direction from the kind: the **sign of `delta_minor`** is what decides
whether an entry adds to or subtracts from the wallet, and it is what
drives the `+` prefix and the colour in the Δ column.

Entries are returned newest-first and the console asks for the latest
100. The screen is therefore a recent-activity view, not a full
statement; a long history extends past what this page shows.

## Top-ups and payment references

A top-up is a positive-delta entry of kind `topup`. Because top-ups are
taken through Razorpay, the entry's `ref` is the handle you use to tie a
ledger credit back to the payment that produced it. The ledger does not
constrain the reference's format, and the console prints it verbatim
(truncated with an ellipsis when it is too wide for the column), so
treat it as an opaque string to match on rather than to parse.

The same column carries the reference for debits, which is what lets you
trace a specific `egress` entry back to the metering write behind it.

## What the screen tells you when the balance runs out

The ledger records money movement; it does not itself gate traffic, and
this screen exposes no enforcement control, no threshold and no
cutoff toggle. What it gives you is the trail: credits in, debits out,
and the reference for each.

Practically, that means the page is a diagnosis surface, not a lever. If
egress debits have consumed the credits, the fix visible from here is a
further top-up, which will appear as a new `topup` entry once it posts.

## Reconciling the ledger against usage counters

The two panels are deliberately independent reads of the same
underlying facts, which makes reconciliation possible:

1. Take an account's `egressBytes` from the billable-egress panel.
2. Find that account's `egress` entries in the ledger for the same
   period (match on the shortened `sub`, then confirm with the full
   `sub` from the overview row).
3. Compare. The usage panel reflects the Redis counters `rnpd` bills
   from, so the debit should follow from the same bytes.

A mismatch means one of two things: a debit has not yet posted for usage
already counted (usage leads the ledger, since debits are written by
`rnpd` after metering), or the period boundary differs between the two
views. Check `overview.period` before concluding anything from a
timestamp near a period edge.

When they disagree and both are current, the ledger wins. It is the
money record; the counter is the input to it.

## Reading the empty and error states correctly

On a billing page, a zero is a claim. The screen is built so that a
failed fetch can never be mistaken for a quiet period:

| What you see                                                    | What it means                                              |
| --------------------------------------------------------------- | ---------------------------------------------------------- |
| *Loading usage…*                                                | The overview request is in flight.                          |
| *Billing unavailable*                                           | The overview returned no data. Nothing about usage is known — do not read this as zero usage. Refresh, and check status if it persists. |
| *No billable egress yet*                                        | The overview loaded and no account has non-zero egress this period. |
| *Loading ledger…*                                               | The ledger request is in flight.                            |
| *No ledger activity*                                            | The ledger loaded and is empty. No top-ups or debits have posted. |
| *Ledger unavailable*                                            | The ledger could not be read. Refresh to retry.             |

If the overview is unavailable, the whole page collapses to that single
state — the ledger panel is not shown alongside it, because a money page
that renders half its numbers invites the wrong conclusion about the
other half.
