---
id: CAP-ACC-REC-001
title: Record processor payouts without duplicate entry
feature: FEAT-ACC-REC-01
product_area: accounting
module: reconciliation
revision: 2
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - react-console acceptance inventory CAP-ACC-REC-002 (record part)
source_sha:
  services-accounting: f47cb263875bbc0c5640fba7dae6785ec32695f8
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - >-
    services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (§2 payouts: manual entry and duplicate
    rules)
  - 'services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D6 (architecture §3.4 U1, U9)'
  - 'services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D8 (processor import out)'
purpose: Finance staff need a dependable deposit record before they can prepare its explanation.
outcomes:
  - Each merchant deposit or debit has one retained payout record.
  - Look-alike entry requires an explicit recorded acknowledgement.
  - Creation retry preserves the original record.
concepts:
  - CON-ACC-REC-001
actors:
  - ACT-ACC-017
  - ACT-ACC-018
surfaces:
  - console
  - api
preconditions:
  - The merchant belongs to the selected unit and remains active.
postconditions:
  - The created payout retains manual origin with verification and reconciliation pending.
permissions:
  - key: accounting.payouts.create
    where_checked: api
    scope: tenant
  - key: accounting.payouts.read
    where_checked: api
    scope: tenant
  - key: accounting.payouts.create
    where_checked: console
    scope: tenant
