Skip to content

Use Cases

Tiana_ edited this page May 30, 2026 · 1 revision

Use Cases

Functional capabilities of FinCore Engine - what the system must be able to do, expressed in user-centric terms. Each use case is independently testable and traces to one or more Epics.

Conventions

  • Actor: who initiates the use case (Client App, Operator, Webhook Producer, Internal Service, Scheduled Job)
  • Preconditions: state required before the use case can run
  • Main flow: happy path
  • Alternative flows: variations and exceptions
  • Postconditions: state after success
  • Acceptance criteria: testable statements (used to derive contract tests)

UC-01 - Create Account

Field Value
ID UC-01
Epic Epic-01 Ledger
Actor Client App (authenticated via OAuth2 client credentials)
Goal Open a new ledger account with explicit type and currency

Preconditions

  • Client has a valid JWT with role LEDGER_WRITER
  • Currency code is ISO 4217

Main flow

  1. Client sends POST /v1/accounts with { name, type, currency, metadata? } and Idempotency-Key header
  2. Service validates the request (currency in ISO 4217, type in allowed enum, name length 1..255)
  3. Service checks the idempotency key; if present and matches, returns the previous response
  4. Service generates accountId = UUID.randomUUID()
  5. Service persists the account with status = ACTIVE, version = 0, created_at = now()
  6. Service emits account.created event via Outbox
  7. Service returns 201 Created with the full account representation

Alternative flows

  • A1 - Idempotency replay: same Idempotency-Key and same request body → return the original 201 response (cached for 24h)
  • A2 - Idempotency conflict: same Idempotency-Key, different request body → 409 Conflict (RFC 7807)
  • A3 - Validation error: invalid currency / unsupported type / empty name → 400 Bad Request
  • A4 - Auth missing/invalid: → 401 Unauthorized or 403 Forbidden

Postconditions

  • Row exists in accounts with the returned id
  • Row exists in outbox_events with event_type = account.created
  • Audit trail captures actor_id, correlation_id, request_hash

Acceptance criteria

  • AC-01.1: Concurrent identical POSTs with same idempotency key produce exactly one row in accounts
  • AC-01.2: Account version starts at 0 and increments on each update
  • AC-01.3: account.created event payload includes id, type, currency, created_at
  • AC-01.4: Response p99 latency < 200ms under normal load
  • AC-01.5: All write paths logged with structured JSON including correlation_id

UC-02 - Get Account Balance

Field Value
ID UC-02
Epic Epic-01 Ledger
Actor Client App (any authenticated reader)
Goal Retrieve the current balance of an account in its native currency

Preconditions

  • Account exists and is not soft-deleted
  • Caller has role LEDGER_READER or higher

Main flow

  1. Client sends GET /v1/accounts/{id}/balance
  2. Service loads the account; if missing → 404
  3. Service computes the balance from the materialized view account_balances (sum of entries)
  4. Service returns 200 OK with { accountId, currency, balance, asOf, version }

Alternative flows

  • A1 - Fresh balance required: query parameter ?fresh=true triggers REFRESH MATERIALIZED VIEW CONCURRENTLY before returning (rate-limited to once per second per account)
  • A2 - Account closed: status != ACTIVE → balance still returned with status field

Postconditions

  • No state change
  • Read counted in metrics ledger.balance.reads_total

Acceptance criteria

  • AC-02.1: Balance == sum of all entries.amount for the account (per currency)
  • AC-02.2: Balance is consistent within a single read (snapshot isolation)
  • AC-02.3: p99 latency < 50ms for cached reads, < 200ms for fresh=true

UC-03 - List Account Entries (Statement)

Field Value
ID UC-03
Epic Epic-01 Ledger
Actor Client App (operator viewing transactions)
Goal Retrieve a paginated list of entries (debits/credits) for an account

Preconditions

  • Account exists
  • Caller has role LEDGER_READER

Main flow

  1. Client sends GET /v1/accounts/{id}/entries?from=...&to=...&cursor=...&limit=50
  2. Service validates date range (max 90 days per request)
  3. Service queries entries with cursor-based pagination
  4. Service returns 200 OK with { items, nextCursor, hasMore }

Alternative flows

  • A1 - No range provided: defaults to last 30 days
  • A2 - Invalid cursor: 400 Bad Request with explicit correlation_id

Postconditions

  • No state change

Acceptance criteria

  • AC-03.1: Entries returned in deterministic order (created_at DESC, id DESC)
  • AC-03.2: Cursor is opaque, base64-encoded, includes integrity hash
  • AC-03.3: Page returns <= limit items; hasMore=true only if there exist more matching rows

UC-04 - Post Double-Entry Transaction

Field Value
ID UC-04
Epic Epic-01 Ledger
Actor Internal service (Payments) or Client App with role LEDGER_POSTER
Goal Atomically debit one or more accounts and credit one or more accounts, preserving SUM(entries) = 0 per currency

