---
id: STATE-ACC-TRX-007
title: Payment reversal and dispute
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: none
    meaning: No relevant reversal or dispute has been opened.
  - name: reversal processing
    meaning: >-
      A committed processor cancellation awaits its outcome without yet changing
      the receipt.
  - name: reversal completed
    meaning: Cancellation has returned value and removed its live allocations.
  - name: reversal failed
    meaning: The cancellation failed and the receipt is retained unchanged.
  - name: dispute open
    meaning: A processor inquiry or chargeback awaits resolution.
  - name: dispute won
    meaning: The dispute ended in the workspace's favor.
  - name: dispute lost
    meaning: The dispute ended adversely; a chargeback returns the receipt value.
  - name: dispute closed
    meaning: The dispute closed without a money outcome.
transitions:
  - from: none
    to: reversal completed
    trigger: record an immediate return
    guard: >-
      Method behavior must allow the reversal kind, with a reason and no live
      credit from the receipt.
    side_effects:
      - remove live allocations
      - append reversal facts
      - credit the associated paid fee invoice where applicable
    who: >-
      Staff with payment-void or reverse permission, or processor event
      handling.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/payments/payment-reverse.ts;
      services-accounting:modules/transactions/services/integrations/integration-events.ts;
      services-accounting:modules/transactions/db/payment-reversals.table.ts;
      services-accounting:modules/transactions/db/disputes.table.ts;
      services-accounting:modules/transactions/tests/integration/services/payments/payment-reversals.integration.spec.js#it
      reverses the live allocation so the line owes again, and the payment
      item’s facts sum to zero (FC-16)
  - from: none
    to: reversal processing
    trigger: request processor cancellation
    guard: The live electronic receipt must not yet be settled.
    side_effects:
      - commit the reversal before the provider call
    who: Staff with payment-void permission.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/payments/payment-reverse.ts;
      services-accounting:modules/transactions/services/integrations/integration-events.ts;
      services-accounting:modules/transactions/db/payment-reversals.table.ts;
      services-accounting:modules/transactions/db/disputes.table.ts;
      services-accounting:modules/transactions/tests/integration/services/payments/payment-reversals.integration.spec.js#it
      fails a void the processor says is already settled (payment_settled): the
      reversal failed, the payment untouched
  - from: reversal processing
    to: reversal completed
    trigger: confirm processor cancellation
    guard: The reversal must remain processing.
    side_effects:
      - remove allocations
      - record the negative reconciliation movement
    who: 'The cancellation call, scheduled recovery or refund event handling.'
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/payments/payment-reverse.ts;
      services-accounting:modules/transactions/services/integrations/integration-events.ts;
      services-accounting:modules/transactions/db/payment-reversals.table.ts;
      services-accounting:modules/transactions/db/disputes.table.ts;
      services-accounting:modules/transactions/tests/integration/services/payments/payment-reversals.integration.spec.js#it
      records the void’s settlement item beside the capture: kind void, minus
      2000
  - from: reversal processing
    to: reversal failed
    trigger: record cancellation failure
    guard: 'Provider outcome establishes refusal, including an already settled charge.'
    side_effects:
      - retain the unchanged receipt and save failure
    who: The cancellation call or scheduled recovery.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/payments/payment-reverse.ts;
      services-accounting:modules/transactions/services/integrations/integration-events.ts;
      services-accounting:modules/transactions/db/payment-reversals.table.ts;
      services-accounting:modules/transactions/db/disputes.table.ts;
      services-accounting:modules/transactions/tests/integration/services/payments/payment-reversals.integration.spec.js#it
      fails a void the processor says is already settled (payment_settled): the
      reversal failed, the payment untouched
  - from: none
    to: dispute open
    trigger: open a payment dispute
    guard: >-
      A live electronic receipt and an amount within its total are required;
      provider references deduplicate event creation.
    side_effects:
      - save the dispute record
    who: Staff with dispute-create permission or dispute-opened event handling.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/payments/payment-reverse.ts;
      services-accounting:modules/transactions/services/integrations/integration-events.ts;
      services-accounting:modules/transactions/db/payment-reversals.table.ts;
      services-accounting:modules/transactions/db/disputes.table.ts;
      services-accounting:modules/transactions/tests/integration/services/stripe/stripe-webhooks.integration.spec.js#it
      opens a chargeback from charge.dispute.created, reading the tenant through
      the PaymentIntent, once
  - from: dispute open
    to: dispute won
    trigger: resolve the dispute favorably
    guard: The dispute must remain open.
    side_effects:
      - save the favorable outcome
    who: Staff with dispute-update permission or dispute-updated event handling.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/payments/payment-reverse.ts;
      services-accounting:modules/transactions/services/integrations/integration-events.ts;
      services-accounting:modules/transactions/db/payment-reversals.table.ts;
      services-accounting:modules/transactions/db/disputes.table.ts;
      services-accounting:modules/transactions/tests/integration/services/payments/payment-reversals.integration.spec.js#it
      refuses closing a closed dispute (dispute_not_open), a dispute of a check
      (reversal_kind_mismatch) and an amount beyond the payment
  - from: dispute open
    to: dispute closed
    trigger: close the dispute without money movement
    guard: The dispute must remain open.
    side_effects:
      - save the answered outcome
    who: Staff with dispute-update permission or dispute-updated event handling.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/payments/payment-reverse.ts;
      services-accounting:modules/transactions/services/integrations/integration-events.ts;
      services-accounting:modules/transactions/db/payment-reversals.table.ts;
      services-accounting:modules/transactions/db/disputes.table.ts;
      services-accounting:modules/transactions/tests/integration/services/payments/payment-reversals.integration.spec.js#it
      refuses closing a closed dispute (dispute_not_open), a dispute of a check
      (reversal_kind_mismatch) and an amount beyond the payment
  - from: dispute open
    to: dispute lost
    trigger: resolve the dispute adversely
    guard: >-
      The dispute must remain open; only chargeback kind triggers receipt
      reversal.
    side_effects:
      - reverse the complete receipt for a lost chargeback
    who: Staff with dispute-update permission or dispute-updated event handling.
    provenance: observed
    source: >-
      services-accounting:modules/transactions/services/payments/payment-reverse.ts;
      services-accounting:modules/transactions/services/integrations/integration-events.ts;
      services-accounting:modules/transactions/db/payment-reversals.table.ts;
      services-accounting:modules/transactions/db/disputes.table.ts;
      services-accounting:modules/transactions/tests/integration/services/payments/dispute-inquiry-lost.integration.spec.js#it
      writes no payment reversal and leaves the payment succeeded, nothing
      reversed
