---
id: CAP-COM-ORD-007
title: Collect payment without duplicate charges
feature: FEAT-COM-ORD-02
product_area: commerce
module: orders
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - CAP-COM-BIL-002
  - CAP-COM-BIL-003
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#A2'
  - 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#A11'
  - 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#D4'
  - 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#9. Acceptance scenarios'
  - 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#2. Scope'
purpose: Staff complete the customer’s payment against an existing order and retain the result of each attempt.
outcomes:
  - A declined card leaves time to pay the existing order another way.
  - Retries preserve one charge for the same unresolved attempt.
concepts:
  - CON-COM-ORD-002
actors:
  - ACT-COM-014
  - ACT-COM-017
surfaces:
  - console
  - api
  - job
preconditions: []
postconditions: []
permissions:
  - key: commerce.orders.tender
    where_checked: api
    scope: tenant
  - key: commerce.orders.read
    where_checked: console
    scope: tenant
rules:
  - id: BR-CAP-COM-ORD-007-01
    proved_by: [ATS-COM-ORD-001-20, ATS-COM-ORD-006-16, ATS-COM-ORD-007-01, ATS-COM-ORD-007-16, ATS-COM-ORD-007-19, ATS-COM-ORD-009-09, ATS-COM-ORD-010-12, ATS-COM-ORD-015-15, TST-COM-ORD-075, TST-COM-ORD-103]
    statement: >-
      An unresolved payment attempt is resumed with its existing Accounting key despite changed retry inputs,
      preventing a second charge for that attempt.
    kind: invariant
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#resumes the same attempt on a
      retry, whatever card the retry names, and moves nothing while the intent is processing
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#9. Acceptance scenarios'
  - id: BR-CAP-COM-ORD-007-02
    proved_by: [ATS-COM-ORD-007-02, TST-COM-ORD-075]
    statement: >-
      The normal payment action requires a placed or payment-uncertain order within its tender window and
      refuses accepted orders and attempts after that window.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#refuses a tender after the
      window 409 tender_window_expired and gives the sentence plain in the details, without the error class
      prefix (COMM-M2 R2-5)
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#A2'
  - id: BR-CAP-COM-ORD-007-03
    proved_by: [ATS-COM-ORD-002-12, ATS-COM-ORD-007-03, ATS-COM-ORD-011-09, TST-COM-ORD-075, TST-COM-ORD-143]
    statement: >-
      The frozen policy determines processor, store-credit and terms choices; console staff can record
      supported offline methods even without listed offline choices, while portal buyers cannot record them.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#refuses an offline tender by a
      member, 403 tender_kind_staff_only, and a kind the profile does not allow, 409, read from the ORDER’s
      snapshot: the order placed before the change still allows it (22 §9)
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#D4'
  - id: BR-CAP-COM-ORD-007-04
    proved_by: [ATS-COM-ORD-007-04, TST-COM-ORD-075]
    statement: >-
      Commerce accepts a payment token and stores a merchant-account reference with the token’s final four
      characters, keeping the full token out of the returned attempt.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#never stores the card: the
      attempt keeps a reference of the merchant account and the token’s last four only
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#A11'
  - id: BR-CAP-COM-ORD-007-05
    proved_by: [ATS-COM-ORD-005-18, ATS-COM-ORD-007-05, TST-COM-ORD-075]
    statement: >-
      A card decline preserves the placed order and pro forma, allowing a later attempt within the window
      under a new sequence and key.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#answers 200 declined with the
      processor’s code, the order still placed with its pro forma, the attempt declined
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#A2'
  - id: BR-CAP-COM-ORD-007-06
    proved_by: [ATS-COM-ORD-007-06, TST-COM-ORD-095]
    statement: >-
      A lost payment response leaves the order uncertain until the minute sweep resolves the existing attempt,
      and uncertainty lasting fifteen minutes raises an exception.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/integration/services/orders/financial-sweeps.integration.spec.js#raises
      tender_uncertain on an attempt still uncertain past fifteen minutes
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#9. Acceptance scenarios'
  - id: BR-CAP-COM-ORD-007-07
    proved_by: [ATS-COM-ORD-007-07, ATS-COM-ORD-007-17, ATS-COM-ORD-007-18, TST-COM-ORD-075, TST-COM-ORD-145]
    statement: >-
      Successful payment legs count toward acceptance, with store credit counted at the amount actually
      applied and a zero-application leg declined.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/unit/tender/tender-rules.spec.js#leaves a credit leg that applied
      less than it asked at what it applied, so the order still owes the rest
    origin: none
  - id: BR-CAP-COM-ORD-007-08
    proved_by: [ATS-COM-ORD-007-08, TST-COM-ORD-075]
    statement: >-
      A single-payment policy prevents a portal buyer’s second payment, with a policy explanation, while staff
      can split the payment.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#refuses a member a second
      payment, 409 split_tender_not_allowed naming the policy
    origin: none
  - id: BR-CAP-COM-ORD-007-09
    proved_by: [ATS-COM-ORD-007-09, TST-COM-ORD-075]
    statement: >-
      A terms payment choice accepts the order without a charge, converts its provisional invoice and can
      retain the customer’s purchase-order reference.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#pay later converts the pro
      forma to a real invoice and accepts the order on terms
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#D4'
  - id: BR-CAP-COM-ORD-007-10
    proved_by: [ATS-COM-ORD-007-10, TST-COM-ORD-075]
    statement: >-
      Accounting checks the requested or frozen merchant account against the order’s business unit, currency
      and method before recording a card attempt, and the frozen policy rejects amounts outside its card
      range.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#asks Accounting whether the
      merchant account may settle the tender: the order’s frozen default refused when ineligible, 409
      merchant_account_not_eligible, a request naming an eligible account accepted
    origin: none
  - id: BR-CAP-COM-ORD-007-11
    proved_by: [ATS-COM-ORD-006-13, ATS-COM-ORD-007-11, TST-COM-ORD-075]
    statement: >-
      Payment first tries to bill an unbilled order and refuses the charge if billing still cannot obtain an
      answer.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#refuses the tender 409
      order_not_billed when billing it now gets no answer either, charging nothing
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#A1'
  - id: BR-CAP-COM-ORD-007-12
    proved_by: []
    statement: >-
      The documented Accounting rule identifies the cardholder as payer and refuses application of their
      payment to another customer’s invoice.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: documented
    source: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#A10'
    origin: none
    note: Accounting owns this payment behavior; its recorded recommendation supplies the current intent basis.
  - id: BR-CAP-COM-ORD-007-13
    proved_by: [ATS-COM-ORD-007-12, TST-COM-ORD-018]
    statement: >-
      The composer retains the same order after card decline or an unpaid card-window close, records check
      references or terms purchase-order references and offers only permitted methods.
    kind: policy
    enforced_at:
      - console
    must_prove: true
    provenance: test
    source: >-
      react-console:components/order-composer/__tests__/live-composer.test.tsx#places the order, then takes a
      card: declined first, approved on the same order
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#D4'
  - id: BR-CAP-COM-ORD-007-14
    proved_by: [ATS-COM-ORD-007-13, TST-COM-ORD-017]
    statement: >-
      Order 360 selects pay-now payment and void actions or the terms invoice’s Accounting payment link
      according to order state and permissions, hiding these actions on paid orders.
    kind: permission
    enforced_at:
      - console
    must_prove: true
    provenance: test
    source: >-
      react-console:components/console/order/__tests__/order-360-live-cards.test.tsx#shows Take Payment and
      Void Order on an unpaid pay-now order inside its window
    origin: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#2. Scope'
