Billing::Invoice
Purpose
An Invoice is a customer-facing commercial document generated when an Org::Company wants to purchase entitlements (placement credits, gig credits). It captures what was sold, at what price, under which legal entity, and tracks the payment lifecycle through to entitlement granting.
Invoices serve as the bridge between commercial transactions and entitlement accounting. The commercial layer (what we sold, for how much) is deliberately separated from the entitlements layer (what the customer can spend) — the invoice posting mechanism is what connects them.
Model Context
| Context | Details |
|---|---|
| Aggregate | Billing::Invoice (root) + Billing::InvoiceItem (child) + Billing::Payment (child) + Billing::InvoicePosting (child) |
| Layer | Commercial Documents |
| Upstream dependencies | Billing::Account, Billing::Agreement (terms inform pricing), Billing::LegalEntity (seller), Billing::BillToProfile (buyer) |
| Downstream dependents | Billing::LedgerEntry (posting writes grants), Billing::EntitlementLot (gig posting creates lots), Billing::EntitlementBalance (updated on posting) |
State Machine
Invoice status
| From | To | Trigger | Notes |
|---|---|---|---|
| (new) | draft | UC-1: Invoice created | Default status on creation |
draft | issued | UC-2: Admin issues the invoice | Invoice becomes immutable |
draft | void | UC-8: Admin voids before issuing | Created in error |
issued | partially_paid | UC-5: First verified payment, < total | Awaiting remaining balance |
issued | paid | UC-5: Verified payments >= total | Triggers posting (UC-7) |
issued | void | UC-8: Admin voids before payment | Customer cancels |
partially_paid | paid | UC-5: Cumulative verified payments >= total | Triggers posting (UC-7) |
paid | credited | (future) Credit note / refund workflow | Post-payment correction |
Payment status
| From | To | Trigger | Notes |
|---|---|---|---|
| (new) | submitted | UC-4: Admin records payment | Proof provided, awaiting review |
submitted | verified | UC-5: Finance confirms receipt | Updates invoice status |
submitted | rejected | UC-6: Proof invalid | No effect on invoice |
Use Cases
| ID | Use Case | Trigger | Actor |
|---|---|---|---|
| UC-1 | Create an invoice for a company's credit purchase | Company wants to purchase credits | Admin (sales) |
| UC-2 | Issue a draft invoice for customer payment | Admin confirms draft invoice is correct | Admin (sales) |
| UC-3 | Edit a draft invoice before issuance | Admin needs to correct details before issuing | Admin (sales) |
| UC-4 | Record a bank transfer payment against an invoice | Customer provides proof of bank transfer | Admin (ops) |
| UC-5 | Verify a payment and settle the invoice | Finance confirms money received in bank account | Admin (finance) |
| UC-6 | Reject an invalid payment submission | Proof is invalid or money not received | Admin (finance) |
| UC-7 | Post a paid invoice to grant entitlements | Invoice transitions to paid | System |
| UC-8 | Void an unpaid invoice | Invoice created in error or customer cancels | Admin |
| UC-9 | View invoice details with line items and payments | Admin needs to review a specific invoice | Admin |
| UC-10 | View all invoices for a billing account | Admin needs to see all invoices for a company | Admin |
UC-1: Create an invoice for a company's credit purchase
| Field | Details |
|---|---|
| Actor | Identities::Admin (sales/ops) |
| Trigger | Company wants to purchase entitlements (placement credits or gig credits) |
Preconditions:
Billing::Accountexists for theOrg::Company- An active
Billing::Agreementexists with relevant terms Billing::ProductandBilling::ProductPriceexist for the desired entitlement and marketBilling::LegalEntityexists for the seller jurisdictionBilling::BillToProfileexists for the buyer
System Behavior:
- Admin selects the billing account, product, and quantity
- System resolves the applicable
Billing::ProductPricebased on:country_idmatching the company's countryactive_from/active_untildate range
- System resolves the active
Billing::Agreementto obtain negotiated terms (fee rates, unit prices, discounts) - System generates the invoice in
draftstatus:- Snapshots bill-to fields from
Billing::BillToProfile(company name, attention, email, address) - Sets
currencyfrom the bill-to profile - Associates the seller
Billing::LegalEntity - Generates
invoice_numberusing the legal entity's prefix/suffix
- Snapshots bill-to fields from
- System creates
Billing::InvoiceItemrows:- Placement credits: one item — full value with GST
- Gig credits: two items — principal (tax-exempt) + platform fee (with GST)
- Each item snapshots: description, quantity, unit_price_cents, amount_cents, tax_cents, units_to_grant
- Gig items include
metadatawithplatform_fee_rate_bps,principal_amount_cents,platform_fee_amount_cents
- System computes and stores
subtotal_cents,tax_cents,total_centson the invoice - Admin audit trail recorded
Business Rules:
- Invoice items snapshot all pricing and tax terms at creation time — later changes to products, prices, or agreements do not affect existing invoices
- Gig credit invoices must have exactly two line items: principal (tax_cents = 0) and platform fee (tax applied)
- Placement credit invoices apply GST to the full value
invoice_numberis unique per(invoice_number, billing_account_id)- Pricing is never hard-coded — it comes from
Billing::ProductPrice
Postconditions:
- Invoice exists in
draftstatus with computed totals - No payments or postings exist yet
- No entitlements are granted
UC-2: Issue a draft invoice for customer payment
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Admin confirms the draft invoice is correct and ready to send |
Preconditions:
- Invoice exists in
draftstatus
System Behavior:
- Admin reviews the draft invoice and confirms issuance
- System transitions the invoice to
issuedstatus - System records
issued_attimestamp - The invoice is now available for the customer to pay via bank transfer
Business Rules:
- Once
issued, the invoice amounts and items are immutable - To correct an issued invoice: void it and create a new one (see UC-8)
Postconditions:
- Invoice status =
issued - Invoice is immutable from this point forward
- Customer can initiate payment
UC-3: Edit a draft invoice before issuance
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Admin needs to correct details before issuing |
Preconditions:
- Invoice exists in
draftstatus
System Behavior:
- Admin modifies invoice fields (due date, bill-to details) and/or adds, removes, or updates line items
- System recomputes
subtotal_cents,tax_cents,total_cents - Admin audit trail recorded
Business Rules:
- Editing is only allowed while status is
draft - All creation-time validations still apply (e.g., gig invoices need two line items)
Postconditions:
- Invoice reflects updated values with recomputed totals
UC-4: Record a bank transfer payment against an invoice
| Field | Details |
|---|---|
| Actor | Identities::Admin (business/ops) |
| Trigger | Customer makes a bank transfer and provides proof of payment |
Preconditions:
- Invoice exists in
issuedorpartially_paidstatus
System Behavior:
- Admin creates a
Billing::Paymentrecord with:method=bank_transferstatus=submittedamount_cents= transfer amountbank_reference= reference number from bank statementproof_url= S3 path to proof document (screenshot, receipt)
Business Rules:
- Multiple payments per invoice are allowed (supports partial payments and accidental duplicate recordings)
- A payment in
submittedstatus does not affect the invoice status — it requires verification first
Postconditions:
- Payment record exists in
submittedstatus - Invoice status unchanged
UC-5: Verify a payment and settle the invoice
| Field | Details |
|---|---|
| Actor | Identities::Admin (finance) |
| Trigger | Finance confirms money has been received in the bank account |
Preconditions:
- Payment exists in
submittedstatus - Invoice is in
issuedorpartially_paidstatus
System Behavior:
- Finance admin marks the payment as
verified - System records
verified_at,verified_by_admin_id, andreceived_at - System recomputes
verified_total = sum(amount_cents)across all verified payments for this invoice - System updates the invoice status:
- If
verified_total == 0→ staysissued - If
0 < verified_total < total_cents→partially_paid - If
verified_total >= total_cents→paid, andsettled_at = now
- If
- If invoice becomes
paid, the system automatically triggers posting (UC-7)
Business Rules:
- Only
submittedpayments can be verified - Verification records who verified and when (audit trail)
- Entitlements are granted only when the invoice reaches
paid— partial payments do not grant partial credits
Postconditions:
- Payment status =
verified - Invoice status updated based on cumulative verified total
- If invoice became
paid: posting is triggered (UC-7)
UC-6: Reject an invalid payment submission
| Field | Details |
|---|---|
| Actor | Identities::Admin (finance) |
| Trigger | Proof is invalid or money was not received |
Preconditions:
- Payment exists in
submittedstatus
System Behavior:
- Finance admin marks the payment as
rejected - System records the rejection (no recomputation of invoice status needed — rejected payments don't count)
Business Rules:
- Only
submittedpayments can be rejected - Rejecting a payment does not change the invoice status
- The customer or ops team can record a new payment to try again
Postconditions:
- Payment status =
rejected - Invoice status unchanged
UC-7: Post a paid invoice to grant entitlements
| Field | Details |
|---|---|
| Actor | System (triggered automatically when invoice becomes paid) |
| Trigger | Invoice transitions to paid status |
Preconditions:
- Invoice status =
paid - No
Billing::InvoicePostingexists for this invoice (idempotency guard)
System Behavior:
- Lock the invoice row (
SELECT ... FOR UPDATE) - Create
Billing::InvoicePostingwithposted_atandidempotency_key - For each
Billing::InvoiceItem:- Placement credits: write
Billing::LedgerEntrywithentry_type = grant,available_delta = +units_to_grant,deferred_revenue_delta_cents = +amount_cents - Gig credits (principal item): write
Billing::LedgerEntrywith grant + createBilling::EntitlementLotwithplatform_fee_rate_bpsfrom item metadata,platform_fee_total_cents, andunits_purchased = units_to_grant - Gig credits (platform fee item): record
platform_fee_deferred_delta_centson the ledger entry (no units granted — this is a money-only line)
- Placement credits: write
- Update
Billing::EntitlementBalanceprojections:units_available += units_to_grantdeferred_revenue_cents += amount(placement)platform_fee_deferred_cents += fee(gig)
- All of the above happens in a single DB transaction
Business Rules:
- Unique constraint on
Billing::InvoicePosting.billing_invoice_idprevents double-granting (idempotency) - Posting is atomic — if any step fails, the entire transaction rolls back
- Each invoice can only be posted once
Postconditions:
Billing::InvoicePostingrecord exists- Ledger entries recorded (grants)
- Entitlement balances updated — company can immediately spend credits
- For gig: lots created with FIFO ordering
UC-8: Void an unpaid invoice
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Invoice was created in error, or customer cancels before paying |
Preconditions:
- Invoice is in
draftorissuedstatus - No verified payments exist for this invoice
System Behavior:
- Admin voids the invoice
- System transitions the invoice to
voidstatus
Business Rules:
- Cannot void an invoice that has verified payments — those must be dealt with first
- Cannot void a
paidorcreditedinvoice - A voided invoice is kept for audit purposes (never deleted)
- To correct a voided invoice: create a new one
Postconditions:
- Invoice status =
void - No entitlements are affected (posting never happened)
Open Questions:
- Should voiding require a reason field for audit purposes?
- Should submitted (unverified) payments be automatically rejected when an invoice is voided?
UC-9: View invoice details with line items and payments
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Admin needs to review a specific invoice |
Preconditions:
- Invoice exists
System Behavior:
- Admin navigates to the invoice detail page
- System displays:
- Invoice header: number, status, dates, currency, totals
- Seller details: legal entity name and address
- Buyer details: snapshotted bill-to fields
- Line items: description, quantity, unit price, amount, tax, units to grant
- Payments: list of all payments with status, amount, bank reference, proof link
- Posting status: whether the invoice has been posted, and when
Postconditions:
- Read-only operation — no data changes
UC-10: View all invoices for a billing account
| Field | Details |
|---|---|
| Actor | Identities::Admin |
| Trigger | Admin needs to see all invoices for a company's billing account |
Preconditions:
Billing::Accountexists
System Behavior:
- Admin navigates to the billing account → Invoices tab
- System displays all invoices ordered by
created_atdescending - Each invoice shows: number, status, currency, total, due date, payment status
Business Rules:
- Voided invoices remain visible (audit requirement)
- Filterable by status
Postconditions:
- Read-only operation — no data changes
Invariants
- An invoice must belong to exactly one
Billing::Account - An invoice must have at least one
Billing::InvoiceItem - Once
issued, invoice amounts and items are immutable — corrections require void + reissue - Invoices are never deleted — only voided or credited
invoice_numberis unique per(invoice_number, billing_account_id)- Entitlements are granted only after the invoice reaches
paidstatus — partial payments do not grant partial credits - Each invoice can be posted at most once (enforced by unique constraint on
Billing::InvoicePosting.billing_invoice_id) - Invoice items snapshot all pricing, tax, and terms at creation time — later changes to products or agreements do not affect existing invoices
- Gig credit invoices must have two line items: principal (tax-exempt) and platform fee (taxed)
total_cents = subtotal_cents + tax_centsand must equal the sum of all item(amount_cents + tax_cents)- Admin audit trail (
created_by,updated_by) is always populated on invoices
Model Interactions
| Related Model | Relationship | Interaction |
|---|---|---|
Billing::Account | Invoice belongs_to Account | Account must exist. One account has many invoices over time. |
Billing::Agreement | Invoice references Agreement | Agreement's terms inform pricing at invoice creation. Invoice snapshots the relevant values. |
Billing::LegalEntity | Invoice belongs_to LegalEntity | Seller-of-record. Determines invoice numbering, tax regime, and currency. |
Billing::BillToProfile | Invoice belongs_to BillToProfile | Buyer details source. Invoice snapshots bill-to fields at creation (never joins back for historical invoices). |
Billing::InvoiceItem | Invoice has_many InvoiceItems | Line items created with the invoice. Snapshot product, pricing, tax, and units to grant. |
Billing::Payment | Invoice has_many Payments | Tracks offline bank transfers. Multiple payments supported for partial payments. |
Billing::InvoicePosting | Invoice has_one InvoicePosting | Idempotent record of entitlement granting. Created once when invoice becomes paid. |
Billing::Product | InvoiceItem references Product | What was sold (placement credits, gig credits). Provides grants_units_per_quantity. |
Billing::ProductPrice | InvoiceItem references ProductPrice | Market-specific pricing used at issuance. Values snapshotted into item columns. |
Billing::LedgerEntry | Posting creates LedgerEntries | Grant entries written to the append-only ledger during posting. |
Billing::EntitlementLot | Posting creates Lots (gig only) | FIFO purchase batch created during gig posting with per-lot platform fee rate. |
Billing::EntitlementBalance | Posting updates Balances | units_available and deferred revenue/fee projections updated during posting. |
Identities::Admin | Invoice tracks created_by / updated_by | Audit trail. Payment verification also records verified_by_admin_id. |
Schema Gaps
Items identified by comparing use cases against the current DBML schema (billing.dbml):
| Gap | Impact | Suggested Resolution |
|---|---|---|
No billing_agreement_id on billing_invoices | Cannot trace which agreement's terms were used for pricing | Add FK to billing_agreements |
No issued_at column on billing_invoices | Cannot record when invoice transitioned from draft to issued | Add issued_at timestamp column |
No settled_at column on billing_invoices | Cannot record when invoice became fully paid | Add settled_at timestamp column |
No billing_entitlement_id on billing_invoice_items | Cannot link line items to the entitlement type being granted | Add FK to billing_entitlements |
No uuid on billing_invoices or billing_invoice_items | Inconsistent with other billing tables that all have uuid | Add uuid string [unique, not null] to both tables |
billing_product_price column name on items | Missing _id suffix — inconsistent with FK naming convention | Rename to billing_product_price_id |
amount_cents typed as string on billing_payments | Should be integer for monetary values | Fix DBML type to integer |
No admin_created_by_id / admin_updated_by_id naming | created_by / updated_by on invoices doesn't follow naming convention | Rename to match admin_created_by_id pattern used by Agreement |