Skip to content

DueStatus

Owner context: partners | Entity: Due | Field: status | Values: 3

The lifecycle of a single affiliate commission owed to a partner. A Due starts open when a shift confirms, enters settling when it is grouped into a PaymentRun that begins withdrawing, and reaches paid when that run's outbound transfer confirms on-chain. Forward-only: there are no failure branches and paid is the only sink.

Transitions of a Due are driven by its parent PaymentRun, not by per-Due services. The repository exposes updateStatusByPaymentRunId(...) to bulk-update every due belonging to a run in one call. If you want to know why a due moved, look at what the run did.

States

StateTerminalDescriptionEntry condition
openNoCommission accrued, not yet grouped into a payoutDueCreator on ShiftConfirmedDomainEvent
settlingNoParent PaymentRun is in withdrawing — the payout is in flightPaymentRunWithdrawInitiator bulk-update
paidYes (sink)The run's transfer confirmed; the partner has been paidPaymentRunTransferCompletionChecker bulk-update

Helpers: isOpen(), isSettling(), isPaid(). No isFinal(), no canTransition(), no TRANSITIONS matrix.

Transitions

Transition Table

FromToTrigger (service)Invariant / guardSide effects
(new) → openDueCreator subscribed to ShiftConfirmedDomainEventshift.status.isConfirmed()Emits DueCreatedDomainEvent
open → settlingPaymentRunWithdrawInitiatorParent PaymentRun enters withdrawing; vault has sufficient funds for the pre-payout tradesBulk-updates every Due with paymentRunId = run.id; emits PaymentRunWithdrawInitiatedDomainEvent on the run
settling → paidPaymentRunTransferCompletionCheckerCumulative transfer amount ≥ payout amount and no failed transfers on the runBulk-updates every Due with paymentRunId = run.id; also moves the parent PaymentRun → paid

There are no reverse transitions, no per-Due cancellations, and no failure state. A payout that stalls leaves the Due in settling until the run recovers or an operator intervenes directly on the repository.

Invariants

  1. Initial state is always openDueCreator.ts:73 hardcodes DueStatus.open on creation.
  2. paid is the only sink — nothing moves a due out of paid. No reversal, no unpaid state.
  3. No failure branches — the enum has three forward states and nothing else. A failed payout leaves the due in settling; recovery is out-of-band.
  4. State is a cascade from PaymentRun — neither settling nor paid are set by a per-Due service. They come from bulk updates driven by the parent run. Do not expect a DueStatusChangedDomainEvent — there is none.
  5. Grouping into a PaymentRun is what lets the due move — an open due with paymentRunId = null will stay open forever. The grouping happens when PaymentRunRateQuoter builds a new run; see PaymentRunStatus.
  6. kind is always affiliate today — the Due entity carries a kind field (DueKind), but no non-affiliate due is created anywhere in the codebase.

Relationship with PaymentRun

A Due tracks one commission on one confirmed shift. A PaymentRun groups many dues destined for the same partner for a given payout cycle. The paymentRunId field on Due is nullable — it is set when the run is built around the due, and from that moment the run's lifecycle drives the due's status.

PaymentRun.statusEffect on its Dues
created, ratedDues remain open — the run is still being assembled/priced
withdrawingPaymentRunWithdrawInitiator flips every grouped due to settling
paidPaymentRunTransferCompletionChecker flips every grouped due to paid

Code Pointers

  • Owner context: partners
  • Parent state machine: PaymentRunStatus — controls the transitions of this one
  • Sibling projection (also in PaymentRun): PaymentRunTradeStatus
  • Upstream flow that creates Dues: shift-lifecycle (step 9)
  • Flow that consumes Dues and drives them to paid: payment-run (to be documented)