Ecto schema for the accrue_invoices table.
Stores the local copy of a Stripe invoice, including all rollup columns
(subtotal_minor, tax_minor, total_minor, etc.) that the admin UI
and application logic need without round-tripping to Stripe. The data
jsonb column holds the full Stripe payload for callers that need the
raw shape.
For mutations (finalize_invoice, pay_invoice, void_invoice, etc.)
see Accrue.Billing.InvoiceActions, which is the correct entry point
for any write operation.
Legal user-path transitions
draft -> open | void
open -> paid | uncollectible | void
paid, uncollectible, void -> (terminal)Changeset paths
Two changeset functions exist to support the two sources of status changes:
changeset/2— enforces the transition table; use for user-originated writes (finalize_invoice,pay_invoice,void_invoice).force_status_changeset/2— bypasses transition validation; use only from the webhook reconcile path where Stripe is canonical.
Summary
Functions
Builds a user-path changeset. Enforces the legal transition table —
illegal transitions add an error on :status.
Webhook-path discount denormalization. Mirrors Stripe's
discount_minor + total_discount_amounts into local columns.
Stripe is canonical — no local math, no validate_number guard.
Callers pass nil-safe attrs so the cast preserves existing values
when the webhook doesn't carry them.
Builds a webhook-path changeset. Stripe is canonical in this path, so the transition table is bypassed. Use only from the webhook reconcile path.
Canonical list of invoice statuses.
Types
@type t() :: %Accrue.Billing.Invoice{ __meta__: term(), amount_due_minor: term(), amount_paid_minor: term(), amount_remaining_minor: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), billing_reason: term(), collection_method: term(), currency: term(), customer: term(), customer_id: term(), data: term(), discount_minor: term(), due_date: term(), finalized_at: term(), hosted_url: term(), id: term(), inserted_at: term(), items: term(), last_finalization_error_code: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), number: term(), paid_at: term(), pdf_url: term(), period_end: term(), period_start: term(), processor: term(), processor_id: term(), status: term(), subscription: term(), subscription_id: term(), subtotal_minor: term(), tax_minor: term(), total_cents: term(), total_discount_amounts: term(), total_minor: term(), updated_at: term(), voided_at: term() }
Functions
@spec changeset( %Accrue.Billing.Invoice{ __meta__: term(), amount_due_minor: term(), amount_paid_minor: term(), amount_remaining_minor: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), billing_reason: term(), collection_method: term(), currency: term(), customer: term(), customer_id: term(), data: term(), discount_minor: term(), due_date: term(), finalized_at: term(), hosted_url: term(), id: term(), inserted_at: term(), items: term(), last_finalization_error_code: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), number: term(), paid_at: term(), pdf_url: term(), period_end: term(), period_start: term(), processor: term(), processor_id: term(), status: term(), subscription: term(), subscription_id: term(), subtotal_minor: term(), tax_minor: term(), total_cents: term(), total_discount_amounts: term(), total_minor: term(), updated_at: term(), voided_at: term() } | Ecto.Changeset.t(), map() ) :: Ecto.Changeset.t()
Builds a user-path changeset. Enforces the legal transition table —
illegal transitions add an error on :status.
@spec force_discount_changeset( %Accrue.Billing.Invoice{ __meta__: term(), amount_due_minor: term(), amount_paid_minor: term(), amount_remaining_minor: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), billing_reason: term(), collection_method: term(), currency: term(), customer: term(), customer_id: term(), data: term(), discount_minor: term(), due_date: term(), finalized_at: term(), hosted_url: term(), id: term(), inserted_at: term(), items: term(), last_finalization_error_code: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), number: term(), paid_at: term(), pdf_url: term(), period_end: term(), period_start: term(), processor: term(), processor_id: term(), status: term(), subscription: term(), subscription_id: term(), subtotal_minor: term(), tax_minor: term(), total_cents: term(), total_discount_amounts: term(), total_minor: term(), updated_at: term(), voided_at: term() } | Ecto.Changeset.t(), map() ) :: Ecto.Changeset.t()
Webhook-path discount denormalization. Mirrors Stripe's
discount_minor + total_discount_amounts into local columns.
Stripe is canonical — no local math, no validate_number guard.
Callers pass nil-safe attrs so the cast preserves existing values
when the webhook doesn't carry them.
@spec force_status_changeset( %Accrue.Billing.Invoice{ __meta__: term(), amount_due_minor: term(), amount_paid_minor: term(), amount_remaining_minor: term(), automatic_tax: term(), automatic_tax_disabled_reason: term(), automatic_tax_status: term(), billing_reason: term(), collection_method: term(), currency: term(), customer: term(), customer_id: term(), data: term(), discount_minor: term(), due_date: term(), finalized_at: term(), hosted_url: term(), id: term(), inserted_at: term(), items: term(), last_finalization_error_code: term(), last_stripe_event_id: term(), last_stripe_event_ts: term(), lock_version: term(), metadata: term(), number: term(), paid_at: term(), pdf_url: term(), period_end: term(), period_start: term(), processor: term(), processor_id: term(), status: term(), subscription: term(), subscription_id: term(), subtotal_minor: term(), tax_minor: term(), total_cents: term(), total_discount_amounts: term(), total_minor: term(), updated_at: term(), voided_at: term() } | Ecto.Changeset.t(), map() ) :: Ecto.Changeset.t()
Builds a webhook-path changeset. Stripe is canonical in this path, so the transition table is bypassed. Use only from the webhook reconcile path.
@spec statuses() :: [atom()]
Canonical list of invoice statuses.