Traits / SaaSBillingPayable
SaaSBillingPayable
Captures invoice level billing terms, status, and outstanding balance metadata for SaaS finance teams.
Generated from @oods/foundry 0.10.1
- Group
- domain
- Maturity
- beta
- Contexts
- card, list
Fields it adds
| Field | Type | Required | Description |
|---|---|---|---|
invoice_number | string | yes | Human readable invoice identifier presented to customers. |
status | string | yes | Canonical invoice status after provider mapping. |
provider_status | string | no | Original provider reported status or badge. |
issued_at | datetime | yes | Timestamp when the invoice was finalized or posted. |
due_at | datetime | no | Payment due timestamp derived from payment terms. |
paid_at | datetime | no | Timestamp when full payment cleared. |
total_minor | integer | yes | Total amount due expressed in minor currency units. |
balance_minor | integer | no | Remaining amount outstanding in minor units. |
currency | string | yes | ISO 4217 currency for the invoice. |
payment_terms | string | no | Payment term label describing when payment is expected. |
collection_state | string | no | Dunning phase or retry program detail presented to success teams. |
last_reminder_at | datetime | no | Timestamp of most recent payment reminder issued to customer. |
aging_bucket_days | integer | no | Number of days outstanding grouped for aging reports. |
attempt_count | integer | no | Number of automatic payment attempts made on this invoice (Stripe smart-retries attempt_count). Increments on each retry in the dunning window. Source: docs.stripe.com/billing/revenue-recovery/smart-retries. |
next_payment_attempt | datetime | no | When the next automatic payment retry is scheduled (Stripe smart-retries next_payment_attempt). Defines the open end of the retry window — modeled on the Refundable.refundable_until datetime-window precedent. Absent/null when no further attempts are scheduled (retries exhausted → invoice goes uncollectible, mirroring the subscription `unpaid` state). Source: docs.stripe.com/billing/revenue-recovery/smart-retries. |
memo | string | no | Internal finance note or customer visible memo. |
What it shows in each context
Parameters
statusEnumstring[], required- Canonical invoice status values allowed by the domain.
paymentTermsstring[]- Supported payment term descriptors (ex: net_15, due_upon_receipt).
Objects that use it
The trait file
domains/saas-billing/traits/payable.trait.yaml
trait:
name: SaaSBillingPayable
version: 1.0.0
description: Captures invoice level billing terms, status, and outstanding balance metadata for SaaS finance teams.
category: domain
tags:
- billing
- invoice
- domain
parameters:
- name: statusEnum
type: string[]
required: true
description: Canonical invoice status values allowed by the domain.
default:
- draft
- posted
- open
- processing
- past_due
- paid
- refunded
- uncollectible
- void
- name: paymentTerms
type: string[]
required: false
description: "Supported payment term descriptors (ex: net_15, due_upon_receipt)."
default:
- due_upon_receipt
- net_15
- net_30
- net_45
schema:
invoice_number:
type: string
required: true
description: Human readable invoice identifier presented to customers.
status:
type: string
required: true
description: Canonical invoice status after provider mapping.
validation:
enumFromParameter: statusEnum
provider_status:
type: string
required: false
description: Original provider reported status or badge.
issued_at:
type: datetime
required: true
description: Timestamp when the invoice was finalized or posted.
due_at:
type: datetime
required: false
description: Payment due timestamp derived from payment terms.
paid_at:
type: datetime
required: false
description: Timestamp when full payment cleared.
total_minor:
type: integer
required: true
description: Total amount due expressed in minor currency units.
balance_minor:
type: integer
required: false
description: Remaining amount outstanding in minor units.
default: 0
currency:
type: string
required: true
description: ISO 4217 currency for the invoice.
payment_terms:
type: string
required: false
description: Payment term label describing when payment is expected.
validation:
enumFromParameter: paymentTerms
collection_state:
type: string
required: false
description: Dunning phase or retry program detail presented to success teams.
last_reminder_at:
type: datetime
required: false
description: Timestamp of most recent payment reminder issued to customer.
aging_bucket_days:
type: integer
required: false
description: Number of days outstanding grouped for aging reports.
attempt_count:
type: integer
required: false
description: >-
Number of automatic payment attempts made on this invoice (Stripe smart-retries
attempt_count). Increments on each retry in the dunning window. Source:
docs.stripe.com/billing/revenue-recovery/smart-retries.
default: 0
validation:
minimum: 0
next_payment_attempt:
type: datetime
required: false
description: >-
When the next automatic payment retry is scheduled (Stripe smart-retries
next_payment_attempt). Defines the open end of the retry window — modeled on the
Refundable.refundable_until datetime-window precedent. Absent/null when no further
attempts are scheduled (retries exhausted → invoice goes uncollectible, mirroring
the subscription `unpaid` state). Source: docs.stripe.com/billing/revenue-recovery/smart-retries.
memo:
type: string
required: false
description: Internal finance note or customer visible memo.
semantics:
invoice_number:
semantic_type: billing.invoice.number
token_mapping: tokenMap(billing.invoice.number)
status:
semantic_type: billing.invoice.status
token_mapping: tokenMap(billing.invoice.status.*)
provider_status:
semantic_type: billing.invoice.provider_status
token_mapping: tokenMap(billing.invoice.provider_status)
issued_at:
semantic_type: billing.invoice.issued_at
token_mapping: tokenMap(billing.invoice.issued_at)
due_at:
semantic_type: billing.invoice.due_at
token_mapping: tokenMap(billing.invoice.due_at)
paid_at:
semantic_type: billing.invoice.paid_at
token_mapping: tokenMap(billing.invoice.paid_at)
total_minor:
semantic_type: billing.invoice.total_minor
token_mapping: tokenMap(billing.invoice.total_minor)
ui_hints:
component: CurrencyAmount
currencyField: currency
minorUnits: 100
balance_minor:
semantic_type: billing.invoice.balance_minor
token_mapping: tokenMap(billing.invoice.balance_minor)
ui_hints:
component: CurrencyAmount
currencyField: currency
minorUnits: 100
currency:
semantic_type: billing.invoice.currency
token_mapping: tokenMap(billing.invoice.currency)
payment_terms:
semantic_type: billing.invoice.payment_terms
token_mapping: tokenMap(billing.invoice.payment_terms)
collection_state:
semantic_type: billing.invoice.collection_state
token_mapping: tokenMap(billing.invoice.collection_state)
last_reminder_at:
semantic_type: billing.invoice.last_reminder_at
token_mapping: tokenMap(billing.invoice.last_reminder_at)
aging_bucket_days:
semantic_type: billing.invoice.aging_bucket_days
token_mapping: tokenMap(billing.invoice.aging_bucket_days)
attempt_count:
semantic_type: billing.invoice.attempt_count
token_mapping: tokenMap(billing.invoice.attempt_count)
next_payment_attempt:
semantic_type: billing.invoice.next_payment_attempt
token_mapping: tokenMap(billing.invoice.next_payment_attempt)
memo:
semantic_type: billing.invoice.memo
token_mapping: tokenMap(billing.invoice.memo)
view_extensions:
# s222-m03 (#2502 ruling 13): an invoice's row, page header and card show its total, as a financial trait's recipes do.
list:
- component: BillingSummaryBadge
position: after
props:
amountField: total_minor
currencyField: currency
minorUnits: 100
showInterval: false
card:
- component: BillingSummaryBadge
position: after
props:
amountField: total_minor
currencyField: currency
minorUnits: 100
showInterval: false
tokens:
billing.invoice.number: "var(--cmp-text-strong)"
billing.invoice.total_minor: "var(--cmp-text-body-strong)"
billing.invoice.balance_minor: "var(--cmp-text-warning)"
billing.invoice.status.past_due: "var(--cmp-status-critical-text)"
billing.invoice.status.paid: "var(--cmp-status-success-text)"
metadata:
owners:
- revenue-ops@oods.systems
maturity: beta
regionsUsed:
- list
- card
notes:
- Canonical status options sourced from r4.5 mapping research.
- >-
Dunning/access reconciliation (s126-m05, scoped to the access-revocation need):
the 9-value statusEnum is a SUPERSET of the canonical 5-state invoice model
(draft, posted, paid, past_due, void — see docs/billing/lifecycle-states.md).
Dunning-relevant mapping: `open`/`processing` = pre-dunning (awaiting/settling
payment); `past_due` = overdue, retries ongoing → subscription past_due (GRACE,
keep access); `uncollectible` = retries exhausted → mirrors subscription `unpaid`
(REVOKE access). The retry window is carried by attempt_count + next_payment_attempt.
The subscription-status → access decision is encoded in
src/domain/billing/states.ts hasServiceAccess().