Skip to content

Instantly share code, notes, and snippets.

@dgobaud
Last active June 22, 2026 21:16
Show Gist options
  • Select an option

  • Save dgobaud/30acb5b637a097231d97f7ef93d63fc4 to your computer and use it in GitHub Desktop.

Select an option

Save dgobaud/30acb5b637a097231d97f7ef93d63fc4 to your computer and use it in GitHub Desktop.
Kiro test

Design Document: Debit Card Transaction Processing

Overview

This document describes the technical design for the debit card transaction processing feature. The system receives debit card authorization requests from a payment network, validates and routes them through an authorization pipeline, maintains accurate account balance states via authorization holds, persists a full audit trail, and delivers real-time cardholder notifications.

The design follows a service-oriented architecture where four logical components — Transaction_Processor, Authorization_Engine, Account_Service, and Notification_Service — communicate through well-defined interfaces. The system must be correct (no double-charges, no phantom holds), consistent (Available_Balance invariant always holds), and idempotent (duplicate submissions are safe).

Key Design Goals

  • Correctness: Every balance mutation must be atomic; partial updates must be rolled back or flagged for reconciliation.
  • Idempotency: Any transaction replayed with the same Idempotency_Key must return the original result without side effects.
  • Auditability: Every state transition of a Transaction_Record is persisted durably before it takes effect.
  • Resilience: Transient failures in downstream services (Account_Service, Notification_Service) are handled with bounded retries; irrecoverable failures are routed to manual reconciliation.
  • Latency: Authorization decisions are returned within 2000 ms; cardholder notifications are delivered within 5 seconds.

Architecture

The system is structured as a pipeline of loosely coupled services. An incoming debit card authorization request enters through the Transaction_Processor, which acts as the primary orchestrator.

flowchart TD
    PN[Payment Network] -->|Authorization Request| TP[Transaction_Processor]
    TP -->|Validate & persist PENDING| DB[(Transaction Store)]
    TP -->|Authorize| AE[Authorization_Engine]
    AE -->|Check account state & balance| AS[Account_Service]
    AS -->|Read/write balances & holds| ACDB[(Account Store)]
    AE -->|Place hold| AS
    AE -->|Approval / Decline| TP
    TP -->|Update record| DB
    TP -->|Notify| NS[Notification_Service]
    NS -->|Alert| CH[Cardholder]

    subgraph Settlement Flow
        SF[Settlement Event] -->|Post transaction| TP2[Transaction_Processor]
        TP2 -->|Release hold + debit ledger| AS
        TP2 -->|Update record to POSTED| DB
    end

    subgraph Reversal Flow
        RF[Reversal Request] -->|Reverse transaction| TP3[Transaction_Processor]
        TP3 -->|Release hold or credit ledger| AS
        TP3 -->|Update record to REVERSED| DB
    end
Loading

Component Responsibilities

Component Responsibility
Transaction_Processor Entry point; validates input, manages idempotency, orchestrates the pipeline, persists all state transitions
Authorization_Engine Evaluates approval/decline decisions based on account state and business rules
Account_Service Owns the Account Store; manages Available_Balance, Ledger_Balance, and Authorization_Holds atomically
Notification_Service Delivers cardholder alerts asynchronously with retry logic

Communication Patterns

  • Transaction_Processor ↔ Authorization_Engine: Synchronous request/response (bounded by 2000 ms SLA).
  • Transaction_Processor ↔ Account_Service: Synchronous for balance reads and hold operations (within the authorization path); synchronous for settlement/reversal operations.
  • Transaction_Processor → Notification_Service: Asynchronous; notifications are fire-and-forget from the authorization path perspective, with the Notification_Service owning retry behavior.
  • All persistence operations: Write-ahead; a record is persisted before any downstream action that depends on it.

Components and Interfaces

Transaction_Processor

The Transaction_Processor is the sole entry point for external transaction events (authorization requests, settlement events, reversal requests).

Authorization Request Interface

POST /transactions/authorize
Request:
  card_number:       string  (required)
  amount:            decimal (required, > 0)
  currency:          string  (required, 3-letter ISO 4217)
  merchant_id:       string  (required)
  timestamp:         ISO 8601 datetime (required)
  idempotency_key:   string  (optional; assigned if absent)

Response (success):
  transaction_id:    string
  status:            "APPROVED" | "DECLINED"
  approval_code:     string  (present when APPROVED)
  decline_reason:    string  (present when DECLINED)
  available_balance: decimal (present when APPROVED)

Response (error):
  error_code:        string
  error_detail:      string  (list of invalid/missing fields)

Settlement Interface

POST /transactions/{transaction_id}/settle
Request:
  settlement_amount: decimal (required, > 0)
  settlement_timestamp: ISO 8601 datetime (required)

Response:
  transaction_id:    string
  status:            "POSTED"

Reversal Interface

POST /transactions/{transaction_id}/reverse
Request:
  reason:            string (optional)

Response (success):
  transaction_id:    string
  status:            "REVERSED"

Response (error):
  error_code:        "TRANSACTION_NOT_FOUND" | "ALREADY_REVERSED" | "REVERSAL_NOT_PERMITTED" | "REVERSAL_FAILED"
  current_status:    string (present for REVERSAL_NOT_PERMITTED)

Validation Rules

The Transaction_Processor applies the following validation checks in order before forwarding to the Authorization_Engine:

  1. All required fields present (card_number, amount, currency, merchant_id, timestamp).
  2. amount is a positive numeric value (> 0).
  3. currency matches the ISO 4217 three-letter format (e.g., USD, EUR).
  4. timestamp is a valid ISO 8601 datetime.
  5. Idempotency check: if idempotency_key matches a known record, return stored result.
  6. Card lookup: card_number maps to an active checking account; otherwise reject with CARD_NOT_FOUND.
  7. Persist PENDING Transaction_Record before forwarding.

Authorization_Engine

The Authorization_Engine receives a validated transaction and an account reference, then produces an approval or decline decision.

Authorization Interface (internal)

authorize(account_id, amount, transaction_id) -> AuthorizationResult

