ledger

package
v1.0.0-beta.228 Latest Latest
Warning

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

Go to latest
Published: May 19, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const (
	AnnotationChargeNamespace     = "ledger.charge.namespace"
	AnnotationChargeID            = "ledger.charge.id"
	AnnotationSubscriptionID      = "ledger.subscription.id"
	AnnotationSubscriptionPhaseID = "ledger.subscription.phase.id"
	AnnotationSubscriptionItemID  = "ledger.subscription.item.id"
	AnnotationFeatureID           = "ledger.feature.id"

	AnnotationTransactionTemplateCode = "ledger.transaction.template_code"
	AnnotationTransactionDirection    = "ledger.transaction.direction"
)
View Source
const DefaultCustomerFBOPriority = 100
View Source
const ErrCodeAddressInvalid models.ErrorCode = "ledger_address_invalid"
View Source
const ErrCodeBusinessAccountMissing models.ErrorCode = "ledger_business_account_missing"
View Source
const ErrCodeCostBasisInvalid models.ErrorCode = "ledger_cost_basis_invalid"
View Source
const ErrCodeCreditPriorityInvalid models.ErrorCode = "ledger_credit_priority_invalid"
View Source
const ErrCodeCurrencyInvalid models.ErrorCode = "ledger_currency_invalid"
View Source
const ErrCodeCustomerAccountMissing models.ErrorCode = "ledger_customer_account_missing"
View Source
const ErrCodeEntryInvalid models.ErrorCode = "ledger_entry_invalid"
View Source
const ErrCodeInvalidTransactionTotal models.ErrorCode = "invalid_transaction_total"
View Source
const ErrCodeListTransactionsInputInvalid models.ErrorCode = "ledger_list_transactions_input_invalid"
View Source
const ErrCodeResolutionScopeInvalid models.ErrorCode = "ledger_resolution_scope_invalid"
View Source
const ErrCodeResolutionTemplateUnknown models.ErrorCode = "ledger_resolution_template_unknown"
View Source
const ErrCodeRoutingKeyVersionInvalid models.ErrorCode = "ledger_routing_key_version_invalid"
View Source
const ErrCodeRoutingKeyVersionUnsupported models.ErrorCode = "ledger_routing_key_version_unsupported"
View Source
const ErrCodeRoutingRuleViolated models.ErrorCode = "ledger_routing_rule_violated"
View Source
const ErrCodeTransactionAmountInvalid models.ErrorCode = "ledger_transaction_amount_invalid"
View Source
const ErrCodeTransactionAuthorizationStatusInvalid models.ErrorCode = "ledger_transaction_authorization_status_invalid"
View Source
const ErrCodeTransactionGroupEmpty models.ErrorCode = "ledger_transaction_group_empty"
View Source
const ErrCodeTransactionInputRequired models.ErrorCode = "ledger_transaction_input_required"

Variables