rules:
  - id: BR-CAP-ACC-REC-001-01
    proved_by: [ATS-ACC-REC-001-01, TST-ACC-REC-025, TST-ACC-REC-033]
    statement: >-
      Staff enter a merchant account, an integer minor-unit net amount and a reported deposit day; zero and
      negative amounts are valid, and the merchant’s settlement bank is used when no bank is supplied.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses a body without its
      reported deposit date with 400 naming the field;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses an amount that is not a
      whole number of minor units with 400;
      services-accounting:modules/reconciliation/tests/integration/db/payouts.table.integration.spec.js#stores
      a zero payout;
      services-accounting:modules/reconciliation/tests/integration/db/payouts.table.integration.spec.js#stores
      a negative payout, a debit of the bank account
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D6 (architecture §3.4 U9: negative and zero
      payouts allowed)
    examples:
      positive: A Payrix debit of -15.00 for fees is recorded as a payout of -1500.
      negative: 'A deposit with no reported deposit date is refused, naming the field.'
  - id: BR-CAP-ACC-REC-001-02
    proved_by: [ATS-ACC-REC-001-02, TST-ACC-REC-025, TST-ACC-REC-033, TST-ACC-REC-057]
    statement: >-
      The merchant determines the payout’s unit, currency and processor permanently; inactive merchants and
      banks, or banks in another unit or currency, cannot be used.
    kind: invariant
    enforced_at:
      - api
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#answers 200 with the payout,
      its currency, processor and bank account the merchant account’s;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses an inactive merchant
      account with 400; services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses a
      bank account of another business unit with 400;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#refuses
      a bank account in another currency than the merchant account’s;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#refuses
      an inactive bank account;
      services-accounting:modules/reconciliation/tests/integration/db/payouts.table.integration.spec.js#refuses
      a merchant account in another currency
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (§4: the architecture §6 invariants hold;
      INV-02)
  - id: BR-CAP-ACC-REC-001-03
    proved_by: [ATS-ACC-REC-001-03, ATS-ACC-REC-001-11, ATS-ACC-REC-001-12, TST-ACC-REC-002, TST-ACC-REC-025, TST-ACC-REC-033, TST-ACC-REC-057]
    statement: >-
      A merchant’s nonvoided payouts cannot share a processor reference; voiding the earlier payout releases
      that reference.
    kind: policy
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#refuses
      a reference a live payout of the merchant account has with duplicate-reference;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses a processor reference a
      live payout of the merchant account has with 409 duplicate-reference;
      services-accounting:modules/reconciliation/tests/integration/db/payouts.table.integration.spec.js#allows
      the same reference on another merchant account;
      services-accounting:modules/reconciliation/tests/integration/db/payouts.table.integration.spec.js#allows
      the reference again once the payout holding it is voided;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#records
      a reference again once the payout that had it is voided;
      react-console:acceptance/accounting/playwright/journeys/reconciliation-close/ACT-ACC-0591.spec.ts#ACT-ACC-0591
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (plan approved 2026-10-22 10:40 ET; §2
      scope); §3: a second deposit with the same reference is refused
    examples:
      positive: PX-1083 a second time is refused.
      negative: PX-1083 on another merchant account is accepted.
  - id: BR-CAP-ACC-REC-001-04
    proved_by: [ATS-ACC-REC-001-04, ATS-ACC-REC-001-11, TST-ACC-REC-002, TST-ACC-REC-025, TST-ACC-REC-057]
    statement: >-
      An equal amount within two business days either side in the merchant’s bank calendar requires
      acknowledgement of every possible duplicate, and Rhythm retains and audits those acknowledgements.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#records the possible duplicate
      once every candidate is acknowledged, storing the acknowledgement;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses a payout of the same
      amount reported a business day apart with 409 possible-duplicate and its candidates;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#refuses
      an acknowledgement that leaves a candidate out;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#records
      the payout once every candidate is acknowledged, storing them and auditing the acknowledgement;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#does
      not count a voided payout as a possible duplicate;
      react-console:acceptance/accounting/playwright/journeys/reconciliation-close/ACT-ACC-0591.spec.ts#ACT-ACC-0591
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (§3: the same amount and date warns and
      needs an acknowledgement)
  - id: BR-CAP-ACC-REC-001-05
    proved_by: [ATS-ACC-REC-001-03, ATS-ACC-REC-001-04, ATS-ACC-REC-001-12, TST-ACC-REC-025, TST-ACC-REC-033, TST-ACC-REC-057]
    statement: Voided payouts reserve no processor reference and do not participate in possible-duplicate detection.
    kind: policy
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#does
      not count a voided payout as a possible duplicate;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses a processor reference a
      live payout of the merchant account has with 409 duplicate-reference;
      services-accounting:modules/reconciliation/tests/integration/db/payouts.table.integration.spec.js#allows
      the same reference on another merchant account;
      services-accounting:modules/reconciliation/tests/integration/db/payouts.table.integration.spec.js#allows
      the reference again once the payout holding it is voided;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#records
      a reference again once the payout that had it is voided;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses a payout of the same
      amount reported a business day apart with 409 possible-duplicate and its candidates;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#records the possible duplicate
      once every candidate is acknowledged, storing the acknowledgement;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#refuses
      an acknowledgement that leaves a candidate out;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#records
      the payout once every candidate is acknowledged, storing them and auditing the acknowledgement
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (plan approved 2026-10-22 10:40 ET; §2
      scope)
  - id: BR-CAP-ACC-REC-001-06
    proved_by: [ATS-ACC-REC-001-05, ATS-ACC-REC-001-11, ATS-ACC-REC-001-14, TST-ACC-REC-002, TST-ACC-REC-025, TST-ACC-REC-057]
    statement: >-
      Creation requires a request key scoped to its caller: identical retries return the existing payout, a
      changed body fails, and another caller’s key starts a separate request.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#treats
      another caller’s use of the key as another request;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#answers the same payout for the
      same key and body, recording it once;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses the same key with
      another body with 409; services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses
      a create without an Idempotency-Key with 400;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#refuses
      another request under a key already used with idempotency-key-reused;
      react-console:acceptance/accounting/playwright/journeys/reconciliation-close/ACT-ACC-0591.spec.ts#ACT-ACC-0591
    origin: 'services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (§2: idempotency of every money command)'
  - id: BR-CAP-ACC-REC-001-07
    proved_by: [ATS-ACC-REC-001-06, ATS-ACC-REC-001-13, TST-ACC-REC-057, TST-ACC-REC-058]
    statement: >-
      Payout numbering advances within the workspace without collisions between simultaneous creates, and the
      number is searchable with or without its payout prefix.
    kind: calculation
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#gives
      two payouts recorded at once two numbers;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#numbers
      the tenant’s next payout one after its last;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.queries.integration.spec.js#searches
      by the payout’s number written PO-n
    origin: none
  - id: BR-CAP-ACC-REC-001-08
    proved_by: [ATS-ACC-REC-001-07, TST-ACC-REC-057]
    statement: 'Failed payout creation rolls back the payout, request-key record and audit event together.'
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#writes
      nothing when it refuses: no payout, no key, no event;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#records
      the payout from the merchant account’s facts and answers it whole;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#records
      the payout’s audit event in its transaction, with its key
    origin: none
  - id: BR-CAP-ACC-REC-001-09
    proved_by: [ATS-ACC-REC-001-07, TST-ACC-REC-057]
    statement: >-
      A created payout starts as manual, bank-unverified and unreconciled, with its keyed creation event
      committed alongside it.
    kind: lifecycle
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#records
      the payout’s audit event in its transaction, with its key;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#records
      the payout from the merchant account’s facts and answers it whole;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.integration.spec.js#writes
      nothing when it refuses: no payout, no key, no event
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D6 (architecture §3.4 U1: source is how it was
      entered; verification only by evidence)
  - id: BR-CAP-ACC-REC-001-10
    proved_by: [ATS-ACC-REC-001-08, TST-ACC-REC-025, TST-ACC-REC-058]
    statement: The duplicate preview returns hard-reference and possible-duplicate results without creating a payout.
    kind: calculation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#answers a payout of the amount
      reported two business days before as a possible duplicate;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#answers a reference duplicate;
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#answers none for an amount no
      payout has;
      services-accounting:modules/reconciliation/tests/integration/services/persistence/payouts.queries.integration.spec.js#answers
      the live payouts of the amount within two business days of the date, by number, Columbus Day skipped
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (plan approved 2026-10-22 10:40 ET; §2
      scope)
  - id: BR-CAP-ACC-REC-001-11
    proved_by: [ATS-ACC-REC-001-09, TST-ACC-REC-025]
    statement: >-
      The create permission governs payout recording, the read permission governs duplicate preview, and the
      console disables creation when create is absent.
    kind: permission
    enforced_at:
      - api
      - console
    must_prove: true
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#refuses a principal without
      accounting.payouts.create with 403
    origin: >-
      services-accounting:docs/milestones/plans/ACCT-M8-plan.md#D10 (§2: permissions (preparer and approver
      policies))
  - id: BR-CAP-ACC-REC-001-12
    proved_by: []
    statement: >-
      Console payout creation has no duplicate-acknowledgement payload, so acknowledging a look-alike
      currently requires the API.
    kind: policy
    enforced_at:
      - console
    must_prove: false
    provenance: observed
    source: 'react-console:lib/accounting/reconciliation/store.tsx:createPayout'
    origin: none
