Fields it adds

FieldTypeRequiredDescription
amount integer yes Recurring price expressed in minor currency units (e.g., cents).
currency string yes ISO 4217 currency code for the billing amount.
billing_interval string yes Recurrence cadence label selected from the billingIntervals parameter.
payment_status string (pending, succeeded, failed, retrying, refunded) no Outcome of the most recent collection attempt. Distinct from subscription lifecycle status — a subscription can be "active" with payment_status "retrying".
payment_method_type string (card, ach, wire, invoice, other) no Payment instrument category used for collection.
proration_amount integer no Prorated credit or charge in minor currency units generated by a mid-cycle plan change. Positive values are charges; negative values are credits. Only populated when the supportProration parameter is true.
proration_date integer no Unix timestamp (epoch SECONDS) at which proration is calculated. Pinned when an upcoming-invoice PREVIEW is requested and MUST be passed identically on commit so the committed charge equals the previewed amount. The preview is read-only and does NOT mutate the subscription. Stored as an integer (not a datetime) so the value round-trips byte-identically between preview and commit — an ISO string normalization could shift it and desync the previewed vs charged amount. Only populated when supportProration is true. Source: docs.stripe.com/billing/subscriptions/prorations.
last_payment_at datetime no Timestamp of the most recent successful payment.
next_payment_due_at datetime no Timestamp when the next payment attempt is scheduled.
current_period_start datetime no Beginning timestamp of the active billing cycle.
current_period_end datetime no Ending timestamp of the active billing cycle.
current_period_progress number no Decimal (0.0–1.0) indicating progress through the active billing cycle.

What it shows in each context

list
BillingSummaryBadge
detail
CycleProgressCard, PaymentTimeline
form
BillingIntervalSelector, BillingAmountInput
timeline
PaymentEventTimeline
card
BillingCardMeta

Parameters

defaultCurrency string
ISO 4217 currency code used when a value is not explicitly provided.
billingIntervals string[]
Allowed billing interval values that downstream objects may select.
minorUnits number
Number of fractional units that constitute a single major currency unit (e.g., 100 for cents).
paymentTiming string
Whether the billing entity charges at the start of the period (prepaid) or at the end (postpaid). Affects cycle progress interpretation and dunning trigger points.
supportProration boolean
Whether mid-cycle plan changes generate prorated charges or credits. When true, proration_amount is computed on plan upgrades/downgrades.
prorationBehavior string
Default policy for how mid-cycle plan changes are billed, mirroring Stripe's proration_behavior on subscription updates. Drives charge TIMING, not the proration math, and is only meaningful when supportProration is true: - create_prorations: generate prorated line items, billed on the next invoice (default) - always_invoice: generate prorations AND invoice them immediately - none: apply the change with no proration line items / no immediate adjustment Source: docs.stripe.com/billing/subscriptions/prorations.
cycleAnchorDay number
Day of the month used to anchor renewal dates. When set, all billing periods align to this day regardless of subscription start date. Value of 0 means no anchoring (period starts from subscription creation). BILLING-ANCHOR-RESET DISCLOSURE: changing the billing interval — or otherwise resetting the billing cycle anchor (e.g. Stripe billing_cycle_anchor=now) — RESETS the anchor and starts a new billing period immediately. This interacts with proration: the reset can itself trigger proration for the current period and shifts the renewal date. Surface this consequence to the user BEFORE applying an interval change. Source: docs.stripe.com/billing/subscriptions/prorations.

Objects that use it

The trait file

