finance

package
v1.801.460 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package finance is the prepaid wallet your org pays from: deposits in, usage debits out, always balanced.

It is a per-CUSTOMER, double-entry balance on the native ledger core (apps/treasury/ledger, the same engine the platform reserve posts to). It registers NO routes and NO ops — it is the in-process implementation of cloud's types.FinanceClient (package alias finance.Client, mirroring commerce.Client), the ONE money seam the ai prepaid gate, the admin grant, commerce's credit mint and the edge meter all bill through; billing is the customer-facing door onto it.

ONE LIGHTWEIGHT FILE PER ORG. Each org's books are an isolated Hanzo Base (SQLite) file at <dataDir>/orgs/<org>/finance.db (a separate <...>/finance-test.db for sandbox money, so test and live never mix). There is NO shared or relational database — the file IS the tenant boundary, so one org's wallet writes can never appear in another org's read. A file opens on first use and is cached; opens are serialized so a concurrent first touch opens exactly once.

MONEY-SAFETY. Every write is a BALANCED double-entry posting inside the org's own file: a deposit is funding:platform → wallet (credit the customer, debit the platform float); a usage debit is wallet → revenue:platform (debit the customer, credit platform revenue) — so every customer debit IS a platform-revenue credit in one atomic entry, and a file's postings always sum to zero. Both writes are idempotent on their ref (the ledger's (kind,program,ref) idempotency): a usage debit on RequestID and a deposit on DepositInput.Ref, so a retried debit or a fixed-ref backfill posts AT MOST ONCE; a deposit with an empty Ref takes a fresh ref and stays additive (grants stack). Amounts are int64 minor units (USD cents) — no float ever touches a balance. A balance read is the settled ledger balance, clamped at zero; transient holds are the caller's in-pod concern, never persisted here.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func MigrateOrg

func MigrateOrg(ctx context.Context, org string, balanceCents int64) (string, error)

MigrateOrg carries an org's existing prepaid balance into its native finance wallet, EXACTLY ONCE — the cutover primitive that moves each tenant's money from the legacy commerce ledger onto the ONE finance ledger. It deposits balanceCents into org's pooled wallet (subject == the org slug) under the FIXED idempotency ref "backfill:<org>", so a re-run (a retried operator call, a replayed job) finds the existing entry and credits the wallet at most once — returning the original entry id, moving no money. A non-positive balance is skipped ("" , nil): there is nothing to carry. It resolves the process-wide finance client published at boot; a nil client (finance not co-resident on this deployment) is an error, since there is no wallet to migrate into.

func New

func New(dataDir string) *ledgerFinance

New returns a finance client rooting each org's prepaid wallet ledger under dataDir, in that org's own namespace ("finance", or "finance-test" in sandbox mode). Files open lazily on first use.

func Publish

func Publish(c Client)

Publish records the process-wide finance client. Called once at boot; nil clears.

func SetUsageHook

func SetUsageHook(h func(org string, test bool, project, service string))

SetUsageHook installs the post-debit hook (the cap alert-fire). Pass nil to clear.

Types

type Client

type Client = types.FinanceClient

Client is the in-process inter-subsystem seam cloud's money paths call. It IS cloud's types.FinanceClient — one narrow interface (BalanceCents + Deposit + RecordUsage), kept as an alias so a value satisfies both names with no adapter (mirrors commerce.Client).

func Current

func Current() Client

Current returns the published finance client, or nil before Publish (a consumer treats nil as "money layer not co-resident" and fails closed).

type Kind added in v1.801.350

type Kind string

Kind classifies a wallet entry's economic meaning — money IN or money OUT — and is also the first half of the entry's idempotency key. Kept small and closed: two directions, two names.

IT IS THE ONE VOCABULARY, and it is a TYPE rather than a pair of strings because both halves of the money surface read it. The ledger WRITES these kinds and the customer's finance pages CLASSIFY on them, and while the reader held its own string literals the two silently disagreed: the reader matched commerce's `deposit` / `withdraw` against entries this file has always written as `finance.deposit` / `finance.usage`, so over the peer path a customer's credits rendered empty, their usage totalled zero, and their own top-up signed NEGATIVE. Nothing failed — the strings just never met. Named constants of a named type make that disagreement a compile error instead of a wrong number on a customer's balance page.

const (
	KindDeposit Kind = "finance.deposit" // funding:platform → wallet (a prepaid grant/settlement)
	KindUsage   Kind = "finance.usage"   // wallet → revenue:platform (a metered debit)
	// KindUnknown is what a kind the ledger never wrote parses to. A reader treats it
	// as neither direction rather than guessing one — an entry nobody can classify
	// must not be silently counted as spend.
	KindUnknown Kind = ""
)

func ParseKind added in v1.801.350

func ParseKind(s string) Kind

ParseKind reads a kind back off a wire — the internal plane's Txn.Kind, or a row re-read from an older store. It is the ONE place the ledger's spellings are recognized, so a reader never compares an entry to a string literal.

type TxnRow added in v1.801.350

type TxnRow struct {
	ID        string
	Kind      Kind
	Ref       string
	Memo      string
	Amount    money.Amount
	CreatedAt int64
}

TxnRow is one ledger entry as the billing surface reads it: every kind, not only the usage debits ListUsage keeps. Credits, grants and debits are all transactions to a customer looking at their account, and the three finance pages (credits/usage/ledger) are three projections of THIS one list.

Amount stays the ledger's own money.Amount — 18-decimal exact, the same integer an on-chain balance holds — because this is where that value lives. Flattening to cents here would round away everything below a cent, which is most of what a per-token AI price IS.

Kind is the ledger's OWN Kind, not a string: a customer-facing page decides whether a row is money in or money out by comparing it, and a reader holding its own string literals is how the credits page came to render empty against a ledger full of grants.

type UsageRow

type UsageRow struct {
	ID    string `json:"id"`
	Cents int64  `json:"cents"`
	// Amount is the debit exactly as the ledger holds it — the same 18-decimal
	// value the wallet→revenue posting moved. Cents beside it is the ROUNDING of
	// this value, kept for the wires that already carry cents; it is never the
	// source. TxnRow below has said why since it was written: "Flattening to
	// cents here would round away everything below a cent, which is most of what
	// a per-token AI price IS" — and this row, read from the SAME ledger, was
	// flattening. A customer whose usage was a thousand sub-cent calls read back
	// a page of zeros that summed to zero.
	Amount    money.Amount `json:"amount"`
	Model     string       `json:"model"`
	CreatedAt int64        `json:"createdAt"`
}

UsageRow is one recorded usage debit — the READ twin of RecordUsage. The SAME wallet→revenue posting a metered call wrote is read back here, so the usage a customer SEES is exactly what drained their wallet. Cents is the debit magnitude (USD minor units); Model is the metered-unit label the debit carried (Entry.Memo); CreatedAt is unix seconds.

Jump to

Keyboard shortcuts

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