Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Task 10 — Banking and Payment Processing Platform

Objective

Create a banking and payment-processing platform.

The system manages:

  • customers
  • bank accounts
  • currency balances
  • payment cards
  • merchants
  • transfers
  • card payments
  • payment authorization
  • capture
  • settlement
  • refunds
  • reversals
  • fees
  • transaction limits
  • account holds
  • ledger entries
  • statements
  • fraud rules
  • account freezes
  • disputes
  • idempotency
  • audit history

The implementation must preserve financial consistency across the complete transaction lifecycle.

This task is intentionally focused on modeling financial state rather than implementing a real banking protocol.

The most important rule is:

financial state must never be changed without a corresponding financial record

Domain Overview

Conceptually:

        Customer
           |
           +-- Account
           |      |
           |      +-- Currency Balance
           |      +-- Ledger
           |      +-- Transfer
           |
           +-- Card
                  |
                  v
               Payment
                  |
                  +-- Authorization
                  +-- Capture
                  +-- Settlement
                  +-- Refund
                  +-- Reversal

Merchant side:

        Merchant
           |
           +-- Merchant Account
           |
           +-- Payments
           |
           +-- Settlements

Risk and control:

        Transaction
           |
           +-- Limits
           +-- Fraud Rules
           +-- Holds
           +-- Disputes
           +-- Audit History

Customer

A customer contains:

  • ID
  • First Name
  • Last Name
  • Country
  • Status

For example:

type Customer struct {
    ID        string
    FirstName string
    LastName  string
    Country   string
    Status    CustomerStatus
}

Possible states:

  • active
  • restricted
  • suspended
  • closed

Account

A customer may own multiple accounts. A possible model is:

type Account struct {
    ID         string
    CustomerID string
    Type       AccountType
    Status     AccountStatus
}

Possible account types:

  • current
  • savings
  • business

Possible states:

  • active
  • frozen
  • restricted
  • closed

Currency

Support at least:

  • EUR
  • USD
  • GBP
  • RSD
  • JPY

Use a controlled currency representation.

Account Balance

An account may contain balances in several currencies. A possible model is:

type AccountBalance struct {
    AccountID string
    Currency  Currency
    Available float64
    Held      float64
}

Conceptually:

total balance = available + held

Money reserved by an authorization moves from available to held without immediately becoming a completed payment.

Money Representation

Financial calculations must avoid uncontrolled floating-point rounding errors.

A recommended implementation represents monetary values using:

integer minor units

such as:

  • EUR cents
  • USD cents
  • GBP pence

or another exact decimal representation. For example:

€125.42

may internally be represented as:

12542 cents

Ledger

The ledger is the authoritative financial history.

A possible model is:

type LedgerEntry struct {
    ID            string
    AccountID     string
    Currency      Currency
    Amount        int64
    Direction     LedgerDirection
    ReferenceType string
    ReferenceID   string
    Timestamp     time.Time
}

Possible directions:

  • debit
  • credit

Balances should be consistent with ledger activity.

The implementation must not silently change a balance without recording the corresponding ledger operation.

Double-Entry Principle

For transfers between accounts, financial movement should be represented by matching entries.

Example:

Account A:
-100 EUR

Account B:
+100 EUR

Conceptually:

total debits = total credits

for the same transfer before fees or currency conversion are considered.

Transfer

A transfer moves money between accounts. A possible model is:

type Transfer struct {
    ID              string
    SourceAccountID string
    TargetAccountID string
    Currency        Currency
    Amount          int64
    Status          TransferStatus
}

Possible states:

  • created
  • validated
  • processing
  • completed
  • failed
  • reversed

Transfer Validation

Before processing a transfer, validate:

  • source account exists
  • target account exists
  • accounts are active
  • currency is supported
  • amount > 0
  • sufficient available balance
  • transaction limits
  • fraud rules

Atomic Transfer

A transfer must be atomic. Either:

source debit and destination credit both succeed

or:

neither is committed

The system must never leave:

  • source debited
  • destination not credited

as a successful final state.

Transfer Scenario

Account:

        ACC-100
        EUR Available = 5,000.00

Transfer:

        ACC-100 -> ACC-200
        Amount = 1,250.00 EUR

After successful processing:

        ACC-100: Available = 3,750.00 EUR
        ACC-200: Available += 1,250.00 EUR