AuthorizationResult:
  decision:          "APPROVED" | "DECLINED"
  approval_code:     string  (present when APPROVED)
  decline_reason:    "INSUFFICIENT_FUNDS" | "ACCOUNT_UNAVAILABLE" | 
                     "SERVICE_UNAVAILABLE" | "HOLD_PLACEMENT_FAILED"
  available_balance: decimal (present when APPROVED)

Decision Logic (ordered)

  1. Retrieve account status from Account_Service. If Account_Service is unavailable → decline SERVICE_UNAVAILABLE.
  2. If account is FROZEN or SUSPENDED → decline ACCOUNT_UNAVAILABLE (no balance check).
  3. Retrieve Available_Balance. If balance retrieval fails → decline SERVICE_UNAVAILABLE.
  4. If amount > Available_Balance → decline INSUFFICIENT_FUNDS.
  5. If account status is not ACTIVE at this point → decline ACCOUNT_UNAVAILABLE (guard: only ACTIVE accounts proceed to hold placement).
  6. Instruct Account_Service to place Authorization_Hold. If hold placement fails → decline HOLD_PLACEMENT_FAILED.
  7. Return APPROVED with approval code and updated Available_Balance.

Account_Service

The Account_Service owns all balance state. All mutations are atomic — no partial updates are externally visible.

Interface

getAccountStatus(account_id) -> AccountStatus
  status: "ACTIVE" | "FROZEN" | "SUSPENDED"

getAvailableBalance(account_id) -> BalanceResult
  available_balance: decimal

placeHold(account_id, hold_id, amount) -> HoldResult
  success: boolean

releaseHold(account_id, hold_id) -> ReleaseResult
  success: boolean

settleTransaction(account_id, hold_id, settled_amount) -> SettleResult
  success: boolean

creditLedger(account_id, amount) -> CreditResult
  success: boolean

Balance Invariant

The Account_Service MUST maintain at all times:

Available_Balance = Ledger_Balance − Σ(active Authorization_Hold amounts)

Hold placement reduces Available_Balance atomically. Settlement atomically releases the hold and reduces Ledger_Balance by the settled amount, then recomputes Available_Balance. Hold expiry (7 days) or reversal atomically releases the hold and restores Available_Balance without touching Ledger_Balance.

Notification_Service

The Notification_Service delivers cardholder alerts asynchronously.

Interface

sendAlert(cardholder_id, alert_type, payload) -> DeliveryReceipt

alert_type: "APPROVED" | "DECLINED"

Approved payload:
  merchant_name:     string
  amount:            decimal
  available_balance: decimal

Declined payload:
  merchant_name:     string
  amount:            decimal
  decline_reason:    string

Retry Policy

  • Await delivery confirmation within 30 seconds of each attempt.
  • On failure, retry after 30 seconds, up to 3 total attempts.
  • On exhaustion of retries, log failure with transaction_id and attempt count; do not retry further.
  • If the cardholder has disabled notifications, suppress the alert immediately without attempting delivery.

Data Models

Transaction_Record

TransactionRecord {
  transaction_id:       UUID           // system-assigned
  idempotency_key:      string         // caller-supplied or system-assigned
  card_number:          string         // hashed/tokenized for storage
  account_id:           UUID           // resolved from card_number
  merchant_id:          string
  merchant_name:        string
  amount:               decimal(19,4)
  currency:             ISO 4217 code  // e.g., "USD"
  status:               TransactionStatus
  approval_code:        string?        // set on APPROVED
  decline_reason:       string?        // set on DECLINED
  settlement_amount:    decimal(19,4)? // set on POSTED
  reversal_reason:      string?        // set on REVERSED
  created_at:           timestamp      // when PENDING record was created
  authorized_at:        timestamp?     // when status changed to APPROVED/DECLINED
  posted_at:            timestamp?     // when status changed to POSTED
  reversed_at:          timestamp?     // when status changed to REVERSED
  hold_id:              UUID?          // reference to Authorization_Hold
  reconciliation_flag:  boolean        // true if flagged for manual reconciliation
}

TransactionStatus: PENDING | APPROVED | DECLINED | POSTED | REVERSED

Status transition diagram:

stateDiagram-v2
    [*] --> PENDING: receipt
    PENDING --> APPROVED: authorization success
    PENDING --> DECLINED: authorization failure
    APPROVED --> POSTED: settlement
    APPROVED --> REVERSED: reversal request
    POSTED --> REVERSED: reversal request
Loading

Terminal statuses: APPROVED, DECLINED, POSTED, REVERSED (APPROVED is terminal from an idempotency perspective but can still transition to POSTED or REVERSED.)

Authorization_Hold

AuthorizationHold {
  hold_id:        UUID
  account_id:     UUID
  transaction_id: UUID
  amount:         decimal(19,4)
  status:         HoldStatus  // ACTIVE | RELEASED
  placed_at:      timestamp
  released_at:    timestamp?
  expires_at:     timestamp   // placed_at + 7 calendar days
}

HoldStatus: ACTIVE | RELEASED

Account (read model exposed by Account_Service)

Account {
  account_id:        UUID
  card_number:       string   // tokenized
  status:            AccountStatus  // ACTIVE | FROZEN | SUSPENDED
  ledger_balance:    decimal(19,4)
  available_balance: decimal(19,4)  // derived: ledger_balance − Σ active holds
  active_holds:      AuthorizationHold[]
}

Idempotency Index

IdempotencyRecord {
  idempotency_key:  string     // unique index
  transaction_id:   UUID
  result_status:    TransactionStatus
  result_payload:   JSON       // full response captured at resolution
  created_at:       timestamp
  expires_at:       timestamp  // created_at + 24 hours
}

Correctness Properties

A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.

Property 1: Validation rejects all invalid transactions

For any transaction missing one or more required fields, or containing an amount ≤ 0, or containing a currency code that is not a 3-letter alphabetic string, the Transaction_Processor SHALL reject the transaction with an appropriate error and SHALL NOT create a Transaction_Record or forward the transaction to the Authorization_Engine.

Validates: Requirements 1.1, 1.2, 1.3


Property 2: Card lookup correctness