scenarios:
  - id: SC-CAP-COM-ORD-007-01
    given: Jane’s two-hundred-thirteen-dollar order already exists.
    when: Staff try a declining card and then an approving card.
    then: >-
      The decline leaves the order awaiting payment, and the replacement attempt accepts it with one
      successful charge.
    provenance: documented
    source: 'services-commerce:docs/milestones/plans/COMM-M2-plan.md#3. How it works for the user'
edge_cases:
  - id: SC-CAP-COM-ORD-007-02
    given: A final-second payment and expiry processing compete on the same order.
    when: The payment completes while a void attempt runs.
    then: >-
      The intended outcome preserves either the paid order or a void without retained money; this race remains
      unverified.
    provenance: unknown
    source: FND-COM-ORD-032
constraints: []
depends_on:
  - CAP-COM-ORD-006
related:
  - CAP-COM-ORD-008
  - CAP-COM-ORD-010
journeys:
  - JNY-COM-ORD-001
  - JNY-COM-ORD-002
open_questions: []
evidence:
  - >-
    Examined reference: react-console:components/console/order/__tests__/order-360-live-cards.test.tsx#shows
    Take Payment and Void Order on an unpaid pay-now order inside its window
  - >-
    Examined reference: react-console:components/order-composer/__tests__/live-composer.test.tsx#places the
    order, then takes a card: declined first, approved on the same order
  - 'Examined reference: services-commerce:docs/milestones/plans/COMM-M2-plan.md#2. Scope'
  - 'Examined reference: services-commerce:docs/milestones/plans/COMM-M2-plan.md#3. How it works for the user'
  - 'Examined reference: services-commerce:docs/milestones/plans/COMM-M2-plan.md#9. Acceptance scenarios'
  - 'Examined reference: services-commerce:docs/milestones/plans/COMM-M2-plan.md#A1'
  - 'Examined reference: services-commerce:docs/milestones/plans/COMM-M2-plan.md#A10'
  - 'Examined reference: services-commerce:docs/milestones/plans/COMM-M2-plan.md#A11'
  - 'Examined reference: services-commerce:docs/milestones/plans/COMM-M2-plan.md#A2'
  - 'Examined reference: services-commerce:docs/milestones/plans/COMM-M2-plan.md#D4'
  - >-
    Examined reference: services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#answers 200
    declined with the processor’s code, the order still placed with its pro forma, the attempt declined
  - >-
    Examined reference: services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#asks
    Accounting whether the merchant account may settle the tender: the order’s frozen default refused when
    ineligible, 409 merchant_account_not_eligible, a request naming an eligible account accepted
  - >-
    Examined reference: services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#never stores
    the card: the attempt keeps a reference of the merchant account and the token’s last four only
  - >-
    Examined reference: services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#pay later
    converts the pro forma to a real invoice and accepts the order on terms
  - >-
    Examined reference: services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#refuses a
    member a second payment, 409 split_tender_not_allowed naming the policy
  - >-
    Examined reference: services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#refuses a
    tender after the window 409 tender_window_expired and gives the sentence plain in the details, without the
    error class prefix (COMM-M2 R2-5)
  - >-
    Examined reference: services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#refuses an
    offline tender by a member, 403 tender_kind_staff_only, and a kind the profile does not allow, 409, read
    from the ORDER’s snapshot: the order placed before the change still allows it (22 §9)
  - >-
    Examined reference: services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#refuses the
    tender 409 order_not_billed when billing it now gets no answer either, charging nothing
  - >-
    Examined reference: services-commerce:modules/orders/tests/e2e/billing-and-tender.e2e.spec.js#resumes the
    same attempt on a retry, whatever card the retry names, and moves nothing while the intent is processing
  - >-
    Examined reference:
    services-commerce:modules/orders/tests/integration/services/orders/financial-sweeps.integration.spec.js#raises
    tender_uncertain on an attempt still uncertain past fifteen minutes
  - >-
    Examined reference: services-commerce:modules/orders/tests/unit/tender/tender-rules.spec.js#leaves a
    credit leg that applied less than it asked at what it applied, so the order still owes the rest