View Source
var ErrAddressInvalid = models.NewValidationIssue(
	ErrCodeAddressInvalid,
	"ledger posting address is invalid",
)
View Source
var ErrBusinessAccountMissing = models.NewValidationIssue(
	ErrCodeBusinessAccountMissing,
	"required business ledger account is missing",
)
View Source
var ErrCodeLedgerQueryInvalid models.ErrorCode = "ledger_query_invalid"
View Source
var ErrCostBasisInvalid = models.NewValidationIssue(
	ErrCodeCostBasisInvalid,
	"ledger cost basis is invalid",
)
View Source
var ErrCreditPriorityInvalid = models.NewValidationIssue(
	ErrCodeCreditPriorityInvalid,
	"ledger credit priority is invalid",
)
View Source
var ErrCurrencyInvalid = models.NewValidationIssue(
	ErrCodeCurrencyInvalid,
	"ledger currency is invalid",
)
View Source
var ErrCustomerAccountMissing = models.NewValidationIssue(
	ErrCodeCustomerAccountMissing,
	"required customer ledger account is missing",
)
View Source
var ErrEntryInvalid = models.NewValidationIssue(
	ErrCodeEntryInvalid,
	"ledger entry is invalid",
)
View Source
var ErrInvalidTransactionTotal = models.NewValidationIssue(
	ErrCodeInvalidTransactionTotal,
	"transaction total is invalid, credits and debits must sum to 0",
)
View Source
var ErrLedgerQueryInvalid = models.NewValidationIssue(
	ErrCodeLedgerQueryInvalid,
	"ledger query is invalid",
)
View Source
var ErrListTransactionsInputInvalid = models.NewValidationIssue(
	ErrCodeListTransactionsInputInvalid,
	"ledger list transactions input is invalid",
)
View Source
var ErrResolutionScopeInvalid = models.NewValidationIssue(
	ErrCodeResolutionScopeInvalid,
	"ledger resolution scope is invalid",
)
View Source
var ErrResolutionTemplateUnknown = models.NewValidationIssue(
	ErrCodeResolutionTemplateUnknown,
	"ledger transaction template type is unknown",
)
View Source
var ErrRoutingKeyVersionInvalid = models.NewValidationIssue(
	ErrCodeRoutingKeyVersionInvalid,
	"ledger routing key version is invalid",
)
View Source
var ErrRoutingKeyVersionUnsupported = models.NewValidationIssue(
	ErrCodeRoutingKeyVersionUnsupported,
	"ledger routing key version is unsupported",
)
View Source
var ErrRoutingRuleViolated = models.NewValidationIssue(
	ErrCodeRoutingRuleViolated,
	"ledger routing rule violated",
)
View Source
var ErrTransactionAmountInvalid = models.NewValidationIssue(
	ErrCodeTransactionAmountInvalid,
	"ledger transaction amount is invalid",
)
View Source
var ErrTransactionAuthorizationStatusInvalid = models.NewValidationIssue(
	ErrCodeTransactionAuthorizationStatusInvalid,
	"ledger transaction authorization status is invalid",
)
View Source
var ErrTransactionGroupEmpty = models.NewValidationIssue(
	ErrCodeTransactionGroupEmpty,
	"ledger transaction group must contain at least one transaction",
)
View Source
var ErrTransactionInputRequired = models.NewValidationIssue(
	ErrCodeTransactionInputRequired,
	"transaction input is required",
)

Functions

func ChargeAnnotations

func ChargeAnnotations(chargeID models.NamespacedID) models.Annotations

func SortedFeatures

func SortedFeatures(features []string) []string

SortedFeatures returns a sorted copy of features for canonical storage. Returns nil if empty.

func TransactionAnnotations

func TransactionAnnotations(templateCode string, direction TransactionDirection) models.Annotations

func TransactionTemplateCodeFromAnnotations

func TransactionTemplateCodeFromAnnotations(annotations models.Annotations) (string, error)

func ValidateAddress

func ValidateAddress(ctx context.Context, address PostingAddress) error

func ValidateCostBasis

func ValidateCostBasis(value alpacadecimal.Decimal) error

func ValidateCreditPriority

func ValidateCreditPriority(value int) error

ValidateCreditPriority validates a credit priority integer value.

func ValidateCurrency

func ValidateCurrency(value currencyx.Code) error

ValidateCurrency validates a currency value.

func ValidateEntryInput

func ValidateEntryInput(ctx context.Context, entry EntryInput) error

func ValidateInvariance

func ValidateInvariance(ctx context.Context, entries []EntryInput) error

ValidateInvariance validates that Debit - Credit = 0 for the given entries.

func ValidateRouting

func ValidateRouting(ctx context.Context, entries []EntryInput) error

func ValidateTransactionAmount

func ValidateTransactionAmount(value alpacadecimal.Decimal) error

func ValidateTransactionInput

func ValidateTransactionInput(ctx context.Context, transaction TransactionInput) error

func ValidateTransactionInputWith

func ValidateTransactionInputWith(ctx context.Context, transaction TransactionInput, routingValidator RoutingValidator) error

Types

type Account

type Account interface {
	ID() models.NamespacedID
	Type() AccountType
}

Account represents a ledger account tying together multiple sub-accounts. Accounts describe ownership and purpose while SubAccounts parameterize the actual posting address.

type AccountCatalog

type AccountCatalog interface {
	AccountReader
	AccountProvisioner
}

type AccountLocker

type AccountLocker interface {
	LockAccountsForPosting(ctx context.Context, accounts []Account) error
}

type AccountProvisioner

type AccountProvisioner interface {
	CreateAccount(ctx context.Context, input CreateAccountInput) (Account, error)
	EnsureSubAccount(ctx context.Context, input CreateSubAccountInput) (SubAccount, error)
}

type AccountReader

type AccountReader interface {
	GetAccountByID(ctx context.Context, id models.NamespacedID) (Account, error)
	GetSubAccountByID(ctx context.Context, id models.NamespacedID) (SubAccount, error)

	ListSubAccounts(ctx context.Context, input ListSubAccountsInput) ([]SubAccount, error)
	ListAccounts(ctx context.Context, input ListAccountsInput) ([]Account, error)
}

type AccountResolver

type AccountResolver interface {
	GetCustomerAccounts(ctx context.Context, customerID customer.CustomerID) (CustomerAccounts, error)
	EnsureBusinessAccounts(ctx context.Context, namespace string) (BusinessAccounts, error)
	GetBusinessAccounts(ctx context.Context, namespace string) (BusinessAccounts, error)
}

type AccountType

type AccountType string
const (
	AccountTypeCustomerFBO        AccountType = "customer_fbo" // is this the right name?
	AccountTypeCustomerReceivable AccountType = "customer_receivable"
	AccountTypeCustomerAccrued    AccountType = "customer_accrued"
)

Customer Accounts

const (
	AccountTypeWash      AccountType = "wash"
	AccountTypeEarnings  AccountType = "earnings"
	AccountTypeBrokerage AccountType = "brokerage"
)

Shared Business Accounts

func (AccountType) Validate

func (t AccountType) Validate() error

type Balance

type Balance interface {
	Settled() alpacadecimal.Decimal
	Pending() alpacadecimal.Decimal
}

type BalanceQuerier

type BalanceQuerier interface {
	GetAccountBalance(ctx context.Context, account Account, query RouteFilter, after *TransactionCursor) (Balance, error)
	GetSubAccountBalance(ctx context.Context, subAccount SubAccount, after *TransactionCursor) (Balance, error)
}

type BusinessAccount

type BusinessAccount interface {
	Account

	GetSubAccountForRoute(ctx context.Context, route BusinessRouteParams) (SubAccount, error)
}

BusinessAccount is a business account.

type BusinessAccounts

type BusinessAccounts struct {
	WashAccount      BusinessAccount
	EarningsAccount  BusinessAccount
	BrokerageAccount BusinessAccount
}

type BusinessRouteParams

type BusinessRouteParams struct {
	Currency  currencyx.Code
	CostBasis *alpacadecimal.Decimal
}

func (BusinessRouteParams) Route

func (p BusinessRouteParams) Route() Route

func (BusinessRouteParams) Validate

func (p BusinessRouteParams) Validate() error

type ChargeTransactionAnnotationsInput

type ChargeTransactionAnnotationsInput struct {
	ChargeID models.NamespacedID

	SubscriptionID      *string
	SubscriptionPhaseID *string
	SubscriptionItemID  *string
	FeatureID           *string
}

type CreateAccountInput

type CreateAccountInput struct {
	Namespace   string
	Type        AccountType
	Annotations models.Annotations
}

func (CreateAccountInput) Validate

func (c CreateAccountInput) Validate() error

type CreateSubAccountInput

type CreateSubAccountInput struct {
	Namespace   string
	AccountID   string
	Annotations models.Annotations
	Route       Route
}

func (CreateSubAccountInput) Validate

func (c CreateSubAccountInput) Validate() error

type CustomerAccount

type CustomerAccount interface {
	Account
}

CustomerAccount is a Customer specific account

type CustomerAccounts

type CustomerAccounts struct {
	FBOAccount        CustomerFBOAccount
	ReceivableAccount CustomerReceivableAccount
	AccruedAccount    CustomerAccruedAccount
}

type CustomerAccruedAccount

type CustomerAccruedAccount interface {
	CustomerAccount

	GetSubAccountForRoute(ctx context.Context, route CustomerAccruedRouteParams) (SubAccount, error)
}

CustomerAccruedAccount is a customer accrued account used as a staging area for usage that has been acknowledged but not yet recognized as earnings.

type CustomerAccruedRouteParams

type CustomerAccruedRouteParams struct {
	Currency  currencyx.Code
	CostBasis *alpacadecimal.Decimal
}

CustomerAccruedRouteParams are routing parameters specific to customer accrued sub-accounts. Routed by currency only for now.

func (CustomerAccruedRouteParams) Route

func (CustomerAccruedRouteParams) Validate

func (p CustomerAccruedRouteParams) Validate() error

type CustomerFBOAccount

type CustomerFBOAccount interface {
	CustomerAccount

	GetSubAccountForRoute(ctx context.Context, route CustomerFBORouteParams) (SubAccount, error)
}

CustomerFBOAccount is a customer FBO account.

type CustomerFBORouteParams

type CustomerFBORouteParams struct {
	Currency       currencyx.Code
	CreditPriority int
	TaxCode        *string
	Features       []string
	CostBasis      *alpacadecimal.Decimal
}

CustomerFBORouteParams are routing parameters specific to customer FBO sub-accounts. CreditPriority is required (non-pointer) — the type system enforces its presence.

func (CustomerFBORouteParams) Route

func (p CustomerFBORouteParams) Route() Route

func (CustomerFBORouteParams) Validate

func (p CustomerFBORouteParams) Validate() error

type CustomerReceivableAccount

type CustomerReceivableAccount interface {
	CustomerAccount

	GetSubAccountForRoute(ctx context.Context, route CustomerReceivableRouteParams) (SubAccount, error)
}

CustomerReceivableAccount is a customer receivable account.

type CustomerReceivableRouteParams

type CustomerReceivableRouteParams struct {
	Currency                       currencyx.Code
	CostBasis                      *alpacadecimal.Decimal
	TransactionAuthorizationStatus TransactionAuthorizationStatus
}

CustomerReceivableRouteParams are routing parameters specific to customer receivable sub-accounts. TransactionAuthorizationStatus is required; callers must explicitly select the open or authorized route.

func (CustomerReceivableRouteParams) Route

func (CustomerReceivableRouteParams) Validate

func (p CustomerReceivableRouteParams) Validate() error

type Entry

type Entry interface {
	EntryInput
	TransactionID() models.NamespacedID
}

type EntryInput

type EntryInput interface {
	PostingAddress() PostingAddress
	Amount() alpacadecimal.Decimal
}

type Filters

type Filters struct {
	// BookedAtPeriod is inclusive-exclusive... should it be? Maybe finally add period inclusivity params?
	BookedAtPeriod *timeutil.OpenPeriod
	After          *TransactionCursor
	TransactionID  *string
	// AccountID narrows the query to a single account via its sub-accounts.
	AccountID *string
	Route     RouteFilter
}

type Ledger

type Ledger interface {
	// CommitGroup commits a list of transactions on the Ledger atomically
	CommitGroup(ctx context.Context, group TransactionGroupInput) (TransactionGroup, error)

	// GetTransactionGroup loads a previously committed transaction group including its transactions.
	GetTransactionGroup(ctx context.Context, id models.NamespacedID) (TransactionGroup, error)

	// ListTransactions lists transactions on the Ledger according to some filters
	//
	// TODO: Cursoring gets problematic due to diff between wall_clock and booked_at. It would be convenient to return in order of booked_at as that simplifies parsing. This API will likely change.
	ListTransactions(ctx context.Context, params ListTransactionsInput) (ListTransactionsResult, error)
}

type ListAccountsInput

type ListAccountsInput struct {
	Namespace    string
	AccountTypes []AccountType
}

type ListSubAccountsInput

type ListSubAccountsInput struct {
	Namespace string
	AccountID string

	Route RouteFilter
}

type ListTransactionsCreditMovement

type ListTransactionsCreditMovement uint8
const (
	ListTransactionsCreditMovementUnspecified ListTransactionsCreditMovement = iota
	ListTransactionsCreditMovementPositive
	ListTransactionsCreditMovementNegative
)

type ListTransactionsInput

type ListTransactionsInput struct {
	Namespace string
	Cursor    *TransactionCursor
	Before    *TransactionCursor
	Limit     int

	TransactionID *models.NamespacedID

	// AccountIDs scopes the query to transactions with entries on these accounts.
	AccountIDs []string
	Currency   *currencyx.Code

	CreditMovement ListTransactionsCreditMovement

	// AnnotationFilters matches transactions whose annotations contain all the given key-value pairs.
	AnnotationFilters map[string]string
}

func (ListTransactionsInput) Validate

func (i ListTransactionsInput) Validate() error

type ListTransactionsResult

type ListTransactionsResult struct {
	Items      []Transaction
	NextCursor *TransactionCursor
}

type PostingAddress

type PostingAddress interface {
	models.Equaler[PostingAddress]

	SubAccountID() string
	AccountType() AccountType
	Route() SubAccountRoute
}

PostingAddress encapsulates an address you can post-against. This is a one-to-one mapping to a SubAccount, this address format exists for routing purposes where the full sub-sccount isn't needed.

type Query

type Query struct {
	Namespace string

	Cursor *pagination.Cursor

	Filters Filters
}

func (Query) Validate

func (p Query) Validate() error

type QuerySummedResult

type QuerySummedResult struct {
	SettledSum alpacadecimal.Decimal
	PendingSum alpacadecimal.Decimal
}

type Route

type Route struct {
	Currency                       currencyx.Code
	TaxCode                        *string
	Features                       []string
	CostBasis                      *alpacadecimal.Decimal
	CreditPriority                 *int
	TransactionAuthorizationStatus *TransactionAuthorizationStatus
}

Route holds the literal values that identify a sub-account's routing path. It is used for creation, persistence, and routing key generation.

func (Route) Filter

func (r Route) Filter() RouteFilter

Filter converts a Route to a RouteFilter for use in queries.

func (Route) Normalize

func (r Route) Normalize() (Route, error)

Normalize canonicalizes route values so semantically equivalent routes share the same stored literals and routing keys.

func (Route) Validate

func (r Route) Validate() error

type RouteFilter

type RouteFilter struct {
	Currency currencyx.Code

	// DEFERRED: tax/feature not active yet.
	// Non-currency fields are retained for near-future expansion.
	TaxCode   *string
	Features  []string
	CostBasis mo.Option[*alpacadecimal.Decimal]

	// CreditPriority is only meaningful for customer_fbo queries.
	CreditPriority *int

	// TransactionAuthorizationStatus is currently only meaningful for customer_receivable queries.
	// Nil means "do not filter by authorization status", not "open".
	TransactionAuthorizationStatus *TransactionAuthorizationStatus
}

RouteFilter is the set of route fields that can be used to filter sub-accounts and query balances.

func (RouteFilter) Normalize

func (f RouteFilter) Normalize() (RouteFilter, error)

Normalize canonicalizes route filter values before querying.

type RoutingKey

type RoutingKey struct {
	// contains filtered or unexported fields
}

func BuildRoutingKey

func BuildRoutingKey(version RoutingKeyVersion, route Route) (RoutingKey, error)

func BuildRoutingKeyV1

func BuildRoutingKeyV1(route Route) (RoutingKey, error)