For any transaction whose card number does not map to an active checking account, the Transaction_Processor SHALL reject it with CARD_NOT_FOUND and SHALL NOT forward it to the Authorization_Engine.

Validates: Requirements 1.5


Property 3: Idempotency round-trip

For any transaction that has reached a terminal status within the past 24 hours, resubmitting it with the same Idempotency_Key SHALL return the identical status and result payload without creating a new Transaction_Record or mutating any account balance.

Validates: Requirements 1.6, 6.2, 6.3


Property 4: Available_Balance invariant

For any account, at any point in time, Available_Balance SHALL equal Ledger_Balance minus the sum of all active Authorization_Hold amounts.

Validates: Requirements 3.1, 3.2, 3.3, 3.4


Property 5: No overdraft

For any transaction where amount > Available_Balance at the time of authorization, the Authorization_Engine SHALL decline the transaction with INSUFFICIENT_FUNDS and SHALL NOT place an Authorization_Hold.

Validates: Requirements 2.3


Property 6: Frozen/suspended account blocks all transactions

For any transaction submitted against a frozen or suspended account, the Authorization_Engine SHALL decline it with ACCOUNT_UNAVAILABLE without performing a balance check or placing a hold.

Validates: Requirements 2.4


Property 7: Hold placement atomicity

For any approved transaction, a successful hold placement SHALL reduce Available_Balance by exactly the transaction amount, leaving Ledger_Balance unchanged. If hold placement fails, the transaction SHALL be declined with HOLD_PLACEMENT_FAILED and Available_Balance SHALL be unchanged.

Validates: Requirements 2.2, 3.1


Property 8: Settlement balance correctness

For any settled transaction, after settlement the Ledger_Balance SHALL be reduced by the settled amount, the Authorization_Hold SHALL be released, and Available_Balance SHALL equal the updated Ledger_Balance minus the sum of all remaining active holds.

Validates: Requirements 3.2


Property 9: Hold expiry and reversal restore balance

For any hold that is released (via reversal or 7-day expiry), the Available_Balance SHALL be restored by the hold amount and the Ledger_Balance SHALL remain unchanged.

Validates: Requirements 3.3


Property 10: Transaction_Record write-ahead durability

For any transaction, the Transaction_Processor SHALL persist a PENDING Transaction_Record before forwarding to the Authorization_Engine. No status transition resulting from authorization, settlement, or reversal SHALL be visible without a durable write preceding it.

Validates: Requirements 4.1, 4.6


Property 11: Reversal terminal-status safety

For any reversal request referencing a Transaction_Record that is already REVERSED, the system SHALL return ALREADY_REVERSED. For a record that is PENDING or DECLINED, the system SHALL return REVERSAL_NOT_PERMITTED. For a non-existent transaction, it SHALL return TRANSACTION_NOT_FOUND. In all these cases, no balance mutation SHALL occur.

Validates: Requirements 7.3, 7.4, 7.5


Property 12: Concurrent transaction serialization

For any set of concurrent transactions against the same account, the final Available_Balance SHALL reflect the cumulative effect of each transaction's full authorization, as if they were processed sequentially; no two transactions SHALL race to consume the same balance.

Validates: Requirements 6.4


Property 13: Notification content completeness

For any approved transaction, the cardholder alert SHALL contain the merchant name, transaction amount, and updated Available_Balance. For any declined transaction, the alert SHALL contain the merchant name, transaction amount, and decline reason code.

Validates: Requirements 5.1, 5.2


Error Handling

Validation Errors (synchronous, client errors)

Condition Response
Missing required field VALIDATION_ERROR with list of missing/invalid fields
Amount ≤ 0 VALIDATION_ERROR — amount invalid
Invalid currency code VALIDATION_ERROR — currency invalid
Card not found CARD_NOT_FOUND
Initial record persist fails PROCESSING_ERROR — transaction rejected

Authorization Errors

Condition Decline Code
Account frozen or suspended ACCOUNT_UNAVAILABLE
Insufficient Available_Balance INSUFFICIENT_FUNDS
Account_Service unavailable SERVICE_UNAVAILABLE
Hold placement failure HOLD_PLACEMENT_FAILED

Settlement / Reversal Errors

Condition Error Code
Transaction not found TRANSACTION_NOT_FOUND
Transaction already reversed ALREADY_REVERSED
Transaction in PENDING or DECLINED state REVERSAL_NOT_PERMITTED
Account_Service fails to release hold or credit ledger REVERSAL_FAILED

Retry and Reconciliation Policy

  • Account_Service balance operations (hold, release, settle, credit): Retry up to 3 times on transient failure. On exhaustion, flag Transaction_Record.reconciliation_flag = true, do not partially apply changes, and halt all further automated processing for that transaction until manual intervention resolves the flagged record.
  • Transaction_Record status updates: Retry up to 3 times on transient failure for any post-authorization status update (approved or declined). On exhaustion, flag for manual reconciliation without rolling back the authorization.
  • Notification delivery: Retry up to 3 total attempts, waiting 30 seconds between each. On exhaustion, log and stop.
  • Authorization_Engine timeout: If the full authorization cycle exceeds 2000 ms, the Authorization_Engine declines with SERVICE_UNAVAILABLE.

Partial Failure Guarantee

No partial state is committed externally. If a multi-step operation (e.g., release hold + update ledger) cannot complete atomically, the entire operation is either retried or flagged for reconciliation. At no point is the Available_Balance invariant violated by a half-applied update visible to callers.


Testing Strategy

Dual Testing Approach

The testing strategy combines example-based unit tests for specific scenarios with property-based tests for universal correctness guarantees.

Unit / Integration Tests cover:

  • Specific valid and invalid authorization request examples
  • Each decline reason code triggered by its exact condition
  • Status transition sequences (PENDING → APPROVED → POSTED, etc.)
  • Notification suppression for opted-out cardholders
  • Retry exhaustion paths routing to reconciliation
  • Settlement and reversal happy paths and error cases

Property-Based Tests cover the correctness properties enumerated in the Correctness Properties section. Each property test runs a minimum of 100 iterations using randomly generated inputs.

Property-Based Testing

Library selection: Use fast-check (TypeScript/JavaScript), hypothesis (Python), or jqwik (Java/Kotlin) depending on the implementation language.

