---
id: TST-ACC-TRX-109
title: Refunds suite
kind: integration
product_area: accounting
module: transactions
revision: 1
status: draft
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
repository: services-accounting
files:
  - >-
    modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js
implements:
  - ATS-ACC-TRX-012-01
  - ATS-ACC-TRX-012-04
  - ATS-ACC-TRX-012-05
  - ATS-ACC-TRX-012-06
  - ATS-ACC-TRX-012-07
  - ATS-ACC-TRX-012-08
  - ATS-ACC-TRX-012-09
implements_parts:
  - scenario: ATS-ACC-TRX-012-01
    assertion: >-
      it refuses more than an allocation holds (over_refund) and more than the
      unapplied money (over_refund)
    provenance: test
    source: >-
      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)
  - scenario: ATS-ACC-TRX-012-01
    assertion: >-
      it refuses a credit refund beyond the balance (over_refund) and one
      through a processor the credit did not come from (cash_account_mismatch)
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      refuses a credit refund beyond the balance (over_refund) and one through a
      processor the credit did not come from (cash_account_mismatch)
  - scenario: ATS-ACC-TRX-012-01
    assertion: it refuses a refund naming no money (nothing_to_refund)
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      refuses a refund naming no money (nothing_to_refund)
  - scenario: ATS-ACC-TRX-012-04
    assertion: >-
      it takes the money back from the allocation: a reversing row per refund,
      the original reversed by 3000
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      takes the money back from the allocation: a reversing row per refund, the
      original reversed by 3000
  - scenario: ATS-ACC-TRX-012-04
    assertion: >-
      it keeps the invoice settled for the credited 2000 (a credit memo line)
      and reopens the 1000: the line owes 1000
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      keeps the invoice settled for the credited 2000 (a credit memo line) and
      reopens the 1000: the line owes 1000
  - scenario: ATS-ACC-TRX-012-05
    assertion: >-
      it requests it pending_approval, refusing to execute it
      (refund_not_requested) or to let the requester approve it (self_approval)
    provenance: test
    source: >-
      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)
  - scenario: ATS-ACC-TRX-012-05
    assertion: >-
      it is approved by another person (requested, approved_by_id theirs), then
      executed
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      is approved by another person (requested, approved_by_id theirs), then
      executed
  - scenario: ATS-ACC-TRX-012-06
    assertion: >-
      it refuses executing it again (refund_not_requested) and cancelling it
      (refund_not_cancellable)
    provenance: test
    source: >-
      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)
  - scenario: ATS-ACC-TRX-012-06
    assertion: 'it cancels a requested refund with the reason, nothing moved'
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      cancels a requested refund with the reason, nothing moved
  - scenario: ATS-ACC-TRX-012-07
    assertion: >-
      it records it offline: executed, the payment partially_refunded with 1000
      refunded (FC-11, FC-14)
    provenance: test
    source: >-
      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)
  - scenario: ATS-ACC-TRX-012-08
    assertion: >-
      it leaves the invoice lines as they were: nothing owed again, no credit
      memo kept
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      leaves the invoice lines as they were: nothing owed again, no credit memo
      kept
  - scenario: ATS-ACC-TRX-012-08
    assertion: >-
      it leaves the payment as it was: nothing unapplied, refunded or pending,
      its facts unchanged
    provenance: test
    source: >-
      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
  - scenario: ATS-ACC-TRX-012-09
    assertion: >-
      it leaves a lost answer processing (refund_uncertain, the money pending)
      and the sweeper finishes it with one refund
    provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js#it
      leaves a lost answer processing (refund_uncertain, the money pending) and
      the sweeper finishes it with one refund
environment: local
implementation_state: implemented
latest_result: not-run
latest_run_at: null
latest_run_ref: null
conformance: not-verified
estimated_minutes: 3
isolation: >-
  Suite-marked records in the prepared local test database; fixture teardown
  removes owned records.
provenance: test
source: >-
  services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js