func NewRoutingKey

func NewRoutingKey(version RoutingKeyVersion, value string) (RoutingKey, error)

func (RoutingKey) Value

func (k RoutingKey) Value() string

func (RoutingKey) Version

func (k RoutingKey) Version() RoutingKeyVersion

type RoutingKeyVersion

type RoutingKeyVersion string
const RoutingKeyVersionV1 RoutingKeyVersion = "v1"

func (RoutingKeyVersion) Validate

func (v RoutingKeyVersion) Validate() error

type RoutingValidator

type RoutingValidator interface {
	ValidateEntries(entries []EntryInput) error
}

type SubAccount

type SubAccount interface {
	// Returns the address of the sub-account
	Address() PostingAddress

	// AccountID returns the identifier of the parent account.
	AccountID() models.NamespacedID

	// Route returns the routing values of the sub-account.
	Route() Route
}

SubAccount is an actual address you can post against. It has all required routing information provided. Accounts describe ownership and purpose while SubAccounts parameterize the actual posting address.

type SubAccountRoute

type SubAccountRoute struct {
	// contains filtered or unexported fields
}

func NewSubAccountRouteFromData

func NewSubAccountRouteFromData(id string, key RoutingKey, route Route) (SubAccountRoute, error)

NewSubAccountRouteFromData hydrates a sub-account route from persisted data. Does not enforce Route & RoutingKey equality due to possible version mismatch

func NewSubAccountRouteFromRoute

func NewSubAccountRouteFromRoute(id string, version RoutingKeyVersion, route Route) (SubAccountRoute, error)

NewSubAccountRouteFromRoute creates a new sub-account route from a literal route

func (SubAccountRoute) ID

func (r SubAccountRoute) ID() string

func (SubAccountRoute) Route

func (r SubAccountRoute) Route() Route

func (SubAccountRoute) RoutingKey

func (r SubAccountRoute) RoutingKey() RoutingKey

type Transaction

type Transaction interface {
	Cursor() TransactionCursor
	BookedAt() time.Time
	Entries() []Entry
	ID() models.NamespacedID
	Annotations() models.Annotations
}

Transaction represents a list of entries booked at the same time

type TransactionAuthorizationStatus

type TransactionAuthorizationStatus string
const (
	TransactionAuthorizationStatusOpen       TransactionAuthorizationStatus = "open"
	TransactionAuthorizationStatusAuthorized TransactionAuthorizationStatus = "authorized"
)

func (TransactionAuthorizationStatus) Validate

type TransactionCursor

type TransactionCursor struct {
	BookedAt  time.Time
	CreatedAt time.Time
	ID        models.NamespacedID
}

func (TransactionCursor) Compare

func (c TransactionCursor) Compare(other TransactionCursor) int

Compare returns cursor ordering by BookedAt, then CreatedAt, then ID. It returns -1 if c < other, 0 if equal, and 1 if c > other.

func (TransactionCursor) Validate

func (c TransactionCursor) Validate() error

type TransactionDirection

type TransactionDirection string
const (
	TransactionDirectionForward    TransactionDirection = "forward"
	TransactionDirectionCorrection TransactionDirection = "correction"
)

func TransactionDirectionFromAnnotations

func TransactionDirectionFromAnnotations(annotations models.Annotations) (TransactionDirection, error)

type TransactionGroup

type TransactionGroup interface {
	ID() models.NamespacedID
	Transactions() []Transaction
	Annotations() models.Annotations
}

TransactionGroup represents a group of transactions written to the ledger at the same time

type TransactionGroupInput

type TransactionGroupInput interface {
	Namespace() string
	Transactions() []TransactionInput
	Annotations() models.Annotations
}

type TransactionInput

type TransactionInput interface {
	BookedAt() time.Time
	EntryInputs() []EntryInput
	Annotations() models.Annotations
	AsGroupInput(namespace string, annotations models.Annotations) TransactionGroupInput
}

Jump to

Keyboard shortcuts

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