billing

package
v1.801.307 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 28, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

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

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount registers the customer-facing /v1/billing/* read surface on app.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL