---
id: STATE-COM-CAT-008
title: Promotion code lifecycle
object: DOM-COM-CAT-005
product_area: commerce
module: catalog
revision: 1
status: draft
design_intent: proposed
implementation_evidence: observed
source_sha:
  services-commerce: c8b66a98e2490617aec38ba5c8165dd9bd0a8f9b
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
states:
  - name: active
    meaning: 'Checkout may use the code if dates, restrictions and limits also pass.'
  - name: disabled
    meaning: Claims are temporarily rejected until staff enable the code.
  - name: revoked
    meaning: Claims are permanently rejected and the status has no outgoing move.
transitions:
  - from: active
    to: disabled
    trigger: Staff suspend code claims.
    guard: The caller must hold campaign management authority.
    side_effects:
      - The parent campaign history records the code status change.
    who: Campaign coordinator (commerce.promotions.manage)
    provenance: observed
    source: 'services-commerce:modules/promotions/services/persistence/promotion-codes.ts:PROMOTION_CODE_TRANSITIONS'
  - from: disabled
    to: active
    trigger: Staff restore suspended code claims.
    guard: The caller must hold campaign management authority.
    side_effects:
      - The parent campaign history records the code status change.
    who: Campaign coordinator (commerce.promotions.manage)
    provenance: observed
    source: 'services-commerce:modules/promotions/services/persistence/promotion-codes.ts:PROMOTION_CODE_TRANSITIONS'
  - from: active
    to: revoked
    trigger: Staff permanently revoke the code.
    guard: The caller must hold campaign management authority.
    side_effects:
      - The parent campaign history records the code status change.
    who: Campaign coordinator (commerce.promotions.manage)
    provenance: test
    source: >-
      services-commerce:modules/promotions/tests/e2e/promotion-codes.e2e.spec.js#keeps a revoked code revoked:
      no operation moves it again
  - from: disabled
    to: revoked
    trigger: Staff permanently revoke the code.
    guard: The caller must hold campaign management authority.
    side_effects:
      - The parent campaign history records the code status change.
    who: Campaign coordinator (commerce.promotions.manage)
    provenance: observed
    source: 'services-commerce:modules/promotions/services/persistence/promotion-codes.ts:PROMOTION_CODE_TRANSITIONS'
invalid:
  - from: revoked
    to: active
    why: Revocation has no restoration operation.
  - from: revoked
    to: disabled
    why: A revoked code cannot become temporarily disabled.
concurrency: >-
  Status changes lock the same code row that placement updates conditionally. The row-lock order determines
  whether a use precedes deactivation or receives an inactive refusal.
invariants:
  - INV-DOM-COM-CAT-005-06
  - INV-DOM-COM-CAT-005-09
evidence:
  - >-
    services-commerce at c8b66a98: modules/promotions/db/promotion-codes.table.ts (PROMOTION_CODE_STATUSES);
    services/persistence/promotion-codes.ts (transitionPromotionCode);
    services/persistence/promotion-redemptions.ts (recordRedemption).
---

# Promotion code lifecycle

## The Lifecycle

```mermaid
stateDiagram-v2
  [*] --> active: created or generated
  active --> disabled
  disabled --> active
  active --> revoked
  disabled --> revoked
```

## Transitions Explained

Active: Checkout may use the code if dates, restrictions and limits also pass. Disabled: Claims are temporarily rejected until staff enable the code. Revoked: Claims are permanently rejected and the status has no outgoing move.

## What Cannot Happen

Revocation has no restoration operation. A revoked code cannot become temporarily disabled.

## Concurrency and Timing

Status changes lock the same code row that placement updates conditionally. The row-lock order determines whether a use precedes deactivation or receives an inactive refusal.