Generator design:

  • arbTransaction: generates transactions with arbitrary card numbers, amounts, currencies, and merchants — including invalid variants (negative amounts, missing fields, malformed currency codes).
  • arbAccount: generates account states with arbitrary ledger balances, hold sets, and statuses (ACTIVE, FROZEN, SUSPENDED).
  • arbHoldSet: generates sets of Authorization_Hold records with varying amounts and statuses.
  • arbIdempotencyKey: generates strings that may or may not collide with existing keys.

Tag format: Each property-based test MUST include a reference comment:

// Feature: debit-card-transaction-processing, Property {N}: {property_text}

Minimum iteration count: 100 iterations per property test.

Mocking strategy: Account_Service and Notification_Service are replaced with in-memory fakes for property tests. This keeps tests fast, deterministic for replays, and free of external I/O costs while still exercising the full logic of Transaction_Processor and Authorization_Engine.

Test Coverage Targets

Area Approach
Input validation (Req 1) Property 1 (PBT) + example unit tests per field
Card lookup (Req 1) Property 2 (PBT)
Idempotency (Req 1, 6) Property 3 (PBT)
Authorization decisions (Req 2) Properties 5, 6, 7 (PBT)
Balance invariant (Req 3) Property 4 (PBT)
Settlement correctness (Req 3) Property 8 (PBT)
Hold expiry/reversal (Req 3) Property 9 (PBT)
Record durability (Req 4) Property 10 (PBT with in-memory write-ahead log)
Reversal safety (Req 7) Property 11 (PBT)
Concurrency (Req 6.4) Property 12 (concurrency PBT with parallel runners)
Notification content (Req 5) Property 13 (PBT)
Retry / reconciliation (Req 3.5, 4.7) Integration tests with simulated failures
Latency SLAs (Req 2.5, 2.6, 5.1, 5.2) Performance integration tests

Requirements Document

Introduction

This feature handles the processing of incoming debit card transactions for checking accounts. When a cardholder uses their debit card to make a purchase, withdraw cash, or perform any other debit transaction, the system must validate the transaction, check the account balance, authorize or decline the transaction, and update the account state accordingly. The feature covers the full transaction lifecycle: receipt, validation, authorization, posting, and notification.

Glossary

  • Transaction_Processor: The system component responsible for receiving, validating, and routing incoming debit card transactions.
  • Authorization_Engine: The system component that evaluates whether a transaction should be approved or declined based on account state and business rules.
  • Account_Service: The system component responsible for reading and updating checking account balances and state.
  • Notification_Service: The system component responsible for sending transaction alerts to the cardholder.
  • Transaction: A debit card event representing a request to debit funds from a checking account (e.g., purchase, ATM withdrawal, online payment).
  • Authorization_Hold: A temporary reservation of funds on the account balance pending final settlement of a transaction.
  • Available_Balance: The checking account balance minus any active authorization holds.
  • Ledger_Balance: The checking account balance reflecting only fully posted (settled) transactions.
  • Merchant: The entity initiating a point-of-sale or card-not-present transaction.
  • Cardholder: The account owner associated with the debit card used in a transaction.
  • Transaction_Record: The persisted representation of a transaction and its current status (pending, approved, declined, posted, reversed).
  • Idempotency_Key: A unique identifier used to detect and prevent duplicate transaction processing.

Requirements

Requirement 1: Transaction Receipt and Validation

User Story: As a Transaction_Processor, I want to receive and validate incoming debit card transactions, so that only well-formed transactions enter the authorization pipeline.

Acceptance Criteria

  1. WHEN an incoming transaction is received, THE Transaction_Processor SHALL validate that the transaction contains a card number, a positive numeric transaction amount, a 3-letter ISO 4217 currency code, a merchant identifier, and a timestamp.
  2. IF a received transaction is missing any required field or contains an invalid field value, THEN THE Transaction_Processor SHALL reject the transaction with an error indicating which required field(s) are missing or invalid and SHALL NOT forward it to the Authorization_Engine.
  3. IF a received transaction amount is less than or equal to zero, THEN THE Transaction_Processor SHALL reject the transaction with a validation error indicating the amount is invalid.
  4. WHEN a transaction is received with a card number that maps to an active checking account, THE Transaction_Processor SHALL forward the transaction to the Authorization_Engine.
  5. IF a received transaction contains a card number that does not map to any active checking account, THEN THE Transaction_Processor SHALL reject the transaction with reason code CARD_NOT_FOUND and SHALL NOT forward it to the Authorization_Engine.
  6. IF a received transaction contains an Idempotency_Key that matches a previously processed transaction, THEN THE Transaction_Processor SHALL return the result of the original transaction without re-processing.

Requirement 2: Authorization

User Story: As a Cardholder, I want my debit card transactions to be authorized against my available balance, so that I cannot spend more than I have.

Acceptance Criteria

  1. WHEN a validated transaction is submitted for authorization, THE Authorization_Engine SHALL first check whether the account is frozen or suspended before retrieving the Available_Balance from the Account_Service.
  2. WHEN the transaction amount is less than or equal to the Available_Balance and the account status is ACTIVE, THE Authorization_Engine SHALL approve the transaction and instruct the Account_Service to place an Authorization_Hold for the transaction amount. IF the Authorization_Hold placement fails, THEN THE Authorization_Engine SHALL reverse the approval decision and decline the transaction with reason code HOLD_PLACEMENT_FAILED.
  3. WHEN the transaction amount exceeds the Available_Balance, THE Authorization_Engine SHALL decline the transaction with a reason code of INSUFFICIENT_FUNDS.
  4. WHILE an account is frozen or suspended, THE Authorization_Engine SHALL decline all incoming transactions for that account with a reason code of ACCOUNT_UNAVAILABLE without proceeding to the balance check.
  5. WHEN a transaction is authorized, THE Authorization_Engine SHALL return an approval code and the updated Available_Balance to the Transaction_Processor within 2000 milliseconds of receiving the authorization request.
  6. WHEN a transaction is declined, THE Authorization_Engine SHALL return a decline reason code to the Transaction_Processor within 2000 milliseconds of receiving the authorization request.
  7. IF the Account_Service is unavailable when retrieving the Available_Balance, THEN THE Authorization_Engine SHALL decline the transaction with reason code SERVICE_UNAVAILABLE and SHALL NOT place an Authorization_Hold.

