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.