Phase 3 Plan 05 invoice write surface (BILL-17/18/19).
Exposes the five user-path invoice actions on Accrue.Billing via the
Plan 01 defdelegate facade: finalize_invoice, void_invoice,
pay_invoice, mark_uncollectible, send_invoice. Each follows the
D3-18 one-shape Repo.transact/2 pattern:
telemetry.span
|> Repo.transact (
Processor.<op>(stripe_id, opts)
|> InvoiceProjection.decompose/1
|> update row via Invoice.changeset/2 (user path, enforces transitions)
|> upsert child items by stripe_id
|> Events.record/1 (same transaction — EVT-04)
)
|> IntentResult.wrap (pay_invoice only — may surface SCA)pay_invoice/2 returns an intent_result tagged union because Stripe
may surface SCA/3DS; the other four return plain {:ok, %Invoice{}}.
Every action has a bang variant that raises on {:error, _};
pay_invoice!/2 additionally raises Accrue.ActionRequiredError on
:requires_action.
The webhook path uses the force-status bypass on the Invoice schema —
that bypass is NOT reachable from this module. Illegal user-path
transitions (e.g. draft -> paid) are rejected by Invoice.changeset/2
with an error on :status and propagate as
{:error, %Ecto.Changeset{}}.
Summary
Functions
@spec finalize_invoice( Accrue.Billing.Invoice.t(), keyword() ) :: {:ok, Accrue.Billing.Invoice.t()} | {:error, term()}
@spec finalize_invoice!( Accrue.Billing.Invoice.t(), keyword() ) :: Accrue.Billing.Invoice.t()
@spec mark_uncollectible( Accrue.Billing.Invoice.t(), keyword() ) :: {:ok, Accrue.Billing.Invoice.t()} | {:error, term()}
@spec mark_uncollectible!( Accrue.Billing.Invoice.t(), keyword() ) :: Accrue.Billing.Invoice.t()
@spec pay_invoice( Accrue.Billing.Invoice.t(), keyword() ) :: {:ok, Accrue.Billing.Invoice.t()} | {:ok, :requires_action, map()} | {:error, term()}
@spec pay_invoice!( Accrue.Billing.Invoice.t(), keyword() ) :: Accrue.Billing.Invoice.t()
@spec send_invoice( Accrue.Billing.Invoice.t(), keyword() ) :: {:ok, Accrue.Billing.Invoice.t()} | {:error, term()}
@spec send_invoice!( Accrue.Billing.Invoice.t(), keyword() ) :: Accrue.Billing.Invoice.t()
@spec void_invoice( Accrue.Billing.Invoice.t(), keyword() ) :: {:ok, Accrue.Billing.Invoice.t()} | {:error, term()}
@spec void_invoice!( Accrue.Billing.Invoice.t(), keyword() ) :: Accrue.Billing.Invoice.t()