Requirement 3: Authorization Hold Management

User Story: As an Account_Service, I want to manage authorization holds accurately, so that the Available_Balance always reflects reserved funds.

Acceptance Criteria

  1. WHEN an Authorization_Hold is placed on an account, THE Account_Service SHALL reduce the Available_Balance by the hold amount without changing the Ledger_Balance.
  2. WHEN a held transaction is settled, THE Account_Service SHALL release the Authorization_Hold in full, reduce the Ledger_Balance by the actual settled amount, and recompute the Available_Balance as the updated Ledger_Balance minus the sum of all remaining active Authorization_Hold amounts.
  3. IF a held transaction is reversed or reaches 7 calendar days from the hold placement timestamp without settlement, THEN THE Account_Service SHALL release the Authorization_Hold, restore the Available_Balance by the hold amount, and preserve the Ledger_Balance unchanged.
  4. THE Account_Service SHALL ensure the Available_Balance equals the Ledger_Balance minus the sum of all active Authorization_Hold amounts at all times.
  5. IF a hold-release or ledger-update operation fails during settlement or reversal, THEN THE Account_Service SHALL retry the failed operation up to 3 times and, if all retries fail, flag the Transaction_Record for manual reconciliation without partially applying the balance changes and SHALL stop all further processing for that transaction until manual intervention resolves the flagged record.

Requirement 4: Transaction Record Persistence

User Story: As a Transaction_Processor, I want every transaction attempt to be persisted with its outcome, so that there is a complete audit trail.

Acceptance Criteria

  1. WHEN a transaction is received, THE Transaction_Processor SHALL persist a Transaction_Record with status PENDING before forwarding to the Authorization_Engine. IF the initial persist fails, THEN THE Transaction_Processor SHALL reject the transaction and SHALL NOT forward it to the Authorization_Engine.
  2. WHEN a transaction is approved, THE Transaction_Processor SHALL update the Transaction_Record status to APPROVED and record the approval code and timestamp.
  3. WHEN a transaction is declined, THE Transaction_Processor SHALL update the Transaction_Record status to DECLINED and record the decline reason code and timestamp.
  4. WHEN a transaction is settled, THE Transaction_Processor SHALL update the Transaction_Record status to POSTED and record the settlement amount and timestamp.
  5. WHEN a transaction is reversed, THE Transaction_Processor SHALL update the Transaction_Record status to REVERSED and record the reversal timestamp and reason.
  6. THE Transaction_Processor SHALL persist all Transaction_Record updates such that no status transition committed before a system restart is absent or altered upon recovery.
  7. IF a Transaction_Record status update fails after authorization for any transaction (approved or declined), THEN THE Transaction_Processor SHALL retry the update up to 3 times and, if all retries fail, flag the record for manual reconciliation without rolling back the authorization decision.

Requirement 5: Cardholder Notification

User Story: As a Cardholder, I want to receive real-time alerts for debit card activity on my checking account, so that I am aware of all transactions.

Acceptance Criteria

  1. WHEN a transaction is approved, THE Notification_Service SHALL send the Cardholder an alert containing the merchant name, transaction amount, and updated Available_Balance within 5 seconds of authorization.
  2. WHEN a transaction is declined, THE Notification_Service SHALL send the Cardholder an alert containing the merchant name, transaction amount, and decline reason code within 5 seconds of the decline decision.
  3. IF a Cardholder has disabled transaction notifications, THEN THE Notification_Service SHALL suppress alerts for that Cardholder's account based on the notification preference setting at the time of delivery attempt.
  4. IF the Notification_Service fails to receive a delivery confirmation within 30 seconds of a delivery attempt, THEN THE Notification_Service SHALL retry after 30 seconds, up to a maximum of 3 total attempts, after which the retry limit is enforced and no further attempts are made. IF all 3 attempts fail, THEN THE Notification_Service SHALL log the failure with the Transaction_Record identifier and delivery attempt count and SHALL NOT retry further.

Requirement 6: Idempotency and Duplicate Prevention

User Story: As a Transaction_Processor, I want to detect and handle duplicate transaction submissions, so that a Cardholder is never charged more than once for the same transaction.

Acceptance Criteria

  1. WHEN a transaction is received without an Idempotency_Key provided by the originator, THE Transaction_Processor SHALL assign a unique Idempotency_Key to that transaction upon receipt.
  2. WHEN a transaction is received with an Idempotency_Key matching an existing Transaction_Record that has reached a terminal status (APPROVED, DECLINED, POSTED, or REVERSED) within the past 24 hours, THE Transaction_Processor SHALL return the stored status and result of the original transaction without creating a new record or modifying account balances.
  3. IF a transaction is received with an Idempotency_Key matching an existing Transaction_Record that is still in PENDING status, THEN THE Transaction_Processor SHALL return the PENDING status without re-processing or creating a new record.
  4. WHEN multiple transactions for the same account are processed concurrently, THE Transaction_Processor SHALL ensure the Available_Balance is fully updated to reflect each transaction's complete effect before the next transaction for that account is evaluated.

Requirement 7: Transaction Reversal

User Story: As a Cardholder, I want authorized transactions to be reversible, so that erroneous or disputed charges can be corrected.