Preconditions

  • All referenced accounts exist and are ACTIVE
  • All entries are in the same currency (single-currency transaction); multi-currency requires separate FX flow
  • Caller has role LEDGER_POSTER

Main flow

  1. Client sends POST /v1/transactions with { reference, description?, entries: [{accountId, amount, direction}] } and Idempotency-Key header
  2. Service validates: at least 2 entries, sum equals zero, no duplicate accountId+direction pairs unless explicitly allowed
  3. Service checks idempotency key
  4. In a single DB transaction (REPEATABLE_READ):
    • Insert one transactions row with status = POSTED
    • Insert N entries rows
    • Verify the invariant SUM(entries.amount) = 0 (deferred trigger check at commit)
    • Refresh affected account_balances MV rows
    • Append outbox_events row with event_type = transaction.posted
  5. On commit, service returns 201 Created with the transaction representation

Alternative flows

  • A1 - Invariant violation: trigger raises exception → 422 Unprocessable Entity, transaction rolled back
  • A2 - Account inactive: any account status != ACTIVE409 Conflict
  • A3 - Reference collision: duplicate reference (unique constraint) → 409 Conflict
  • A4 - Multi-currency entries: → 422 Unprocessable Entity
  • A5 - Optimistic lock failure: concurrent posting → retry up to 3 times, then 503

Postconditions

  • Transaction with new id exists
  • Account balances updated atomically
  • Outbox event written

Acceptance criteria

  • AC-04.1: Concurrent posts to the same accounts always produce consistent balances (no lost updates)
  • AC-04.2: Failed posts leave zero rows in transactions and entries
  • AC-04.3: Database-level trigger blocks any write that would violate SUM = 0
  • AC-04.4: Outbox event is committed in the same transaction as the ledger writes
  • AC-04.5: p99 latency < 300ms with 100 concurrent writers

UC-05 - Reverse a Posted Transaction

Field Value
ID UC-05
Epic Epic-01 Ledger
Actor Operator with role LEDGER_REVERSER
Goal Reverse a previously posted transaction by creating a compensating transaction

Preconditions

  • Original transaction exists with status = POSTED
  • Original transaction has not been reversed before
  • Caller has role LEDGER_REVERSER

Main flow

  1. Client sends POST /v1/transactions/{id}/reverse with { reason } and Idempotency-Key
  2. Service loads the original transaction
  3. Service creates a new transaction with inverted entries (debits become credits and vice versa) linked via reverses_id
  4. Original transaction marked status = REVERSED
  5. Outbox event transaction.reversed emitted

Alternative flows

  • A1 - Already reversed: 409 Conflict
  • A2 - Missing: 404 Not Found

Postconditions

  • Compensating transaction exists with same magnitude opposite directions
  • Both transactions linked via foreign keys

Acceptance criteria

  • AC-05.1: Reverse preserves invariants
  • AC-05.2: reason is mandatory and stored in audit log
  • AC-05.3: Original transaction is not deleted (immutable journal)

UC-06 - Initiate Payment

Field Value
ID UC-06
Epic Epic-02 Payments
Actor Client App (authenticated user or system)
Goal Initiate a payment from one account to another, possibly via an external bank provider

Preconditions

  • Both from_account and to_account exist
  • from_account has sufficient available balance (or overdraft allowed)
  • Caller has role PAYMENTS_INITIATOR

Main flow

  1. Client sends POST /v1/payments with { fromAccountId, toAccountId, amount, currency, reference? } and Idempotency-Key
  2. Service validates the payment (amount > 0, currencies match accounts)
  3. Service creates a Payment aggregate in state CREATED
  4. Service runs synchronous pre-checks:
    • Balance check
    • Decision engine evaluation (UC-13)
  5. If decision = APPROVE, payment moves to state PROCESSING and posts a ledger transaction (UC-04)
  6. Outbox event payment.created is emitted
  7. Service returns 202 Accepted with the payment representation

Alternative flows

  • A1 - Insufficient balance: 422 Unprocessable Entity, payment moves to state FAILED
  • A2 - Decision = REJECT: payment moves to REJECTED, returns 202 with explanation
  • A3 - Decision = REVIEW: payment moves to PENDING_REVIEW, returns 202

Postconditions

  • payments row exists with appropriate state
  • Ledger transaction exists if approved
  • Outbox event committed

Acceptance criteria

  • AC-06.1: Payment lifecycle states follow the defined state machine (no illegal transitions)
  • AC-06.2: Idempotent retries return the same payment
  • AC-06.3: Decision engine is invoked exactly once per attempt

UC-07 - Track Payment Status

Field Value
ID UC-07
Epic Epic-02 Payments
Actor Client App
Goal Retrieve the current status of a payment, including history

