billingaccount

package
v1.48.2 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package billingaccount is the top-level, tenant-independent money-of-record primitive. A BillingAccount is the unit balance is recorded against; entities (users, orgs, projects) BIND to accounts (Binding) so a debit walks an ordered chain and falls to the next account when one cannot cover it.

The account's stable string Id IS its ledger subject key: the append-only commerce ledger keys every row on transaction.SourceId/DestinationId, and a vaulted card keys on paymentmethod.CustomerId — so an account's money and its instruments are exactly the rows carrying its id. Balance is the derived sum over that ledger (models/transaction/util), never a mutable field here. This entity carries only administrative metadata (who administers it, display name, currency, upstream processor handles); it never stores an amount.

Index

Constants

View Source
const (
	HolderUser    = "user"
	HolderOrg     = "org"
	HolderProject = "project"
)

Holder/owner kinds — the ONE vocabulary shared by a binding's HolderKind and an account's OwnerKind, so the two can never name entity classes differently.

View Source
const BindingKind = "billing-binding"

BindingKind is the datastore kind for a holder→account binding.

View Source
const Kind = "billing-account"

Kind is the datastore kind for a billing account.

Variables

This section is empty.

Functions

func BindingID

func BindingID(holderKind, holderId, accountId string) string

BindingID derives the stable storage id for a (holderKind, holderId, accountId) triple. Same inputs → same id → a re-bind upserts onto the one row via the storage ON CONFLICT(id, kind, namespace), so the backfill is idempotent.

func BindingQuery

func BindingQuery(db *datastore.Datastore) datastore.Query

BindingQuery returns a datastore query over bindings in db's namespace.

func NewAccountID

func NewAccountID() string

NewAccountID mints an opaque, stable id for an account created without a pre-existing ledger subject to adopt. It is deliberately NOT derived from any tenant slug: an account is tenant-independent, so its identity must not encode where it happens to be bound today.

func Query

Query returns a datastore query over billing accounts in db's namespace.

Types

type BillingAccount

type BillingAccount struct {
	mixin.Model[BillingAccount]

	DisplayName string `json:"displayName,omitempty"`

	// OwnerKind/OwnerId name the entity that administers this account (who may
	// bind it, add instruments, read its ledger). It is the account's steward,
	// NOT a scoping key — the money is keyed on the account Id.
	OwnerKind string `json:"ownerKind"`
	OwnerId   string `json:"ownerId"`

	Currency currency.Type `json:"currency" orm:"default:usd"`

	// ProviderType/ProviderRef are the upstream processor's handle for this
	// account (e.g. a Square/Stripe customer), when one has been provisioned.
	ProviderType string `json:"providerType,omitempty"`
	ProviderRef  string `json:"providerRef,omitempty"`
}

BillingAccount is one account money is recorded against. Its Id is a free-form stable string that doubles as the ledger subject key. Accounts minted fresh take an opaque NewAccountID ("acct_…"); an account backfilled for an existing tenant instead ADOPTS that tenant's current subject string as its id, so the append-only ledger and vaulted cards carry forward with no migration.

func Get

Get loads the account with the given stable id from db's namespace, or returns datastore.ErrNoSuchEntity when absent. The read is key-exact (kind-qualified) so it round-trips on both the SQLite and Postgres backends.

func New

New returns an initialized BillingAccount bound to db.

func (*BillingAccount) Load

func (a *BillingAccount) Load(ps []datastore.Property) error

func (*BillingAccount) Save

func (a *BillingAccount) Save() ([]datastore.Property, error)

type Binding

type Binding struct {
	mixin.Model[Binding]

	HolderKind string `json:"holderKind"`
	HolderId   string `json:"holderId"`
	AccountId  string `json:"accountId"`
	Priority   int    `json:"priority"`
}

Binding maps a holder (a user, org, or project) to a billing account with a priority. A holder's bindings, ordered by ascending Priority (lower is charged first), are the ordered chain a debit walks — falling to the next account when one cannot cover the debit or its card is declined. A holder may bind many accounts (redundancy, personal overflow); an account may be bound by many holders.

The row id is DETERMINISTIC in (HolderKind, HolderId, AccountId), so re-binding the same pair upserts (e.g. to change Priority) rather than forking a duplicate — the property the idempotent backfill relies on.

func Bind

func Bind(db *datastore.Datastore, holderKind, holderId, accountId string, priority int) (*Binding, error)

Bind idempotently records that holder (holderKind, holderId) draws on accountId at the given priority, in db's namespace. Re-binding the same pair updates the priority in place (deterministic id + upsert). It returns the persisted Binding.

func ForHolder

func ForHolder(db *datastore.Datastore, holderKind, holderId string) ([]*Binding, error)

ForHolder returns holder (holderKind, holderId)'s bindings in db's namespace, ascending by Priority (the charge order). Ties break on AccountId so the order is TOTAL and deterministic. An empty holderId yields no rows. Ordering is done in Go, not via a datastore Order clause, so the money-critical charge order is identical on every backend (SQLite/Postgres/in-memory).

func NewBinding

func NewBinding(db *datastore.Datastore) *Binding

NewBinding returns an initialized Binding bound to db.

func (*Binding) Load

func (b *Binding) Load(ps []datastore.Property) error

func (*Binding) Save

func (b *Binding) Save() ([]datastore.Property, error)

Jump to

Keyboard shortcuts

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