Skip to main content

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

ContextDetails
AggregateBilling::Invoice (root) + Billing::InvoiceItem (child) + Billing::Payment (child) + Billing::InvoicePosting (child)
LayerCommercial Documents
Upstream dependenciesBilling::Account, Billing::Agreement (terms inform pricing), Billing::LegalEntity (seller), Billing::BillToProfile (buyer)
Downstream dependentsBilling::LedgerEntry (posting writes grants), Billing::EntitlementLot (gig posting creates lots), Billing::EntitlementBalance (updated on posting)

State Machine

Invoice status

FromToTriggerNotes
(new)draftUC-1: Invoice createdDefault status on creation
draftissuedUC-2: Admin issues the invoiceInvoice becomes immutable
draftvoidUC-8: Admin voids before issuingCreated in error
issuedpartially_paidUC-5: First verified payment, < totalAwaiting remaining balance
issuedpaidUC-5: Verified payments >= totalTriggers posting (UC-7)
issuedvoidUC-8: Admin voids before paymentCustomer cancels
partially_paidpaidUC-5: Cumulative verified payments >= totalTriggers posting (UC-7)
paidcredited(future) Credit note / refund workflowPost-payment correction

Payment status

FromToTriggerNotes
(new)submittedUC-4: Admin records paymentProof provided, awaiting review
submittedverifiedUC-5: Finance confirms receiptUpdates invoice status
submittedrejectedUC-6: Proof invalidNo effect on invoice

Use Cases

IDUse CaseTriggerActor
UC-1Create an invoice for a company's credit purchaseCompany wants to purchase creditsAdmin (sales)
UC-2Issue a draft invoice for customer paymentAdmin confirms draft invoice is correctAdmin (sales)
UC-3Edit a draft invoice before issuanceAdmin needs to correct details before issuingAdmin (sales)
UC-4Record a bank transfer payment against an invoiceCustomer provides proof of bank transferAdmin (ops)
UC-5Verify a payment and settle the invoiceFinance confirms money received in bank accountAdmin (finance)
UC-6Reject an invalid payment submissionProof is invalid or money not receivedAdmin (finance)
UC-7Post a paid invoice to grant entitlementsInvoice transitions to paidSystem
UC-8Void an unpaid invoiceInvoice created in error or customer cancelsAdmin
UC-9View invoice details with line items and paymentsAdmin needs to review a specific invoiceAdmin
UC-10View all invoices for a billing accountAdmin needs to see all invoices for a companyAdmin

UC-1: Create an invoice for a company's credit purchase

FieldDetails
ActorIdentities::Admin (sales/ops)
TriggerCompany wants to purchase entitlements (placement credits or gig credits)

Preconditions:

  • Billing::Account exists for the Org::Company
  • An active Billing::Agreement exists with relevant terms
  • Billing::Product and Billing::ProductPrice exist for the desired entitlement and market
  • Billing::LegalEntity exists for the seller jurisdiction
  • Billing::BillToProfile exists for the buyer

System Behavior:

  1. Admin selects the billing account, product, and quantity
  2. System resolves the applicable Billing::ProductPrice based on:
    • country_id matching the company's country
    • active_from / active_until date range
  3. System resolves the active Billing::Agreement to obtain negotiated terms (fee rates, unit prices, discounts)
  4. System generates the invoice in draft status:
    • Snapshots bill-to fields from Billing::BillToProfile (company name, attention, email, address)
    • Sets currency from the bill-to profile
    • Associates the seller Billing::LegalEntity
    • Generates invoice_number using the legal entity's prefix/suffix
  5. System creates Billing::InvoiceItem rows:
    • 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 metadata with platform_fee_rate_bps, principal_amount_cents, platform_fee_amount_cents
  6. System computes and stores subtotal_cents, tax_cents, total_cents on the invoice
  7. 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_number is unique per (invoice_number, billing_account_id)
  • Pricing is never hard-coded — it comes from Billing::ProductPrice

Postconditions:

  • Invoice exists in draft status with computed totals
  • No payments or postings exist yet
  • No entitlements are granted

UC-2: Issue a draft invoice for customer payment

FieldDetails
ActorIdentities::Admin
TriggerAdmin confirms the draft invoice is correct and ready to send