Main flow

  1. Client sends GET /v1/payments/{id} (optionally ?include=events)
  2. Service returns { id, state, amount, ..., events?: [...] } where events show state transitions

Acceptance criteria

  • AC-07.1: Event log is append-only
  • AC-07.2: All state transitions include actor, timestamp, reason

UC-08 - Handle Payment Provider Webhook

Field Value
ID UC-08
Epic Epic-02 Payments
Actor External Bank Provider (HMAC-signed webhook)
Goal Update a payment status based on a callback from the bank

Preconditions

  • Webhook is HMAC-signed with shared secret per provider
  • Provider is registered

Main flow

  1. Provider POSTs to /v1/webhooks/payments/{providerId} with signed body
  2. Service verifies HMAC signature; if invalid → 401 Unauthorized (logged for forensic purposes)
  3. Service deduplicates the webhook by provider_event_id (stored in processed_webhooks table)
  4. Service updates the corresponding payment via state machine
  5. If terminal state (COMPLETED or FAILED), ledger may post compensating entries
  6. Outbox event payment.<status> emitted
  7. Service responds 200 OK

Alternative flows

  • A1 - Duplicate webhook: deduplicated, 200 OK returned (idempotent for provider)
  • A2 - Unknown payment: 404 Not Found

Acceptance criteria

  • AC-08.1: Replay attacks blocked by deduplication
  • AC-08.2: Signature verification mandatory; secret rotated quarterly
  • AC-08.3: Webhook processing p99 < 500ms

UC-09 - Retry Failed Payment

Field Value
ID UC-09
Epic Epic-02 Payments
Actor Scheduled job or operator
Goal Retry a payment that failed due to transient error

Preconditions

  • Payment is in state FAILED with retryable = true
  • Retry attempts < max retries (default 5)

Main flow

  1. Job loads pending retries with exponential backoff (1m, 5m, 15m, 1h, 6h)
  2. For each, re-invokes the bank adapter
  3. Updates payment state based on the response

Acceptance criteria

  • AC-09.1: Retries are idempotent - same Idempotency-Key reused
  • AC-09.2: After max retries, payment moves to PERMANENTLY_FAILED and emits alert

UC-10 - Run KYC Verification

Field Value
ID UC-10
Epic Epic-04 Compliance
Actor Client App (during user onboarding)
Goal Initiate identity verification through a pluggable provider

Preconditions

  • User is authenticated
  • KYC provider is configured (sandbox by default)

Main flow

  1. Client POSTs /v1/kyc/sessions with { userId, providerId? }
  2. Service creates a KYC session in state PENDING
  3. Service calls the configured KycProvider adapter (sandbox returns mock URL)
  4. Service returns { sessionId, providerSessionUrl, status }

Alternative flows

  • A1 - Provider down: queue for retry, return 202 with pending
  • A2 - User uploads doc: POST /v1/kyc/sessions/{id}/documents
  • A3 - Provider callback: handled via webhook (UC-08 style), updates session to APPROVED/REJECTED/MANUAL_REVIEW

Acceptance criteria

  • AC-10.1: PII is encrypted at rest (AES-256 via DB-level encryption)
  • AC-10.2: Provider switching requires no API contract change
  • AC-10.3: All session state transitions are auditable

UC-11 - Run AML Check on Transaction

Field Value
ID UC-11
Epic Epic-04 Compliance
Actor Internal service (triggered on every payment via Kafka consumer)
Goal Evaluate a transaction against AML rules and flag suspicious activity

Preconditions

  • Transaction posted (UC-04)

Main flow

  1. Compliance service consumes transaction.posted event
  2. Loads applicable AML rules from aml_rules table
  3. Evaluates rules sequentially (stop on first match or evaluate all per config)
  4. If matched, creates an aml_alert row and emits aml.flagged event
  5. Optionally creates a compliance_case for human review

Acceptance criteria

  • AC-11.1: Rules engine is deterministic - same input produces same output
  • AC-11.2: Rule changes are versioned; old transactions evaluated against rules at time of post
  • AC-11.3: p99 evaluation < 50ms

UC-12 - Resolve Compliance Case (Manual Review)

Field Value
ID UC-12
Epic Epic-04 Compliance
Actor Compliance officer (human)
Goal Approve or reject a flagged transaction after manual investigation

Main flow

  1. Officer fetches GET /v1/compliance/cases/{id}
  2. Officer reviews transaction details, rule that fired, AI explanation (if available via plugin)
  3. Officer POSTs /v1/compliance/cases/{id}/resolve with { decision: APPROVE|REJECT, reason }
  4. Service updates the case state, optionally reverses the transaction (UC-05) if rejected
  5. Outbox event compliance.case.resolved emitted

Acceptance criteria

  • AC-12.1: Resolution requires reason (audit trail)
  • AC-12.2: Resolved cases are immutable