traits/financial/Billable.trait.yaml
trait:
  name: Billable
  version: 2.1.0
  description: |
    Recurring billing mechanics trait that normalizes cycle tracking, payment timing,
    and period management for subscription-based entities. Billable owns the billing
    CYCLE — when payments occur, how periods progress, and what the payment state is.

    DISTINCT from Priceable — Priceable handles PRICING (amount, currency, models).
    Billable handles RECURRENCE (cycles, periods, payment timing, proration).
    A Subscription composes both: Priceable for "how much" and Billable for "when/how".

    Core concepts:
    - Billing Cycle: current_period_start → current_period_end with progress tracking
    - Payment Timing: prepaid (charge at period start) vs postpaid (charge at period end)
    - Proration: fractional billing when mid-cycle changes occur, with preview/commit
      timing (proration_date), a charge-timing policy (prorationBehavior), and
      billing-anchor-reset disclosure
    - Payment Status: tracks the outcome of the most recent collection attempt
    - Cycle Anchoring: fixed day-of-month for consistent renewal dates

    PRORATION SCOPE (trait-split gap): proration semantics live ONLY on this CORE
    financial/Billable trait. The saas-billing domain trait SaaSBillingBillable
    (domains/saas-billing/traits/billable.trait.yaml) does NOT expose proration, so a
    Subscription composing only SaaSBillingBillable silently CANNOT prorate. To enable
    proration for a SaaS subscription, compose financial/Billable (as the core
    Subscription object does, alias SubscriptionBilling).
  category: financial
  tags:
    - billing
    - recurring
    - financial
    - cycle
    - payment
    - subscription

parameters:
  - name: defaultCurrency
    type: string
    required: false
    description: ISO 4217 currency code used when a value is not explicitly provided.
    default: usd
    validation:
      pattern: "^[a-zA-Z]{3}$"
  - name: billingIntervals
    type: string[]
    required: false
    description: Allowed billing interval values that downstream objects may select.
    default:
      - monthly
      - quarterly
      - annual
  - name: minorUnits
    type: number
    required: false
    description: Number of fractional units that constitute a single major currency unit (e.g., 100 for cents).
    default: 100
    validation:
      minimum: 1
  - name: paymentTiming
    type: string
    required: false
    description: |
      Whether the billing entity charges at the start of the period (prepaid)
      or at the end (postpaid). Affects cycle progress interpretation and
      dunning trigger points.
    default: prepaid
    validation:
      enum:
        - prepaid
        - postpaid
  - name: supportProration
    type: boolean
    required: false
    description: |
      Whether mid-cycle plan changes generate prorated charges or credits.
      When true, proration_amount is computed on plan upgrades/downgrades.
    default: false
  - name: prorationBehavior
    type: string
    required: false
    description: |
      Default policy for how mid-cycle plan changes are billed, mirroring Stripe's
      proration_behavior on subscription updates. Drives charge TIMING, not the
      proration math, and is only meaningful when supportProration is true:
      - create_prorations: generate prorated line items, billed on the next invoice (default)
      - always_invoice: generate prorations AND invoice them immediately
      - none: apply the change with no proration line items / no immediate adjustment
      Source: docs.stripe.com/billing/subscriptions/prorations.
    default: create_prorations
    validation:
      enum:
        - create_prorations
        - always_invoice
        - none
  - name: cycleAnchorDay
    type: number
    required: false
    description: |
      Day of the month used to anchor renewal dates. When set, all billing
      periods align to this day regardless of subscription start date.
      Value of 0 means no anchoring (period starts from subscription creation).

      BILLING-ANCHOR-RESET DISCLOSURE: changing the billing interval — or otherwise
      resetting the billing cycle anchor (e.g. Stripe billing_cycle_anchor=now) — RESETS
      the anchor and starts a new billing period immediately. This interacts with
      proration: the reset can itself trigger proration for the current period and shifts
      the renewal date. Surface this consequence to the user BEFORE applying an interval
      change. Source: docs.stripe.com/billing/subscriptions/prorations.
    default: 0
    validation:
      minimum: 0
      maximum: 31