evidence:
  - provenance: test
    source: >-
      services-accounting:modules/transactions/tests/integration/services/refunds/refunds.integration.spec.js
    assertion_titles:
      - the refund commands (integration)
      - 'a refund of unapplied money, recorded offline'
      - >-
        numbers the tenant’s first refund 1,000,000, from the check’s bank
        account, in the batch named, with no balance effect
      - >-
        records it offline: executed, the payment partially_refunded with 1000
        refunded (FC-11, FC-14)
      - >-
        writes the request and record_offline document events, and the
        refund_requested and refunded payment events
      - >-
        replays the same key and body as the same refund, writing nothing
        (FC-19)
      - >-
        refuses executing it again (refund_not_requested) and cancelling it
        (refund_not_cancellable)
      - a refund of allocated money by line
      - >-
        takes the money back from the allocation: a reversing row per refund,
        the original reversed by 3000
      - >-
        keeps the invoice settled for the credited 2000 (a credit memo line) and
        reopens the 1000: the line owes 1000
      - >-
        leaves the check refunded 4000, allocated 3000 and unapplied 5000, its
        facts summing to minus the unapplied money (FC-07)
      - >-
        refuses more than an allocation holds (over_refund) and more than the
        unapplied money (over_refund)
      - refuses a refund naming no money (nothing_to_refund)
      - a card payment’s refund through the processor
      - >-
        succeeds with the dummy’s reference through the merchant account; the
        payment partially_refunded, 500 refunded, none pending
      - >-
        records the refund’s settlement item for reconciliation: kind refund,
        minus 500, keyed refund:<id>
      - >-
        refuses a declined refund (processor_declined): the refund failed, the
        pending money given back
      - >-
        leaves a lost answer processing (refund_uncertain, the money pending)
        and the sweeper finishes it with one refund
      - a declined card refund by line moves no money (DEF-ACT-0852)
      - with balance_effect reopen
      - refuses the refund as declined and fails it
      - >-
        leaves the invoice lines as they were: nothing owed again, no credit
        memo kept
      - >-
        leaves the payment as it was: nothing unapplied, refunded or pending,
        its facts unchanged
      - with balance_effect credit
      - refuses the refund as declined and fails it
      - >-
        leaves the invoice lines as they were: nothing owed again, no credit
        memo kept
      - >-
        leaves the payment as it was: nothing unapplied, refunded or pending,
        its facts unchanged
      - approval
      - >-
        requests it pending_approval, refusing to execute it
        (refund_not_requested) or to let the requester approve it
        (self_approval)
      - >-
        is approved by another person (requested, approved_by_id theirs), then
        executed
      - 'a cancellation, and a credit’s refund'
      - 'cancels a requested refund with the reason, nothing moved'
      - >-
        refunds a manual credit’s balance by check: the credit refunded 300, its
        balance 400, its facts summing to minus the balance
      - >-
        refuses a credit refund beyond the balance (over_refund) and one through
        a processor the credit did not come from (cash_account_mismatch)
      - the refund lines are insert-only for the application role
      - 'refuses an UPDATE and a DELETE of a refund line (42501: no grant)'
    imports:
      - 'const { sql } = require(''drizzle-orm'');'
      - >-
        const { expectDatabaseRefusal } =
        require('@rhythmsoftware/macroservice-testing/refusals');
      - >-
        const { manualClock } =
        require('@rhythmsoftware/macroservice-time/clock');
      - 'const { stage } = require(''@rhythmsoftware/macroservice-testing/stage'');'
      - >-
        const { TEST_CLOCK, TEST_NOW } =
        require('@rhythmsoftware/macroservice-testing/execution-context');
      - >-
        const { updateSettings } =
        require('../../../../../books/services/persistence/settings');
      - >-
        const { createCredit } =
        require('../../../../services/credits/credit-commands');
      - >-
        const { dummyPaymentProcessor } =
        require('../../../../services/payments/dummy-processor');
      - >-
        const { sweepProcessingIntents } =
        require('../../../../services/payments/intent-sweeper');
      - >-
        const { receivePayment } =
        require('../../../../services/payments/payment-receive');
      - >-
        const { approveRefund, cancelRefund, executeRefund, requestRefund } =
        require('../../../../services/refunds/refund-commands');
      - '} = require(''../../payments-fixtures'');'
      - "\t\tconst { check } = world.require();"
      - "\t\tconst { check, allocation } = world.require();"
      - "\t\t\tconst { requested } = recorded.require();"
      - "\t\t\tconst { executed } = recorded.require();"
      - "\t\t\tconst { check } = world.require();"
      - "\t\t\tconst { requested } = recorded.require();"
      - "\t\t\tconst { check } = world.require();"
      - "\t\t\tconst { requested } = recorded.require();"
      - "\t\t\tconst { requested } = recorded.require();"
      - "\t\t\tconst { check, allocation } = world.require();"
      - "\t\t\tbyLine.require();"
      - "\t\t\tconst { invoice } = world.require();"
      - "\t\t\tconst { credited, reopened } = byLine.require();"
      - "\t\t\tconst { check } = world.require();"
      - "\t\t\tbyLine.require();"
      - "\t\t\tconst { check, allocation } = world.require();"
      - "\t\t\tconst { check } = world.require();"
      - "\t\t\tconst { payment, executed } = card.require();"
      - "\t\t\tconst { executed } = card.require();"
      - "\t\t\tconst { payment } = card.require();"
      - "\t\t\tconst { payment } = card.require();"
      - "\t\t\t\tawait expectFailed(reopen.require());"
      - "\t\t\t\tawait expectLinesUnchanged(reopen.require());"
      - "\t\t\t\tawait expectPaymentUnchanged(reopen.require());"
      - "\t\t\t\tawait expectFailed(credit.require());"
      - "\t\t\t\tawait expectLinesUnchanged(credit.require());"
      - "\t\t\t\tawait expectPaymentUnchanged(credit.require());"
      - "\t\t\tconst { check } = world.require();"
      - "\t\t\tconst { settings } = approval.require();"
      - "\t\t\tconst { requested } = approval.require();"
      - "\t\t\tconst { requested } = approval.require();"
      - "\t\t\tconst { check } = world.require();"
      - "\t\t\tconst { credited } = byLine.require();"
    test_double_declarations: []
---

# Refunds suite

## Verification Boundary

The suite exercises transaction commands and database protections in the local prepared test environment. Registration records the existing executable file; it does not report a run or establish product approval.

## What This Suite Proves

Its cited assertions contribute evidence for refund requests cannot exceed their source; line return distinguishes credit from reopened debt; another person approves before payment; completed return is final; offline return updates receipt standing; definite processor failure restores local position; uncertain refund waits for a definite outcome. The structured assertion bindings identify which parts of combined scenarios it executes.

## What It Does Not Prove

Local commands and route invocations do not establish deployed routing, live processor guarantees or the complete staff browser experience. An unbound feature chain remains proposed, and this registration carries no execution result.

## Real Dependencies and Test Doubles

The suite uses the imported commands, route handlers or persistence code against the local test database. Fixture helpers supply accounting context; any stepped clock, dummy processor or substituted external client is retained in the evidence declarations.

## Must Not Be Doubled

The command or persistence behavior under assertion and its relevant database protections must remain real. Substituting the final financial result removes evidence of transactional effects.