Acceptance Criteria

  1. WHEN a reversal request is received for an APPROVED transaction, THE Transaction_Processor SHALL instruct the Account_Service to release the associated Authorization_Hold and SHALL update the Transaction_Record status to REVERSED. IF the Account_Service fails to release the hold, THEN THE Transaction_Processor SHALL return an error with reason code REVERSAL_FAILED and SHALL NOT update the Transaction_Record status.
  2. WHEN a reversal request is received for a POSTED transaction, THE Transaction_Processor SHALL instruct the Account_Service to credit the Ledger_Balance by the original transaction amount and update the Available_Balance accordingly, and SHALL update the Transaction_Record status to REVERSED. IF the Account_Service fails to apply the credit, THEN THE Transaction_Processor SHALL return an error with reason code REVERSAL_FAILED and SHALL NOT update the Transaction_Record status.
  3. IF a reversal request references a Transaction_Record that does not exist, THEN THE Transaction_Processor SHALL return an error with reason code TRANSACTION_NOT_FOUND.
  4. IF a reversal request references a Transaction_Record with status REVERSED, THEN THE Transaction_Processor SHALL return an error with reason code ALREADY_REVERSED.
  5. IF a reversal request references a Transaction_Record with status PENDING or DECLINED, THEN THE Transaction_Processor SHALL return an error with reason code REVERSAL_NOT_PERMITTED indicating the current transaction status.

Implementation Plan: Debit Card Transaction Processing

Overview

This plan breaks the debit card transaction processing feature into incremental coding steps, following the four-component architecture defined in the design: Transaction_Processor, Authorization_Engine, Account_Service, and Notification_Service. Each task builds directly on the previous one, and all components are wired together in the final integration step.

TypeScript is used throughout, with fast-check for property-based tests and a standard test runner (Jest / Vitest) for unit and integration tests.