Preconditions:

  • Invoice exists in draft status

System Behavior:

  1. Admin reviews the draft invoice and confirms issuance
  2. System transitions the invoice to issued status
  3. System records issued_at timestamp
  4. 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

FieldDetails
ActorIdentities::Admin
TriggerAdmin needs to correct details before issuing

Preconditions:

  • Invoice exists in draft status

System Behavior:

  1. Admin modifies invoice fields (due date, bill-to details) and/or adds, removes, or updates line items
  2. System recomputes subtotal_cents, tax_cents, total_cents
  3. 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

FieldDetails
ActorIdentities::Admin (business/ops)
TriggerCustomer makes a bank transfer and provides proof of payment

Preconditions:

  • Invoice exists in issued or partially_paid status

System Behavior:

  1. Admin creates a Billing::Payment record with:
    • method = bank_transfer
    • status = submitted
    • amount_cents = transfer amount
    • bank_reference = reference number from bank statement
    • proof_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 submitted status does not affect the invoice status — it requires verification first

Postconditions:

  • Payment record exists in submitted status
  • Invoice status unchanged

UC-5: Verify a payment and settle the invoice

FieldDetails
ActorIdentities::Admin (finance)
TriggerFinance confirms money has been received in the bank account

Preconditions:

  • Payment exists in submitted status
  • Invoice is in issued or partially_paid status

System Behavior:

  1. Finance admin marks the payment as verified
  2. System records verified_at, verified_by_admin_id, and received_at
  3. System recomputes verified_total = sum(amount_cents) across all verified payments for this invoice
  4. System updates the invoice status:
    • If verified_total == 0 → stays issued
    • If 0 < verified_total < total_centspartially_paid
    • If verified_total >= total_centspaid, and settled_at = now
  5. If invoice becomes paid, the system automatically triggers posting (UC-7)

Business Rules:

  • Only submitted payments 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

FieldDetails
ActorIdentities::Admin (finance)
TriggerProof is invalid or money was not received

Preconditions:

  • Payment exists in submitted status