invalid:
  - from: reversal completed
    to: reversal processing
    why: A completed reversal blocks another nonfailed reversal of that receipt.
  - from: dispute lost
    to: dispute open
    why: Final dispute resolution cannot reopen the same dispute.
  - from: dispute won
    to: dispute lost
    why: A favorable final outcome cannot later become an adverse outcome.
concurrency: >-
  A partial unique index limits nonfailed reversals. Consumed-event records
  deduplicate processor delivery within the outcome transaction.
invariants:
  - INV-DOM-ACC-TRX-003-11
  - INV-DOM-ACC-TRX-003-18
evidence:
  - >-
    services-accounting at f47cb263: services/payments/payment-reverse.ts;
    db/payment-reversals.table.ts, disputes.table.ts;
    services/integrations/integration-events.ts.
---
# Payment reversal and dispute

## The Lifecycle

Reversal and dispute are separate supporting records on a receipt. A lost inquiry records its outcome without the chargeback money movement.

```mermaid
stateDiagram-v2
  direction LR
  state "none" as s0
  state "reversal processing" as s1
  state "reversal completed" as s2
  state "reversal failed" as s3
  state "dispute open" as s4
  state "dispute won" as s5
  state "dispute lost" as s6
  state "dispute closed" as s7
  s0 --> s2 : record an immediate return
  s0 --> s1 : request processor cancellation
  s1 --> s2 : confirm processor cancellation
  s1 --> s3 : record cancellation failure
  s0 --> s4 : open a payment dispute
  s4 --> s5 : resolve the dispute favorably
  s4 --> s7 : close the dispute without money movement
  s4 --> s6 : resolve the dispute adversely
```

## 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

A partial unique index limits nonfailed reversals. Consumed-event records deduplicate processor delivery within the outcome transaction. Processor cancellation can require scheduled lookup or human resolution after an uncertain response.