scenarios:
  - id: SC-CAP-ACC-REC-001-01
    given: 'The Payrix merchant in the sample unit has a reported deposit of $9,725.00 on October 10.'
    when: Enter the processor deposit.
    then: A manual unverified payout stores 972500 minor units with that accounting day.
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/e2e/reconciliation-scenario.e2e.spec.js#step 3: enters
      PX-1083 as a manual, unverified payout of 972500 dated 2026-10-10
  - id: SC-CAP-ACC-REC-001-02
    given: The sample processor reference already belongs to a live payout.
    when: Attempt another payout with that reference.
    then: Creation fails for a hard-reference duplicate.
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/e2e/reconciliation-scenario.e2e.spec.js#step 3: refuses
      a second entry of PX-1083 as a duplicate reference
  - id: SC-CAP-ACC-REC-001-03
    given: 'A live payout has the same reported day and $9,725.00 amount.'
    when: Enter another deposit without its own reference.
    then: Possible-duplicate refusal identifies the existing payout.
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/e2e/reconciliation-scenario.e2e.spec.js#step 3: answers
      an entry with no reference, the same amount and date, possible-duplicate naming PX-1083
edge_cases:
  - id: SC-CAP-ACC-REC-001-04
    given: An equal amount is reported three business days earlier.
    when: Preview duplicates for the new deposit.
    then: That earlier payout is outside possible-duplicate eligibility.
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/e2e/payouts.e2e.spec.js#answers none when the payout of
      the amount was reported three business days before
  - id: SC-CAP-ACC-REC-001-05
    given: Processor charges produce a debit rather than a deposit.
    when: Record a negative-fifteen-dollar payout.
    then: The bank debit is retained as a signed payout.
    provenance: test
    source: >-
      services-accounting:modules/reconciliation/tests/integration/db/payouts.table.integration.spec.js#stores
      a negative payout, a debit of the bank account
constraints: []
depends_on: []
related:
  - CAP-ACC-REC-002
  - CAP-ACC-REC-004
  - CAP-ACC-REC-020
journeys:
  - JNY-ACC-REC-001
  - JNY-ACC-REC-002
  - JNY-ACC-REC-006
open_questions: []
evidence:
  - >-
    Examined supporting service material: modules/reconciliation/aws/api/payouts.routes.ts,
    payouts.schemas.ts; services/persistence/payouts.ts (create region), payout-refusals.ts,
    payout-numbers.ts; services/rules/payout-rules.ts; db/payouts.table.ts; tests e2e/payouts.e2e.spec.js,
    integration/services/persistence/payouts.integration.spec.js, db/payouts.table.integration.spec.js;
    docs/milestones/plans/ACCT-M8-plan.md §2, §3, D6, D10.
  - >-
    Examined supporting console material: components/accounting/reconciliation/add-payout-sheet.tsx;
    lib/accounting/reconciliation/store.tsx (createPayout), client.ts; acceptance journeys ACT-ACC-0591,
    ACT-ACC-0609, ACT-ACC-0610.
controls: [TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-006, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-TEN-004, TC-DAT-001, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-005]
exercised_by: [FAS-ACC-REC-01-01, FAS-ACC-REC-01-03, FAS-ACC-REC-01-04]
suites: [TST-ACC-REC-002, TST-ACC-REC-025, TST-ACC-REC-033, TST-ACC-REC-057, TST-ACC-REC-058]
---

# Record processor payouts without duplicate entry

## Purpose

Finance staff need a dependable deposit record before they can prepare its explanation.

## How It Works

Preparers start from the processor report and select the merchant context. Rhythm checks whether the bank movement already has a recorded counterpart. The saved payout becomes the starting point for matching and later review. An error leaves staff with a refusal to investigate rather than an extra deposit to clean up. The current console cannot complete the acknowledgement path for look-alike deposits.

## Relationships

Payout correction maintains the saved record, matching explains its contents and record controls supply retry and audit safeguards.