controls: [TC-IAM-006, 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-CRY-001, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-LOG-004, TC-LOG-005, TC-AVL-004, TC-MON-001, TC-MON-002, TC-MON-004, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-VEN-001, TC-VEN-002, TC-VEN-003, TC-PRV-001, TC-PRV-002]
exercised_by: [FAS-COM-ORD-02-01, FAS-COM-ORD-02-02, FAS-COM-ORD-02-03, FAS-COM-ORD-02-05, FAS-COM-ORD-06-03]
suites: [TST-COM-ORD-017, TST-COM-ORD-018, TST-COM-ORD-075, TST-COM-ORD-095, TST-COM-ORD-103, TST-COM-ORD-143, TST-COM-ORD-145]
---

# Collect payment without duplicate charges

## Purpose

Staff complete the customer’s payment against an existing order and retain the result of each attempt. A failed or uncertain payment remains traceable for recovery.

## How It Works

The clerk chooses an available payment method on the placed order. Processor methods use Accounting’s payment integration, while offline methods record the staff-supplied payment facts. The clerk sees a decline and can attempt a permitted replacement payment. A lost answer instead leaves an unresolved attempt that must be reconciled. Acceptance follows the required amount or an eligible terms choice. Order 360 then offers actions appropriate to the order’s current financial state.

## Relationships

Billing supplies the invoice, order inspection supplies recovery actions, and voiding clears orders that cannot complete payment.