schema:
  amount:
    type: integer
    required: true
    description: Recurring price expressed in minor currency units (e.g., cents).
    validation:
      minimum: 0
  currency:
    type: string
    required: true
    description: ISO 4217 currency code for the billing amount.
    validation:
      pattern: "^[a-zA-Z]{3}$"
  billing_interval:
    type: string
    required: true
    description: Recurrence cadence label selected from the billingIntervals parameter.
    validation:
      enumFromParameter: billingIntervals
  payment_status:
    type: string
    required: false
    description: |
      Outcome of the most recent collection attempt. Distinct from subscription
      lifecycle status — a subscription can be "active" with payment_status "retrying".
    validation:
      enum:
        - pending
        - succeeded
        - failed
        - retrying
        - refunded
    default: pending
  payment_method_type:
    type: string
    required: false
    description: Payment instrument category used for collection.
    validation:
      enum:
        - card
        - ach
        - wire
        - invoice
        - other
  proration_amount:
    type: integer
    required: false
    description: |
      Prorated credit or charge in minor currency units generated by a mid-cycle
      plan change. Positive values are charges; negative values are credits.
      Only populated when the supportProration parameter is true.
    default: 0
  proration_date:
    type: integer
    required: false
    description: |
      Unix timestamp (epoch SECONDS) at which proration is calculated. Pinned when an
      upcoming-invoice PREVIEW is requested and MUST be passed identically on commit so the
      committed charge equals the previewed amount. The preview is read-only and does NOT
      mutate the subscription. Stored as an integer (not a datetime) so the value round-trips
      byte-identically between preview and commit — an ISO string normalization could shift it
      and desync the previewed vs charged amount. Only populated when supportProration is true.
      Source: docs.stripe.com/billing/subscriptions/prorations.
    validation:
      minimum: 0
  last_payment_at:
    type: datetime
    required: false
    description: Timestamp of the most recent successful payment.
  next_payment_due_at:
    type: datetime
    required: false
    description: Timestamp when the next payment attempt is scheduled.
  current_period_start:
    type: datetime
    required: false
    description: Beginning timestamp of the active billing cycle.
  current_period_end:
    type: datetime
    required: false
    description: Ending timestamp of the active billing cycle.
  current_period_progress:
    type: number
    required: false
    description: Decimal (0.0–1.0) indicating progress through the active billing cycle.
    validation:
      minimum: 0
      maximum: 1

semantics:
  amount:
    semantic_type: billing.cycle.amount_minor
    token_mapping: tokenMap(billing.amount.*)
    ui_hints:
      component: CurrencyAmount
      currencyField: currency
      minorUnitsParameter: minorUnits
  currency:
    semantic_type: billing.cycle.currency
    token_mapping: tokenMap(billing.currency.*)
    ui_hints:
      component: CurrencyCode
  billing_interval:
    semantic_type: billing.cycle.interval
    token_mapping: tokenMap(billing.interval.*)
    ui_hints:
      component: BillingIntervalBadge
      parameterSource: billingIntervals
  payment_status:
    semantic_type: billing.cycle.payment_status
    token_mapping: tokenMap(billing.payment.status.*)
    ui_hints:
      component: PaymentStatusBadge
  payment_method_type:
    semantic_type: billing.cycle.payment_method
    token_mapping: tokenMap(billing.payment.method.*)
    ui_hints:
      component: PaymentMethodIcon
  proration_amount:
    semantic_type: billing.cycle.proration
    token_mapping: tokenMap(billing.proration.*)
    ui_hints:
      minorUnitsParameter: minorUnits
      component: CurrencyAmount
      currencyField: currency
      signIndicator: true
  proration_date:
    # `timestamp` semantic: this field is a Unix-seconds point-in-time, so it groups with
    # the temporal/date fields (slot-expander categorizes on the `timestamp` token) rather
    # than landing uncategorized in an unrelated slot. ui_hints render it as a timestamp.
    semantic_type: billing.cycle.proration_timestamp
    token_mapping: tokenMap(billing.timestamp.proration)
    ui_hints:
      component: Timestamp
      unixSeconds: true
  last_payment_at:
    semantic_type: billing.cycle.last_payment_at
    token_mapping: tokenMap(billing.timestamp.last_payment)
    ui_hints:
      component: RelativeTimestamp
  next_payment_due_at:
    semantic_type: billing.cycle.next_payment_due_at
    token_mapping: tokenMap(billing.timestamp.next_payment)
    ui_hints:
      component: RelativeTimestamp
      urgencyThresholdDays: 3
  current_period_start:
    semantic_type: billing.cycle.period_start
    token_mapping: tokenMap(billing.timestamp.period_start)
    ui_hints:
      component: Timestamp
  current_period_end:
    semantic_type: billing.cycle.period_end
    token_mapping: tokenMap(billing.timestamp.period_end)
    ui_hints:
      component: Timestamp
  current_period_progress:
    semantic_type: billing.cycle.period_progress
    token_mapping: tokenMap(billing.cycle.progress)
    ui_hints:
      component: CycleProgressBar

