Documentation
¶
Overview ¶
Package billing mounts the CUSTOMER-facing, org-scoped billing surface (/v1/billing/{usage,balance,gpu-eligibility,gpu-charge,payment-methods}) on the unified cloud binary.
WHY THIS EXISTS. On the console host (console.hanzo.ai) the ingress routes /v1/* straight to cloud-api:8000 — the console's Next BFF is reached only at "/". So the console's /v1/billing/usage + /v1/billing/balance calls land HERE, on cloud-api, NOT on the console's per-tenant commerce proxy. cloud-api previously wired commerce billing ONLY under the admin-gated aggregate (clients/admin, /v1/admin/*), so a normal org owner (e.g. davelorenzini / maxpower) hitting /v1/billing/usage had NO customer route and was denied — the "Access required" wall on every product overview + o11y usage panel. This adds exactly the customer surface those calls need.
TENANT ISOLATION (the whole point). The org is the VALIDATED IAM owner claim (principal.Org — the trusted X-Org-Id the identity middleware minted from the caller's verified session/bearer, HIP-0026; NEVER a client-supplied header). A customer therefore reads ONLY their OWN org's ledger. The commerce billing subject is pinned server-side to that org and NO client-supplied subject/org query param is ever forwarded, so the browser cannot widen scope. This is the per-org READ twin of the admin god-view (clients/admin) — the SAME commerce S2S machinery, but scoped to the caller instead of all-orgs (which stays admin-only).
SUBJECT. Prepaid balance is per-ORG: commerce keys the wallet under the BARE org slug as the `user` subject + the trusted `X-Org-Id` (admin.orgSubject / metering identityFromCtx — verified live: user=<org> + X-Org-Id=<org> returns the real wallet, "org/user" reads an empty one). The gateway debits this SAME key, so a read here shows exactly what the org is charged.
PASSTHROUGH. The console's normalizeUsageRecords parses commerce's RAW per-request ledger ({usage:[{transactionId,amount,metadata,createdAt}]}); balance is the raw {balance,holds,available} cents object. So this proxies commerce's body + status VERBATIM — it never reshapes or rolls up (the rollup is the admin aggregate's job).
finance.go mounts the customer-facing, org-scoped /v1/finance/* PROJECTION of the commerce billing plane — the data the finance.hanzo.ai app shell and the console Finance module (both render the SAME @hanzo/finance-ui components) consume.
WHY IT LIVES IN THE BILLING PACKAGE. These endpoints do not add a billing system; they PROJECT the one that already exists. The commerce wallet (prepaid balance + the deposit/withdraw transaction ledger + saved cards) is the single source of truth for what a customer holds and spends; /v1/finance/* is a per-org read-only VIEW of it in the shape the finance UI expects. This package already owns the commerceProxy + the per-org subject-pinning (the whole tenant-isolation argument in billing.go), so the finance reads reuse that machinery verbatim rather than standing up a second commerce client — one and only one commerce read path.
DIVISION OF THE /v1/finance/* SURFACE. The surface has two data planes:
- the CUSTOMER's commerce wallet — balance, credits, usage, invoices, payment-methods, ledger — mounted HERE (commerce-projected), and
- the PLATFORM's reserve fund — treasury — mounted in clients/treasury (the ledger-of-record it owns).
They compose on the same app under one prefix; the routes are disjoint so there is no collision. The treasury lane already serves GET /v1/finance/treasury (+ the admin reserve mutations); this lane adds the six commerce-projected reads.
TENANT ISOLATION. Identical to the /v1/billing/* reads: the org is the VALIDATED IAM owner (principal.Org), the commerce billing subject is PINNED server-side to it on every subject key, and NO client-supplied subject/org is forwarded — so a caller reads ONLY its OWN org's wallet and can never widen scope.
SHAPE. Unlike the /v1/billing/* passthrough, these RESHAPE commerce's raw wire into the typed finance contract (USD cents, optional-safe), because the finance UI's shape differs from commerce's (e.g. commerce `holds` → pendingCents; a withdraw → a signed ledger posting). The reshape is the whole value this lane adds over the raw ledger.
HONEST GAPS. There is no per-org customer-invoice ledger in commerce today (it is a prepaid wallet: deposits + withdraws, not issued invoices), so /v1/finance/invoices returns an honest empty typed array rather than a fabricated figure. It becomes real the day an invoice ledger exists — the shape is already stable for the UI.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
This section is empty.