Tasks

  • 1. Set up project structure, types, and shared interfaces

    • Create directory structure: src/types, src/services, src/processor, src/engine, tests/unit, tests/property, tests/integration
    • Define all shared TypeScript types and enums: TransactionStatus, HoldStatus, AccountStatus, TransactionRecord, AuthorizationHold, Account, IdempotencyRecord, AuthorizationResult, HoldResult, BalanceResult, etc.
    • Define service interfaces: IAccountService, IAuthorizationEngine, INotificationService, ITransactionStore, IIdempotencyStore
    • Configure TypeScript compiler (tsconfig.json) and test runner (jest.config.ts or vitest.config.ts)
    • Install dependencies: fast-check, test runner, and any assertion library
    • Requirements: 1.1, 2.1, 3.1, 4.1, 5.1, 6.1, 7.1
  • 2. Implement Account_Service

    • 2.1 Implement in-memory Account_Service with atomic balance operations

      • Implement getAccountStatus, getAvailableBalance, placeHold, releaseHold, settleTransaction, and creditLedger methods
      • Enforce the Available_Balance = Ledger_Balance − Σ(active hold amounts) invariant atomically on every mutation
      • Model hold expiry: holds older than 7 calendar days are treated as released when computing Available_Balance
      • Use per-account locking (mutex or equivalent) to prevent concurrent partial updates
      • Requirements: 3.1, 3.2, 3.3, 3.4
    • [ ]* 2.2 Write property test for the Available_Balance invariant (Property 4)

      • Property 4: Available_Balance invariant
      • Validates: Requirements 3.1, 3.2, 3.3, 3.4
      • Use arbAccount and arbHoldSet generators; assert invariant holds after every sequence of hold/release/settle/credit operations
      • Tag: // Feature: debit-card-transaction-processing, Property 4: Available_Balance invariant
      • Minimum 100 iterations
    • [ ]* 2.3 Write property test for hold placement atomicity (Property 7)

      • Property 7: Hold placement atomicity
      • Validates: Requirements 2.2, 3.1
      • Assert that a successful placeHold reduces Available_Balance by exactly the hold amount and leaves Ledger_Balance unchanged; assert that a failed placeHold leaves both balances unchanged
      • Tag: // Feature: debit-card-transaction-processing, Property 7: Hold placement atomicity
      • Minimum 100 iterations
    • [ ]* 2.4 Write property test for settlement balance correctness (Property 8)

      • Property 8: Settlement balance correctness
      • Validates: Requirements 3.2
      • Assert that after settleTransaction, Ledger_Balance decreases by the settled amount, the hold is released, and Available_Balance equals the updated Ledger_Balance minus remaining active holds
      • Tag: // Feature: debit-card-transaction-processing, Property 8: Settlement balance correctness
      • Minimum 100 iterations
    • [ ]* 2.5 Write property test for hold expiry and reversal balance restoration (Property 9)

      • Property 9: Hold expiry and reversal restore balance
      • Validates: Requirements 3.3
      • Assert that after releaseHold (via reversal or expiry), Available_Balance increases by the hold amount and Ledger_Balance is unchanged
      • Tag: // Feature: debit-card-transaction-processing, Property 9: Hold expiry and reversal restore balance
      • Minimum 100 iterations
  • 3. Implement Transaction_Processor input validation

    • 3.1 Implement the validation layer in Transaction_Processor

      • Validate presence of all required fields (card_number, amount, currency, merchant_id, timestamp)
      • Validate amount > 0
      • Validate currency matches the three-letter ISO 4217 regex (/^[A-Z]{3}$/)
      • Validate timestamp is a parseable ISO 8601 datetime
      • Return structured VALIDATION_ERROR response listing each invalid or missing field
      • Requirements: 1.1, 1.2, 1.3
    • [ ]* 3.2 Write property test for validation rejects all invalid transactions (Property 1)

      • Property 1: Validation rejects all invalid transactions
      • Validates: Requirements 1.1, 1.2, 1.3
      • Use arbTransaction generator producing arbitrary transactions including those missing required fields, with amount ≤ 0, and with malformed currency codes
      • Assert that every invalid transaction is rejected with VALIDATION_ERROR and that no Transaction_Record is created and the Authorization_Engine is never invoked
      • Tag: // Feature: debit-card-transaction-processing, Property 1: Validation rejects all invalid transactions
      • Minimum 100 iterations
  • 4. Implement card lookup and idempotency check in Transaction_Processor

    • 4.1 Implement card-to-account lookup and CARD_NOT_FOUND rejection

      • Resolve card_number to an account_id using the account store
      • If no active checking account is found, reject immediately with CARD_NOT_FOUND without forwarding to Authorization_Engine
      • Requirements: 1.4, 1.5
    • [ ]* 4.2 Write property test for card lookup correctness (Property 2)

      • Property 2: Card lookup correctness
      • Validates: Requirements 1.5
      • Use arbTransaction with card numbers that may or may not exist in the in-memory account store
      • Assert that every transaction with an unknown card number returns CARD_NOT_FOUND and the Authorization_Engine is never invoked
      • Tag: // Feature: debit-card-transaction-processing, Property 2: Card lookup correctness
      • Minimum 100 iterations
    • 4.3 Implement idempotency check and Idempotency_Key assignment

      • If no idempotency_key is supplied, assign a unique UUID before further processing
      • Look up idempotency_key in the Idempotency Index before any other processing
      • If a terminal-status match is found within the 24-hour window, return the stored result payload immediately without creating a record or mutating balances
      • If a PENDING-status match is found, return the PENDING result without re-processing
      • Requirements: 6.1, 6.2, 6.3
    • [ ]* 4.4 Write property test for idempotency round-trip (Property 3)

      • Property 3: Idempotency round-trip
      • Validates: Requirements 1.6, 6.2, 6.3
      • Use arbIdempotencyKey generator; submit identical transactions twice with the same key; assert the second call returns identical status and payload and that no new Transaction_Record is created and no balance mutation occurs
      • Tag: // Feature: debit-card-transaction-processing, Property 3: Idempotency round-trip
      • Minimum 100 iterations
  • 5. Implement write-ahead persistence in Transaction_Processor

    • 5.1 Implement Transaction_Record persistence with write-ahead guarantee

      • Persist a PENDING Transaction_Record (with all available fields populated) before invoking the Authorization_Engine
      • If the initial persist fails, reject the transaction with PROCESSING_ERROR and do not forward to Authorization_Engine
      • Implement updateTransactionRecord for all status transitions (APPROVED, DECLINED, POSTED, REVERSED) with retry-up-to-3 logic and reconciliation_flag on exhaustion
      • Persist all record updates durably so no committed status transition is lost on restart
      • Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7
    • [ ]* 5.2 Write property test for Transaction_Record write-ahead durability (Property 10)

      • Property 10: Transaction_Record write-ahead durability
      • Validates: Requirements 4.1, 4.6
      • Use an in-memory write-ahead log; generate arbitrary authorization sequences and assert that every status transition is recorded in the log before it is visible to callers; simulate crashes and assert no committed transition is absent on recovery
      • Tag: // Feature: debit-card-transaction-processing, Property 10: Transaction_Record write-ahead durability
      • Minimum 100 iterations
  • 6. Implement Authorization_Engine

    • 6.1 Implement the Authorization_Engine decision logic

      • Implement the ordered decision steps from the design: account-status check → balance retrieval → overdraft check → active-status guard → hold placement → approval
      • Return APPROVED with approval_code and updated available_balance, or DECLINED with the appropriate reason code
      • Enforce the 2000 ms timeout; decline with SERVICE_UNAVAILABLE on breach
      • Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7
    • [ ]* 6.2 Write property test for no-overdraft guarantee (Property 5)

      • Property 5: No overdraft
      • Validates: Requirements 2.3
      • Use arbTransaction and arbAccount; for any transaction where amount > Available_Balance, assert the engine declines with INSUFFICIENT_FUNDS and no hold is placed
      • Tag: // Feature: debit-card-transaction-processing, Property 5: No overdraft
      • Minimum 100 iterations
    • [ ]* 6.3 Write property test for frozen/suspended account blocks (Property 6)

      • Property 6: Frozen/suspended account blocks all transactions
      • Validates: Requirements 2.4
      • Generate accounts with FROZEN or SUSPENDED status and arbitrary transactions; assert all are declined with ACCOUNT_UNAVAILABLE without any balance check or hold placement
      • Tag: // Feature: debit-card-transaction-processing, Property 6: Frozen/suspended account blocks all transactions
      • Minimum 100 iterations
  • 7. Checkpoint — core authorization path

    • Ensure all tests pass for tasks 1–6. Verify that a well-formed transaction against an active account with sufficient balance produces an APPROVED response with a hold placed, and that all decline paths (insufficient funds, frozen account, service unavailable, hold failure) are exercised.
    • Ensure all tests pass, ask the user if questions arise.
  • 8. Implement Transaction_Processor settlement flow

    • 8.1 Implement the POST /transactions/{transaction_id}/settle endpoint

      • Look up Transaction_Record by transaction_id; return TRANSACTION_NOT_FOUND if absent
      • Invoke Account_Service.settleTransaction(account_id, hold_id, settlement_amount)
      • On success, update Transaction_Record status to POSTED, recording settlement_amount and posted_at
      • On Account_Service failure, retry up to 3 times; on exhaustion set reconciliation_flag = true
      • Requirements: 3.2, 4.4
    • [ ]* 8.2 Write unit tests for settlement flow

      • Test happy path: APPROVED → POSTED with correct balance update
      • Test settlement on a non-existent transaction: expect TRANSACTION_NOT_FOUND
      • Test retry-then-reconcile path when Account_Service fails persistently
      • Requirements: 3.2, 4.4
  • 9. Implement Transaction_Processor reversal flow

    • 9.1 Implement the POST /transactions/{transaction_id}/reverse endpoint

      • Look up Transaction_Record; return TRANSACTION_NOT_FOUND if absent
      • Enforce terminal-status guards: return ALREADY_REVERSED for REVERSED, REVERSAL_NOT_PERMITTED for PENDING or DECLINED
      • For APPROVED: call Account_Service.releaseHold; on success update record to REVERSED
      • For POSTED: call Account_Service.creditLedger; on success update record to REVERSED
      • On Account_Service failure return REVERSAL_FAILED without updating the record
      • Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
    • [ ]* 9.2 Write property test for reversal terminal-status safety (Property 11)

      • Property 11: Reversal terminal-status safety
      • Validates: Requirements 7.3, 7.4, 7.5
      • Generate reversal requests against records in all possible statuses (PENDING, DECLINED, APPROVED, POSTED, REVERSED) and against non-existent IDs; assert the correct error code is returned in each case and that no balance mutation occurs for the invalid cases
      • Tag: // Feature: debit-card-transaction-processing, Property 11: Reversal terminal-status safety
      • Minimum 100 iterations
    • [ ]* 9.3 Write unit tests for reversal flow

      • Test APPROVED → REVERSED (hold released)
      • Test POSTED → REVERSED (ledger credited)
      • Test each error case: TRANSACTION_NOT_FOUND, ALREADY_REVERSED, REVERSAL_NOT_PERMITTED for PENDING and DECLINED
      • Test REVERSAL_FAILED when Account_Service rejects the operation
      • Requirements: 7.1, 7.2, 7.3, 7.4, 7.5
  • 10. Implement Notification_Service

    • 10.1 Implement Notification_Service with retry logic and preference suppression

      • Implement sendAlert(cardholder_id, alert_type, payload) for APPROVED and DECLINED alert types
      • Include merchant_name, amount, available_balance in approved alerts; include merchant_name, amount, decline_reason in declined alerts
      • Check cardholder notification preference before delivery; suppress immediately if opted out
      • Retry up to 3 total attempts with 30-second intervals on delivery failure; log transaction_id and attempt count on exhaustion
      • Requirements: 5.1, 5.2, 5.3, 5.4
    • [ ]* 10.2 Write property test for notification content completeness (Property 13)

      • Property 13: Notification content completeness
      • Validates: Requirements 5.1, 5.2
      • Generate arbitrary approved and declined transactions; assert that every approved-transaction alert contains merchant_name, amount, and available_balance; assert that every declined-transaction alert contains merchant_name, amount, and decline_reason
      • Tag: // Feature: debit-card-transaction-processing, Property 13: Notification content completeness
      • Minimum 100 iterations
    • [ ]* 10.3 Write unit tests for Notification_Service

      • Test opted-out cardholder: alert is suppressed with zero delivery attempts
      • Test retry exhaustion: 3 failed attempts result in a logged failure and no further retries
      • Test successful delivery on first attempt
      • Requirements: 5.3, 5.4
  • 11. Wire components together in Transaction_Processor

    • 11.1 Connect Authorization_Engine and Notification_Service into the Transaction_Processor authorization pipeline

      • After validation, card lookup, idempotency check, and PENDING persist: invoke Authorization_Engine
      • On APPROVED: update record to APPROVED, fire async Notification_Service.sendAlert
      • On DECLINED: update record to DECLINED, fire async Notification_Service.sendAlert
      • Return the authorization response (including approval_code / decline_reason and available_balance when approved) to the caller
      • Requirements: 1.4, 2.5, 2.6, 4.2, 4.3, 5.1, 5.2
    • [ ]* 11.2 Write integration tests for the full authorization pipeline

      • Test end-to-end: valid transaction → APPROVED, hold placed, record updated, notification triggered
      • Test end-to-end: insufficient funds → DECLINED, no hold, record updated, notification triggered
      • Test end-to-end: frozen account → DECLINED, no balance check, record updated, notification triggered
      • Test end-to-end: duplicate submission with same idempotency key returns original result
      • Requirements: 1.1, 2.1, 2.2, 2.3, 2.4, 4.2, 4.3, 5.1, 5.2, 6.2
  • 12. Implement concurrent transaction serialization

    • 12.1 Implement per-account serialization for concurrent authorizations

      • Add per-account locking (or equivalent serialization mechanism) around the balance-read → hold-placement sequence in Authorization_Engine + Account_Service to prevent races
      • Ensure that when multiple in-flight transactions target the same account, each sees the fully committed Available_Balance from all prior transactions
      • Requirements: 6.4
    • [ ]* 12.2 Write property test for concurrent transaction serialization (Property 12)

      • Property 12: Concurrent transaction serialization
      • Validates: Requirements 6.4
      • Launch parallel authorization requests against the same account with amounts that collectively would overdraw the account if races occurred; assert the final Available_Balance is ≥ 0 and equals the Ledger_Balance minus all active holds; assert that no two transactions consumed the same funds
      • Tag: // Feature: debit-card-transaction-processing, Property 12: Concurrent transaction serialization
      • Minimum 100 iterations
  • 13. Final checkpoint — full pipeline

    • Run the complete test suite (unit, property, integration). Confirm all 13 correctness properties have associated PBT tasks and all requirements (1–7) are covered by at least one test. Ensure no reconciliation flags are set spuriously. Ask the user if any questions arise.
    • Ensure all tests pass, ask the user if questions arise.