System Behavior:

  1. Finance admin marks the payment as rejected
  2. System records the rejection (no recomputation of invoice status needed — rejected payments don't count)

Business Rules:

  • Only submitted payments 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

FieldDetails
ActorSystem (triggered automatically when invoice becomes paid)
TriggerInvoice transitions to paid status

Preconditions:

  • Invoice status = paid
  • No Billing::InvoicePosting exists for this invoice (idempotency guard)

System Behavior:

  1. Lock the invoice row (SELECT ... FOR UPDATE)
  2. Create Billing::InvoicePosting with posted_at and idempotency_key
  3. For each Billing::InvoiceItem:
    • Placement credits: write Billing::LedgerEntry with entry_type = grant, available_delta = +units_to_grant, deferred_revenue_delta_cents = +amount_cents
    • Gig credits (principal item): write Billing::LedgerEntry with grant + create Billing::EntitlementLot with platform_fee_rate_bps from item metadata, platform_fee_total_cents, and units_purchased = units_to_grant
    • Gig credits (platform fee item): record platform_fee_deferred_delta_cents on the ledger entry (no units granted — this is a money-only line)
  4. Update Billing::EntitlementBalance projections:
    • units_available += units_to_grant
    • deferred_revenue_cents += amount (placement)
    • platform_fee_deferred_cents += fee (gig)
  5. All of the above happens in a single DB transaction

Business Rules:

  • Unique constraint on Billing::InvoicePosting.billing_invoice_id prevents double-granting (idempotency)
  • Posting is atomic — if any step fails, the entire transaction rolls back
  • Each invoice can only be posted once

Postconditions:

  • Billing::InvoicePosting record 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

FieldDetails
ActorIdentities::Admin
TriggerInvoice was created in error, or customer cancels before paying

Preconditions:

  • Invoice is in draft or issued status
  • No verified payments exist for this invoice

System Behavior:

  1. Admin voids the invoice
  2. System transitions the invoice to void status

Business Rules:

  • Cannot void an invoice that has verified payments — those must be dealt with first
  • Cannot void a paid or credited invoice
  • 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

FieldDetails
ActorIdentities::Admin
TriggerAdmin needs to review a specific invoice

Preconditions:

  • Invoice exists

System Behavior:

  1. Admin navigates to the invoice detail page
  2. 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

FieldDetails
ActorIdentities::Admin
TriggerAdmin needs to see all invoices for a company's billing account

Preconditions:

  • Billing::Account exists

System Behavior:

  1. Admin navigates to the billing account → Invoices tab
  2. System displays all invoices ordered by created_at descending
  3. 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

  1. An invoice must belong to exactly one Billing::Account
  2. An invoice must have at least one Billing::InvoiceItem
  3. Once issued, invoice amounts and items are immutable — corrections require void + reissue
  4. Invoices are never deleted — only voided or credited
  5. invoice_number is unique per (invoice_number, billing_account_id)
  6. Entitlements are granted only after the invoice reaches paid status — partial payments do not grant partial credits
  7. Each invoice can be posted at most once (enforced by unique constraint on Billing::InvoicePosting.billing_invoice_id)
  8. Invoice items snapshot all pricing, tax, and terms at creation time — later changes to products or agreements do not affect existing invoices
  9. Gig credit invoices must have two line items: principal (tax-exempt) and platform fee (taxed)
  10. total_cents = subtotal_cents + tax_cents and must equal the sum of all item (amount_cents + tax_cents)
  11. Admin audit trail (created_by, updated_by) is always populated on invoices

Model Interactions

Related ModelRelationshipInteraction
Billing::AccountInvoice belongs_to AccountAccount must exist. One account has many invoices over time.
Billing::AgreementInvoice references AgreementAgreement's terms inform pricing at invoice creation. Invoice snapshots the relevant values.
Billing::LegalEntityInvoice belongs_to LegalEntitySeller-of-record. Determines invoice numbering, tax regime, and currency.
Billing::BillToProfileInvoice belongs_to BillToProfileBuyer details source. Invoice snapshots bill-to fields at creation (never joins back for historical invoices).
Billing::InvoiceItemInvoice has_many InvoiceItemsLine items created with the invoice. Snapshot product, pricing, tax, and units to grant.
Billing::PaymentInvoice has_many PaymentsTracks offline bank transfers. Multiple payments supported for partial payments.
Billing::InvoicePostingInvoice has_one InvoicePostingIdempotent record of entitlement granting. Created once when invoice becomes paid.
Billing::ProductInvoiceItem references ProductWhat was sold (placement credits, gig credits). Provides grants_units_per_quantity.
Billing::ProductPriceInvoiceItem references ProductPriceMarket-specific pricing used at issuance. Values snapshotted into item columns.
Billing::LedgerEntryPosting creates LedgerEntriesGrant entries written to the append-only ledger during posting.
Billing::EntitlementLotPosting creates Lots (gig only)FIFO purchase batch created during gig posting with per-lot platform fee rate.
Billing::EntitlementBalancePosting updates Balancesunits_available and deferred revenue/fee projections updated during posting.
Identities::AdminInvoice tracks created_by / updated_byAudit trail. Payment verification also records verified_by_admin_id.

Schema Gaps

Items identified by comparing use cases against the current DBML schema (billing.dbml):

GapImpactSuggested Resolution
No billing_agreement_id on billing_invoicesCannot trace which agreement's terms were used for pricingAdd FK to billing_agreements
No issued_at column on billing_invoicesCannot record when invoice transitioned from draft to issuedAdd issued_at timestamp column
No settled_at column on billing_invoicesCannot record when invoice became fully paidAdd settled_at timestamp column
No billing_entitlement_id on billing_invoice_itemsCannot link line items to the entitlement type being grantedAdd FK to billing_entitlements
No uuid on billing_invoices or billing_invoice_itemsInconsistent with other billing tables that all have uuidAdd uuid string [unique, not null] to both tables
billing_product_price column name on itemsMissing _id suffix — inconsistent with FK naming conventionRename to billing_product_price_id
amount_cents typed as string on billing_paymentsShould be integer for monetary valuesFix DBML type to integer
No admin_created_by_id / admin_updated_by_id namingcreated_by / updated_by on invoices doesn't follow naming conventionRename to match admin_created_by_id pattern used by Agreement