view_extensions:
  list:
    - component: BillingSummaryBadge
      position: after
      props:
        amountField: amount
        currencyField: currency
        intervalField: billing_interval
        minorUnitsParameter: minorUnits
  detail:
    - component: CycleProgressCard
      position: main
      priority: 70
      props:
        progressField: current_period_progress
        periodStartField: current_period_start
        periodEndField: current_period_end
        intervalField: billing_interval
    - component: PaymentTimeline
      position: main
      priority: 60
      props:
        lastPaymentField: last_payment_at
        nextPaymentField: next_payment_due_at
        paymentStatusField: payment_status
        paymentMethodField: payment_method_type
        amountField: amount
        currencyField: currency
  form:
    - component: BillingIntervalSelector
      position: main
      priority: 80
      props:
        intervalField: billing_interval
        intervalsParameter: billingIntervals
    - component: BillingAmountInput
      position: main
      priority: 75
      props:
        amountField: amount
        currencyField: currency
        minorUnitsParameter: minorUnits
  timeline:
    - component: PaymentEventTimeline
      props:
        lastPaymentField: last_payment_at
        nextPaymentField: next_payment_due_at
        paymentStatusField: payment_status
        amountField: amount
        currencyField: currency
  card:
    - component: BillingCardMeta
      position: after
      props:
        amountField: amount
        currencyField: currency
        intervalField: billing_interval
        minorUnitsParameter: minorUnits

tokens:
  # Amount display tokens
  cmp.billing.amount.text: "var(--sys-text-strong)"
  cmp.billing.amount.currency: "var(--sys-text-subtle)"
  cmp.billing.amount.interval: "var(--sys-text-subtle)"
  # Cycle progress tokens
  cmp.billing.cycle.progress.track: "var(--sys-surface-neutral)"
  cmp.billing.cycle.progress.fill: "var(--sys-surface-accent)"
  cmp.billing.cycle.progress.text: "var(--sys-text-default)"
  cmp.billing.cycle.progress.height: "var(--space-1)"
  cmp.billing.cycle.progress.radius: "var(--radius-full)"
  # Payment status tokens
  cmp.billing.payment.succeeded.bg: "var(--sys-surface-success)"
  cmp.billing.payment.succeeded.text: "var(--sys-text-success)"
  cmp.billing.payment.failed.bg: "var(--sys-surface-critical)"
  cmp.billing.payment.failed.text: "var(--sys-text-critical)"
  cmp.billing.payment.retrying.bg: "var(--sys-surface-warning)"
  cmp.billing.payment.retrying.text: "var(--sys-text-warning)"
  cmp.billing.payment.pending.bg: "var(--sys-surface-info)"
  cmp.billing.payment.pending.text: "var(--sys-text-info)"
  # Timeline connector tokens
  cmp.billing.timeline.connector: "var(--sys-border-default)"
  cmp.billing.timeline.dot: "var(--sys-surface-accent)"
  cmp.billing.timeline.text: "var(--sys-text-default)"

dependencies: []
  # Recommended companions (not required):
  # - Priceable: provides pricing metadata (unit amount, pricing model, tax behavior).
  #   Billable provides billing cycle mechanics. A Subscription composes both.
  # - Timestampable: enriches payment timestamps with relative formatting.

metadata:
  created: "2025-10-16"
  updated: "2026-06-24"
  owners:
    - billing@oods.systems
    - finance@oods.systems
  maturity: experimental
  accessibility:
    keyboard: |
      BillingIntervalSelector is keyboard navigable via arrow keys.
      BillingAmountInput supports standard numeric input patterns.
    screenreader: |
      BillingSummaryBadge announces amount, currency, and interval as a single phrase.
      CycleProgressCard announces progress percentage and remaining days.
      PaymentStatusBadge announces payment outcome with tone-appropriate urgency.
  regionsUsed:
    - list
    - detail
    - form
    - timeline
    - card
  examples:
    - Subscription
    - Plan
  references:
    - "objects/provenance.v1.json#/references/ref-0d9aaa77f987 — BillableViewData, BillingSummary, PastDueAction"
    - "objects/provenance.v1.json#/references/ref-374e980524b1 — CanonicalSubscription, BillingInterval, BillingAccount"
    - "domains/saas-billing/traits/billable.trait.yaml — SaaS domain extension"
    - "objects/core/Subscription.object.yaml — Subscription object composition"
    - "objects/provenance.v1.json#/references/ref-be33a98177f7 — billing domain status tokens"