---
id: WF-COM-CAT-014
title: Apply promotions at checkout and record their use at placement
trigger: >-
  Promotion assessment compares valid alternatives and placement acquires only the winning uses. Redemptions
  retain the chosen terms and line allocations within the order transaction.
product_area: commerce
module: catalog
revision: 1
status: draft
design_intent: confirmed
implementation_evidence: observed
inference: []
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
implements:
  - CAP-COM-CAT-016
  - CAP-COM-CAT-017
moves:
  - STATE-COM-CAT-010
  - STATE-COM-CAT-007
crosses:
  - SVC-COM-CAT-promotions
  - SVC-COM-CAT-pricing
idempotency: >-
  Preview replaces the complete assessment. A unique order-and-promotion key prevents repeated redemption;
  order-placement replay handling belongs to Orders.
concurrency: >-
  Preview capacity is provisional. Placement conditionally updates code then promotion counters in stable
  campaign order within the order transaction.
hops:
  - 'n': 1
    actor_or_service: Promotions port (called by checkout)
    action: >-
      Load entered codes, active automatic campaigns, ordered parts, relevant party usage, restrictions and
      product discount exclusions.
    anchor: 'services-commerce:modules/promotions/services/checkout/checkout-candidates.ts:resolveCheckoutCandidates'
    seam: sync
    on_failure: Fail assessment if candidate reads fail.
    left_inconsistent: No new redemption is committed.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: observed
    source: 'services-commerce:modules/promotions/services/checkout/checkout-candidates.ts:resolveCheckoutCandidates'
  - 'n': 2
    actor_or_service: Promotion area providers
    action: >-
      Batch line qualification through registered area providers, with bounded calls; merchandise is the only
      registered provider.
    anchor: 'services-commerce:modules/promotions/services/areas/registry.ts:createAreaRegistry'
    seam: sync
    on_failure: >-
      Unavailable code parts receive a retry refusal; unavailable automatic parts are skipped by the
      evaluator.
    left_inconsistent: No use is committed for an unavailable assessed benefit.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/unit/pricing/parts.spec.js#refuses a code
      promotion_check_unavailable when a part is listed unavailable, and discounts nothing
  - 'n': 3
    actor_or_service: Promotion evaluation library
    action: >-
      Compare permitted audience and Standard alternatives, bounded competing combinations and later
      candidates; allocate the lowest permitted pre-tax, pre-shipping result.
    anchor: 'services-commerce:modules/promotions/services/pricing/alternatives.ts:evaluateAlternatives'
    seam: sync
    on_failure: >-
      Return code refusal reasons; valid candidates giving no benefit remain unapplied and consume no
      capacity.
    left_inconsistent: No redemption is acquired by the pure evaluation.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/unit/pricing/alternatives.spec.js#is the same answer for the
      same input
  - 'n': 4
    actor_or_service: Checkout module
    action: Replace the checkout’s complete promotion assessment during preview.
    anchor: 'services-commerce:modules/promotions/services/persistence/checkout-promotions.ts'
    seam: sync
    on_failure: Fail preview if the evaluation write fails.
    left_inconsistent: The checkout retains a whole earlier assessment rather than a partial replacement.
    observer: The recorded release is visible through usage and performance reads.
    provenance: documented
    source: 'services-commerce:modules/promotions/services/persistence/checkout-promotions.ts'
  - 'n': 5
    actor_or_service: Orders module inside the placement transaction
    action: >-
      In placement savepoints and promotion order, conditionally acquire code and campaign uses, lock
      first-use terms and write frozen redemption allocations.
    anchor: 'services-commerce:modules/promotions/services/persistence/promotion-redemptions.ts:recordRedemption'
    seam: sync
    on_failure: 'Return typed capacity, date, status, restriction or repeat refusals and roll back failed placement.'
    left_inconsistent: The order and redemption transaction commits no partial sale.
    observer: The requesting person or service receives the result and any returned refusal.
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/integration/services/persistence/redemption-concurrency.integration.spec.js#applies
      exactly 100 and answers the other 100 code_exhausted
branches:
  - Billing reads frozen allocations and campaign labels to describe discounts on invoices.
waits: []
evidence:
  - >-
    services-commerce at c8b66a98: modules/promotions/services/checkout/checkout-candidates.ts;
    services/areas/*.ts; services/pricing/*.ts; services/persistence/promotion-redemptions.ts,
    checkout-promotions.ts; modules/orders/services/placement/place-order.ts and
    modules/checkout/services/checkout/checkout-workflow.ts (the callers).
  - >-
    services-commerce at c8b66a98: docs/milestones/COMM-M1-set-up-what-you-sell.md (D12 and its
    clarifications); decisions/COMM-M1-D12-evaluation-algorithm.md; COMM-M1b-promotions-and-codes.md (D-R1 to
    D-R3).
controls: [TC-IAM-006, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-AZ-005, 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-LOG-001, TC-LOG-002, TC-LOG-003, 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-PRV-001, TC-PRV-002]
---

# Apply promotions at checkout and record their use at placement

## What It Does

Promotion assessment compares valid alternatives and placement acquires only the winning uses. Redemptions retain the chosen terms and line allocations within the order transaction.

## The Flow

```mermaid
sequenceDiagram
  participant K as Checkout or orders module
  participant C as Candidate resolution
  participant R as Area providers
  participant L as Evaluation library
  participant D as Order transaction
  K->>C: entered codes, automatic promotions, counts
  C->>R: which lines each part qualifies
  R-->>C: lines, or unavailable
  C->>L: candidates and priced lines
  L-->>K: cheapest permitted result, reasons
  alt preview
    K->>K: replace checkout evaluation rows
  else placement
    K->>D: per promotion: code use, promotion use, limits, redemption
    D-->>D: sums checked at commit
  end
```

## When Something Fails

Fail assessment if candidate reads fail. No new redemption is committed. Unavailable code parts receive a retry refusal; unavailable automatic parts are skipped by the evaluator. No use is committed for an unavailable assessed benefit. Return code refusal reasons; valid candidates giving no benefit remain unapplied and consume no capacity. No redemption is acquired by the pure evaluation. Fail preview if the evaluation write fails. The checkout retains a whole earlier assessment rather than a partial replacement. Return typed capacity, date, status, restriction or repeat refusals and roll back failed placement. The order and redemption transaction commits no partial sale.

## Open Design Questions

The implemented recovery and concurrency behaviour above defines the current boundary. Any change to transaction scope or product authority requires a recorded decision.
