AuroraMeter.Schema.CreditTransaction (Aurora Meter v1.0.0)

View Source

One append-only entry in a tenant's credit ledger, in micro-dollars.

kind says what happened and amount is the signed delta it applied to the balance (grant positive; settle, debit and expire negative or zero; hold and release zero, moving held_delta instead). balance_after and held_after snapshot the row after the entry, so the log alone reproduces every balance.

balance_after, held_after and promotional_after snapshot the row after the entry, so the log alone reproduces every figure.

reference is the caller's idempotency key: unique per kind, so a retried grant is returned instead of credited twice and a retried hold or debit is refused. A hold carries status (:pending, :settled, :released) and, once settled, settled_amount; a promotional grant may carry expires_at and gets expired_at when AuroraMeter.Credits.expire_due/1 consumes it.

category names where a grant's money came from (:paid, :promotional, :adjustment) and, on a reversal, marks :reversal, which is a refund or chargeback taking a paid grant back. A reversal never consumes promotional credit and is reported against grants rather than as spend.

A reversal is kind: :reverse from schema version 9 on, and was kind: :debit, category: :reversal before it. Both shapes are permanent, because the log is append only. Ask reversal?/1 rather than matching on either.

seq is the order. It is a Postgres identity column, assigned in commit order, and it is what every query that reconstructs what happened first sorts by. inserted_at is a wall-clock stamp and is not monotonic, so it orders nothing (findings L20, X213). hold_transaction_id names the hold a settle or release row closes, and updated_at is stamped by the database the first time the row is updated in place.

Summary

Functions

The grant categories.

Builds a changeset for a ledger entry.

The transaction kinds.

Whether this entry is a reversal: a refund or a chargeback taking paid credit back.

Types

category()

@type category() :: :paid | :promotional | :adjustment | :reversal

kind()

@type kind() :: :grant | :hold | :settle | :release | :debit | :expire | :reverse

status()

@type status() :: :pending | :settled | :released

t()

@type t() :: %AuroraMeter.Schema.CreditTransaction{
  __meta__: term(),
  amount: term(),
  balance_after: term(),
  category: term(),
  expired_at: term(),
  expires_at: term(),
  held_after: term(),
  held_delta: term(),
  hold_transaction_id: term(),
  id: term(),
  inserted_at: term(),
  kind: term(),
  metadata: term(),
  promotional_after: term(),
  reference: term(),
  seq: term(),
  settled_amount: term(),
  status: term(),
  tenant_key: term(),
  updated_at: term()
}

Functions

categories()

@spec categories() :: [category()]

The grant categories.

Examples

iex> AuroraMeter.Schema.CreditTransaction.categories()
[:paid, :promotional, :adjustment]

changeset(transaction, attrs)

@spec changeset(t(), map()) :: Ecto.Changeset.t()

Builds a changeset for a ledger entry.

kinds()

@spec kinds() :: [kind()]

The transaction kinds.

Examples

iex> :hold in AuroraMeter.Schema.CreditTransaction.kinds()
true

reversal?(entry)

@spec reversal?(t() | map()) :: boolean()

Whether this entry is a reversal: a refund or a chargeback taking paid credit back.

Two row shapes mean it, and both are permanent. From schema version 9 a reversal is written with kind: :reverse and category: :reversal. Before that it was written with kind: :debit and category: :reversal, and those rows are in the append-only log for ever, so every reader has to accept both. This is the one predicate that knows that; no other module may re-derive it, because a second copy is the one that gets the older shape wrong.

Examples

iex> AuroraMeter.Schema.CreditTransaction.reversal?(%{kind: :reverse, category: :reversal})
true

iex> AuroraMeter.Schema.CreditTransaction.reversal?(%{kind: :debit, category: :reversal})
true

iex> AuroraMeter.Schema.CreditTransaction.reversal?(%{kind: :debit, category: nil})
false

iex> AuroraMeter.Schema.CreditTransaction.reversal?(%{kind: :grant, category: :paid})
false