UC-13 - Evaluate Decision (Rule Engine)

Field Value
ID UC-13
Epic Epic-05 Decision Engine
Actor Internal service (Payments, Compliance, Onboarding)
Goal Run a decision evaluation against active rules and return a deterministic result

Preconditions

  • Decision rules exist and are active

Main flow

  1. Caller invokes the engine with { context: { ... }, ruleSetId? }
  2. Engine loads active rules sorted by priority
  3. Engine evaluates conditions deterministically against the context
  4. Engine returns { decision: APPROVE|REJECT|REVIEW, matchedRules: [...], explanation }
  5. Engine writes a row to decision_logs for audit

Alternative flows

  • A1 - No rules match: returns { decision: APPROVE, matchedRules: [] } (configurable default)
  • A2 - Rule error: catch and log, fail-safe to REVIEW

Acceptance criteria

  • AC-13.1: Engine is pure (no side effects beyond audit log)
  • AC-13.2: p99 evaluation < 10ms for typical rule sets (< 100 rules)
  • AC-13.3: Every decision is reproducible from decision_logs (input context + rule version)

UC-14 - Manage Decision Rules (CRUD + Versioning)

Field Value
ID UC-14
Epic Epic-05 Decision Engine
Actor Risk operator with role DECISION_ADMIN
Goal Create, update, deactivate decision rules with full version history

Main flow

  1. Operator POSTs/PUTs to /v1/decision/rules with JSON DSL definition
  2. Service validates DSL syntax and references
  3. Service stores a new version in rule_versions; the latest version per rule_id is what decision_logs references at evaluation time
  4. Activation requires explicit POST /v1/decision/rules/{id}/activate

Acceptance criteria

  • AC-14.1: Rules are versioned; old versions are queryable
  • AC-14.2: Invalid DSL is rejected with explicit error pointing to the offending field
  • AC-14.3: Rule changes are audited (who, when, what)

UC-15 - Subscribe to Events (Webhooks)

Field Value
ID UC-15
Epic Epic-09 Developer Experience
Actor Client App (webhook consumer)
Goal Receive events about ledger / payments / compliance / decision lifecycle

Main flow

  1. Client POSTs /v1/webhooks/subscriptions with { url, events: [...], secret }
  2. Service stores subscription
  3. On every relevant outbox event, dispatcher signs the payload (HMAC) and POSTs to subscriber URL
  4. On 2xx response → mark delivered
  5. On non-2xx → retry with exponential backoff (1m, 5m, 30m, 6h, 24h, 3d, 7d) up to 7 attempts

Acceptance criteria

  • AC-15.1: Delivery is at-least-once with HMAC integrity
  • AC-15.2: Subscriber can verify signature with their stored secret
  • AC-15.3: Delivery success rate > 99.9% within 30s under normal conditions
  • AC-15.4: Failed deliveries are inspectable via GET /v1/webhooks/subscriptions/{id}/deliveries

Use Case → Epic Traceability

Use Case Epic Priority for v0.1.0
UC-01 Create Account Epic-01 Ledger P0
UC-02 Get Balance Epic-01 Ledger P0
UC-03 List Entries Epic-01 Ledger P0
UC-04 Post Transaction Epic-01 Ledger P0
UC-05 Reverse Transaction Epic-01 Ledger P1
UC-06 Initiate Payment Epic-02 Payments P1 (v0.2.0)
UC-07 Track Payment Epic-02 Payments P1 (v0.2.0)
UC-08 Webhook Handling Epic-02 Payments P1 (v0.2.0)
UC-09 Retry Payment Epic-02 Payments P2 (v0.2.0)
UC-10 KYC Verification Epic-04 Compliance P2 (v0.3.0)
UC-11 AML Check Epic-04 Compliance P2 (v0.3.0)
UC-12 Resolve Case Epic-04 Compliance P2 (v0.3.0)
UC-13 Evaluate Decision Epic-05 Decision Engine P1 (v0.2.0)
UC-14 Manage Rules Epic-05 Decision Engine P1 (v0.2.0)
UC-15 Webhook Subscriptions Epic-09 Developer Experience P2 (v0.4.0)

Out of scope (explicit non-use-cases)

The following are deliberately not part of FinCore Engine OSS:

  • No ML risk scoring (only RiskScorer plug-in interface; implementations are out of scope)
  • No Real bank/KYC provider integrations (only sandbox + plug-in interfaces)
  • No Customer portal / user-facing UI beyond the dashboard sandbox
  • No Multi-tenancy with tenant isolation guarantees (each adopter handles tenancy themselves)
  • No Mobile SDKs (web SDK only)
  • No FX rate management (delegated to bank provider)
  • No Custodial wallet management for crypto
  • No Direct integration with sanctioned-list providers (only SanctionsProvider interface)

Clone this wiki locally