The ledger must contain the corresponding entries.

Payment Card

A customer may have payment cards connected to an account. A possible model is:

type Card struct {
    ID          string
    CustomerID  string
    AccountID   string
    LastFour    string
    Status      CardStatus
}

Possible states:

  • active
  • blocked
  • expired
  • cancelled

Do not store or expose unnecessary sensitive card information for the purpose of this exercise.

Merchant

A merchant contains:

  • ID
  • Name
  • Category
  • Settlement Account
  • Status

For example:

type Merchant struct {
    ID                  string
    Name                string
    Category            string
    SettlementAccountID string
    Status              MerchantStatus
}

Payment

A card payment contains:

  • Payment ID
  • Card ID
  • Merchant ID
  • Currency
  • Amount
  • Status
  • Created At

A possible model is:

type Payment struct {
    ID         string
    CardID     string
    MerchantID string
    Currency   Currency
    Amount     int64
    Status     PaymentStatus
    CreatedAt  time.Time
}

Payment Lifecycle

A normal payment lifecycle is:

        created => authorized => captured => settled

Alternative states:

  • declined
  • reversed
  • partially_refunded
  • refunded

Authorization

Authorization validates whether the payment may proceed.

The system checks:

  • card status
  • account status
  • merchant status
  • available balance
  • transaction limits
  • fraud rules

If successful:

payment amount moves from available balance to held balance

Example:

        Available = 1000.00
        Held      = 0.00
        
        Authorize 250.00

becomes:

Available = 750.00
Held      = 250.00

Authorization Hold

A possible model is:

type AuthorizationHold struct {
    ID        string
    PaymentID string
    AccountID string
    Currency  Currency
    Amount    int64
    Status    HoldStatus
    ExpiresAt time.Time
}

Possible states:

  • active
  • captured
  • released
  • expired

Declined Authorization

If authorization fails:

Payment.Status = declined

No funds may be held. The result should report the reason. Examples:

  • insufficient_funds
  • card_blocked
  • account_frozen
  • limit_exceeded
  • fraud_rejected
  • merchant_unavailable

Capture

Capture converts authorized funds into a payment obligation. For the base task:

capture amount <= authorized amount

The corresponding hold is reduced or consumed.

Partial Capture

Support partial capture. Example:

        Authorized = 500.00
        Captured   = 420.00

The unused:

80.00

must be released back to available balance.

Settlement

Settlement transfers captured funds to the merchant side.

A settlement may contain many captured payments. A possible model is:

type Settlement struct {
    ID         string
    MerchantID string
    PaymentIDs []string
    Gross      int64
    Fees       int64
    Net        int64
    Status     SettlementStatus
}

Conceptually:

Net = Gross - Fees

Fees

The platform may charge fees. A possible model is:

type FeeRule struct {
    ID              string
    MerchantCategory string
    FixedFee         int64
    PercentageBps    int64
}

PercentageBps may use basis points. For example:

150 basis points = 1.50%

Fee calculation must use deterministic rounding.

Refund

A settled or captured payment may be refunded according to the implemented policy. A possible model is:

type Refund struct {
    ID        string
    PaymentID string
    Amount    int64
    Status    RefundStatus
}

Possible states:

  • created
  • completed
  • failed

Partial Refund

Support multiple partial refunds. Example:

Original Payment = 500.00

Refund 1 = 100.00
Refund 2 = 150.00

Total refunded:

250.00

Remaining refundable amount:

250.00

The system must reject a later refund that would cause:

total refunds > captured amount

Full Refund

When total completed refunds equal the captured amount:

Payment.Status = refunded

When:

0 < refunded amount < captured amount

use:

partially_refunded

Reversal

A reversal cancels a payment before normal settlement is completed.

For example, an authorization may be reversed. When an active hold is reversed:

  • held amount decreases
  • available amount increases

The same money must not be released twice.

Authorization Expiration

An authorization hold may expire before capture. When expired:

  • hold is released
  • payment cannot be captured using that expired authorization

The corresponding funds return to available balance.

Transaction Limits

Accounts or cards may have limits. Examples:

  • maximum single transaction
  • daily spending limit
  • daily transfer limit
  • monthly spending limit

A possible model is:

type TransactionLimit struct {
    EntityID       string
    SingleLimit    int64
    DailyLimit     int64
    MonthlyLimit   int64
}

Completed and relevant pending operations must be considered consistently when evaluating limits.

Daily Limit Scenario

Card:

Daily Limit = 2,000.00
Already Used = 1,600.00

New payment:

500.00

must be rejected because:

1,600 + 500 > 2,000

Account Freeze

An account may become frozen.

When:

Account.Status = frozen

new outgoing transfers and payments must be rejected.

Incoming credits may remain allowed.

The implementation must document the chosen incoming-credit policy.

Card Blocking

A blocked card must not create new payment authorizations.

Existing completed payments remain part of history. Blocking a card must not delete previous transactions.

Fraud Rule

Create a simplified rule engine. A possible model is:

type FraudRule struct {
    ID        string
    Type      FraudRuleType
    Threshold int64
    Action    FraudAction
}

Possible actions:

  • allow
  • review
  • reject

Example rules:

single payment > 5,000 -> review
single payment > 20,000 -> reject
more than 5 payments in 2 minutes -> review
payment from blocked merchant -> reject

Fraud Decision

A possible result is:

type FraudDecision struct {
    TransactionID string
    Action        FraudAction
    TriggeredRules []string
}

All triggered rules should be reported. The strongest action wins:

reject > review > allow

Manual Review

A transaction marked review should not automatically become completed. It remains pending until approved or rejected by an explicit review operation.

Merchant Settlement Scenario

Merchant:

MERCHANT-10

Captured payments:

Payment 1 = 100.00
Payment 2 = 250.00
Payment 3 = 650.00

Gross:

1,000.00

If total fees are:

25.00

merchant settlement is:

975.00

The ledger must preserve how the final amount was derived.

Currency Conversion

An optional but recommended extension supports transfers between different currencies.

For example:

        Source: EUR
        Destination: USD

The conversion operation should preserve:

  • source amount
  • exchange rate
  • destination amount
  • rate timestamp

Historical transactions must retain the rate actually used.

Changing the current exchange rate must not modify historical transaction results.

Statement

The platform should generate account statements. A statement contains ledger entries for:

  • account
  • currency
  • date range

A possible model is:

type AccountStatement struct {
    AccountID      string
    Currency       Currency
    OpeningBalance int64
    Entries        []LedgerEntry
    ClosingBalance int64
}

The statement should satisfy:

        Opening Balance + Credits - Debits = Closing Balance

Dispute

A customer may dispute a completed payment. A possible model is:

type Dispute struct {
    ID        string
    PaymentID string
    CustomerID string
    Reason    string
    Status    DisputeStatus
}

Possible states:

  • opened
  • under_review
  • accepted
  • rejected
  • closed

A dispute does not automatically mean that the original payment was invalid.

The financial effect of an accepted dispute should be represented explicitly.

Transaction History

The system must preserve transaction history. Examples:

  • transfer created
  • transfer completed
  • authorization approved
  • authorization declined
  • hold created
  • payment captured
  • payment settled
  • refund completed
  • authorization reversed
  • account frozen
  • card blocked
  • dispute opened

History must not disappear when the current state changes.

Idempotency

Idempotency is mandatory for financial commands. Requests should contain:

RequestID

Examples:

  • transfer request
  • payment authorization
  • capture request
  • refund request
  • settlement request

Processing the same RequestID twice must not:

  • debit twice
  • credit twice
  • create two holds
  • capture twice
  • refund twice
  • settle twice

The system should return the previously known result for an already processed request.

Duplicate Payment Scenario

Request:

RequestID = PAY-REQ-100
Amount = 250.00

is received twice. The final account state must reflect:

one authorization

not:

two authorizations

Financial Invariants

The implementation must preserve important invariants. Examples:

  • available balance >= 0
  • held balance >= 0
  • refund total <= captured amount
  • capture amount <= authorized amount
  • released hold cannot be released again
  • completed transfer has matching ledger entries
  • duplicate request does not duplicate financial effect
  • closed account cannot initiate new transactions

These invariants should be tested directly.

Reconciliation

Create a reconciliation operation that compares:

stored account balances

with:

balances derived from ledger entries

A possible result is:

type ReconciliationResult struct {
    AccountID       string
    Currency        Currency
    StoredBalance   int64
    CalculatedBalance int64
    Difference      int64
    Valid           bool
}

A non-zero difference indicates inconsistent financial state.

Reconciliation Scenario

Assume:

Stored Balance = 10,000.00
Ledger-Derived Balance = 9,950.00

The result must report:

Difference = 50.00
Valid = false

The reconciliation operation must not silently modify the balance.

Repair should be a separate explicit operation.

Audit History

Financial operations should generate audit events. A possible model is:

type AuditEvent struct {
    ID         string
    Timestamp  time.Time
    ActorID    string
    EntityType string
    EntityID   string
    Action     string
    Details    string
}

Audit history should record administrative operations such as:

  • account frozen
  • account unfrozen
  • card blocked
  • limit changed
  • manual review approved
  • manual review rejected
  • dispute resolved

Query Operations

The platform should support:

  • get account balance
  • get available balance
  • get held balance
  • get account transactions
  • get payment
  • get transfer
  • get active holds
  • get merchant settlements
  • get refunds for payment
  • get card spending
  • get triggered fraud rules
  • generate account statement
  • get disputes
  • reconcile account

Queries must not modify financial state.

Command Operations

State-changing operations include:

  • create account
  • deposit funds
  • create transfer
  • authorize payment
  • capture payment
  • reverse authorization
  • expire hold
  • settle payments
  • refund payment
  • freeze account
  • unfreeze account
  • block card
  • change transaction limit
  • open dispute
  • resolve dispute
  • approve manual review
  • reject manual review

Validation

The implementation should validate:

  • duplicate IDs
  • unknown customer
  • unknown account
  • unknown card
  • unknown merchant
  • unknown payment
  • unknown transfer
  • unsupported currency
  • amount <= 0
  • insufficient available balance
  • account frozen
  • card blocked
  • merchant disabled
  • transaction limit exceeded
  • invalid payment transition
  • capture exceeds authorization
  • refund exceeds captured amount
  • duplicate RequestID
  • invalid fee
  • invalid ledger relationship
  • invalid hold state

Required Test Scenarios

Create tests for at least:

  • successful deposit
  • successful transfer
  • insufficient-funds transfer
  • atomic transfer behavior
  • matching transfer ledger entries
  • successful payment authorization
  • declined authorization
  • authorization hold
  • partial capture
  • full capture
  • unused hold release
  • authorization reversal
  • authorization expiration
  • successful settlement
  • fee calculation
  • partial refund
  • multiple partial refunds
  • refund limit rejection
  • full refund
  • daily spending limit
  • single transaction limit
  • frozen account
  • blocked card
  • fraud review
  • fraud rejection
  • manual review approval
  • manual review rejection
  • merchant settlement
  • account statement
  • dispute creation
  • duplicate transfer request
  • duplicate authorization request
  • duplicate capture request
  • duplicate refund request
  • ledger reconciliation success
  • ledger reconciliation failure

Modeling Goal

The purpose of this task is to model a financial system where every state change must remain traceable and financially consistent.

A useful conceptual architecture is:

        Customer Service
              |
              +-- Customers
              +-- Accounts
              +-- Cards
              |
              v
        Transaction Service
              |
              +-- Transfers
              +-- Payments
              |
              v
        Authorization Service
              |
              +-- Validation
              +-- Holds
              +-- Limits
              +-- Fraud Rules
              |
              v
        Payment Lifecycle
              |
              +-- Authorization
              +-- Capture
              +-- Reversal
              +-- Refund
              |
              v
        Settlement Service
              |
              +-- Merchant Settlement
              +-- Fees
              |
              v
           Ledger
              |
              +-- Debits
              +-- Credits
              +-- Statements
              +-- Reconciliation
              |
              v
        Risk and Control
              |
              +-- Account Freeze
              +-- Card Block
              +-- Manual Review
              +-- Disputes
              |
              v
        Audit History

The main challenge is maintaining consistency between balances, holds, ledger entries, payments, transfers, settlements, refunds, limits, and transaction history.

A successful implementation should make it impossible for a normal operation to create money, lose money, charge the same request twice, refund more than was paid, or leave financial state without an auditable explanation.

Scalionix Docs

Keyboard Shortcuts

Navigate the documentation without leaving the keyboard.
Navigation
Previous subject
←
Next subject
→
Previous subsection
Alt + ↑
Next subsection
Alt + ↓
Interface
Documentation Home
Ctrl + Enter
Search
Alt + Q
Open shortcuts
?
Close dialog
Esc
Scalionix Docs

Search Documentation