---
id: STATE-ACC-TRX-010
title: Refund lifecycle
object: DOM-ACC-TRX-003
product_area: accounting
module: transactions
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: not created
    meaning: No return has been requested.
  - name: pending approval
    meaning: Configured approval is outstanding and refundable money is reserved.
  - name: requested
    meaning: The return is approved or needs no approval and awaits payment.
  - name: processing
    meaning: The committed electronic return awaits its provider outcome.
  - name: succeeded
    meaning: The provider confirmed the money return.
  - name: recorded offline
    meaning: Staff recorded the manual money return.
  - name: failed
    meaning: The return failed and released lines were restored.
  - name: cancelled
    meaning: The unpaid request was withdrawn.
transitions:
  - from: not created
    to: requested
    trigger: request return of money
    guard: >-
      The amount must be positive and within its source; check details are
      allowed only for check treatment.
    side_effects:
      - reserve available money
      - save the return request
    who: Staff with refund-create permission or the Commerce reversals function.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      refuses more than an allocation holds (over_refund) and more than the
      unapplied money (over_refund)
  - from: not created
    to: pending approval
    trigger: request an approval-gated return
    guard: Workspace refund approval must be enabled.
    side_effects:
      - reserve available money pending review
    who: Staff with refund-create permission.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      requests it pending_approval, refusing to execute it
      (refund_not_requested) or to let the requester approve it (self_approval)
  - from: pending approval
    to: requested
    trigger: approve the return
    guard: The approver must differ from the requester.
    side_effects:
      - save approval history
    who: Staff with refund-approve permission.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      requests it pending_approval, refusing to execute it
      (refund_not_requested) or to let the requester approve it (self_approval)
  - from: pending approval
    to: cancelled
    trigger: withdraw an unapproved return
    guard: A reason is required.
    side_effects:
      - release reserved money
    who: Staff with refund-create permission.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      refuses executing it again (refund_not_requested) and cancelling it
      (refund_not_cancellable)
  - from: requested
    to: cancelled
    trigger: withdraw an unpaid return
    guard: A reason and an unpaid request are required.
    side_effects:
      - release reserved money
    who: Staff with refund-create permission.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      refuses executing it again (refund_not_requested) and cancelling it
      (refund_not_cancellable)
  - from: requested
    to: recorded offline
    trigger: record a manual return
    guard: The request must be ready for execution.
    side_effects:
      - release the returned allocations
      - create a memo unless debt is to reopen
      - increase refunded amounts
    who: Staff with refund-execute permission.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      records it offline: executed, the payment partially_refunded with 1000
      refunded (FC-11, FC-14)
  - from: requested
    to: processing
    trigger: start an electronic return
    guard: The request must be ready and return through the source merchant account.
    side_effects:
      - release the affected lines
      - commit processing before calling the provider
    who: Staff with refund-execute permission or billing-reversal coordination.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      leaves the payment as it was: nothing unapplied, refunded or pending, its
      facts unchanged
  - from: processing
    to: succeeded
    trigger: confirm money returned
    guard: The return must remain processing.
    side_effects:
      - save provider reference and reconciliation movement
      - credit the applicable fee share
    who: >-
      The execution call, recovery worker, event handler or authorized human
      resolution.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/reversals/reversal-refund-status.integration.spec.js#it
      turns succeeded and completed when the signed event settles the refund
  - from: processing
    to: failed
    trigger: confirm return failure
    guard: The return must remain processing.
    side_effects:
      - restore released line applications
      - release reserved money
    who: >-
      The execution call, recovery worker, event handler or authorized human
      resolution.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      leaves the payment as it was: nothing unapplied, refunded or pending, its
      facts unchanged
  - from: processing
    to: processing
    trigger: retain uncertain return
    guard: The provider outcome remains unknown or pending.
    side_effects:
      - keep money reserved
      - advance lookup recovery and escalate when exhausted
    who: Scheduled recovery.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/refunds/refund-commands.ts;
      services-accounting:modules/transactions/db/refunds.table.ts;
      services-accounting:modules/transactions/tests/integration/services/refunds/refund-timeout-before-send.integration.spec.js#it
      leaves the refund processing a day on, never failed as never received and
      never abandoned
invalid:
  - from: succeeded
    to: cancelled
    why: A paid or withdrawn refund cannot be cancelled again.
  - from: pending approval
    to: recorded offline
    why: Outstanding approval prevents manual payment of the request.
  - from: processing
    to: cancelled
    why: >-
      Processing refunds wait for recovery or human resolution rather than
      cancellation or abandonment.
concurrency: >-
  Database reservation maintenance excludes held money from competing uses.
  Provider outcomes update only a refund that still remains processing.
invariants:
  - INV-DOM-ACC-TRX-003-13
  - INV-DOM-ACC-TRX-003-14
  - INV-DOM-ACC-TRX-003-15
evidence:
  - >-
    services-accounting at f47cb263: services/refunds/refund-commands.ts;
    db/refunds.table.ts, refund-lines.table.ts;
    docs/architecture/accounting-2.0/05-lifecycles.md §4.
---
# Refund lifecycle

## The Lifecycle

Refund separates approval and payment from the financial source of the return. Processing uncertainty retains its reservation, unlike charge abandonment.

```mermaid
stateDiagram-v2
  direction LR
  state "not created" as s0
  state "pending approval" as s1
  state "requested" as s2
  state "processing" as s3
  state "succeeded" as s4
  state "recorded offline" as s5
  state "failed" as s6
  state "cancelled" as s7
  s0 --> s2 : request return of money
  s0 --> s1 : request an approval-gated return
  s1 --> s2 : approve the return
  s1 --> s7 : withdraw an unapproved return
  s2 --> s7 : withdraw an unpaid return
  s2 --> s5 : record a manual return
  s2 --> s3 : start an electronic return
  s3 --> s4 : confirm money returned
  s3 --> s6 : confirm return failure
  s3 --> s3 : retain uncertain return
```

## Transitions Explained

The transition rows identify the initiating action, eligibility checks, resulting records and responsible caller. They retain the pinned evidence for each path.

## What Cannot Happen

The invalid-transition rows describe final-state protection and unsupported paths; command eligibility remains distinct from the declared state vocabulary.

## Concurrency and Timing

Database reservation maintenance excludes held money from competing uses. Provider outcomes update only a refund that still remains processing. Electronic returns use scheduled recovery and attention escalation; no day-based abandonment closes them.