Notes

  • Tasks marked with * are optional and can be skipped for a faster MVP
  • Each task references specific requirements for traceability
  • Checkpoints (tasks 7 and 13) ensure incremental validation
  • Property-based tests use fast-check with a minimum of 100 iterations each
  • All property tests include the required tag comment: // Feature: debit-card-transaction-processing, Property {N}: {text}
  • Account_Service and Notification_Service are replaced with in-memory fakes for property tests
  • Concurrency property test (12.2) requires a parallel-runner harness (e.g., Promise.all with shared in-memory state)

Task Dependency Graph

{
  "waves": [
    { "id": 0, "tasks": ["2.1", "3.1"] },
    { "id": 1, "tasks": ["2.2", "2.3", "2.4", "2.5", "4.1", "4.3"] },
    { "id": 2, "tasks": ["3.2", "4.2", "4.4", "5.1"] },
    { "id": 3, "tasks": ["5.2", "6.1"] },
    { "id": 4, "tasks": ["6.2", "6.3", "8.1", "9.1", "10.1"] },
    { "id": 5, "tasks": ["8.2", "9.2", "9.3", "10.2", "10.3", "11.1"] },
    { "id": 6, "tasks": ["11.2", "12.1"] },
    { "id": 7, "tasks": ["12.2"] }
  ]
}
@dgobaud

dgobaud commented Jun 22, 2026

Copy link
Copy Markdown
Author

https://kiro.dev/ demo - it generates the spec and asks questions.

then you can have it generate tech design, analyze requirements for bugs (maybe should have done first), and generate task list.

"user has a checking account with a debit card. process incoming debit card transactions."

  1. first it generates the spec and asks some questions.
  2. then i had it generate tech design
  3. then analyze requirements
  4. then generate task list

https://gist.github.com/dgobaud/30acb5b637a097231d97f7ef93d63fc4

SPEC GENERATION

image image image image image image

REQUIREMENTS ANALYSIS

image image

GENERATE TASK LIST

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment