Banking API

 This project is a clean, modular Banking API built with FastEndpoints, following the REPR pattern (Request, Endpoint, Response) — a modern, streamlined alternative to traditional MVC architecture. The API emphasizes clear separation of concerns and lightweight endpoint definition. It features role-based access control with three roles:

  • Customer – for account holders
  • Teller – for handling transactions
  • Admin – for managing accounts and system-level operations

 The backend is powered by Event Sourcing, implemented with MartenDb, providing full traceability and consistency through immutable event logs.

UML use case diagram

UML use case diagram

Events and Projection

BankAccount (Read model)

Failed to fetch :(

GistUrl : https://gist.githubusercontent.com/basdidon/69f8c2f00ff92a9a19f2892941db6174/raw/5a82b8b2e360fa76c08534317f25ab1a2069c8fb/BankAccount.cs

Open

BankAccount.cs

 This file defines the read model for a bank account. The BankAccount class represents the current state of a user's bank account. Key properties include:

  • Id : Unique identifier for the account (likely assigned by the event stream).
  • OwnerId : The user who owns the account.
  • AccountNumber : Human-readable identifier for the account (e.g., "123-456789-0").
  • Balance : Current balance of the account.
  • IsFrozen : Boolean flag indicating whether the account is frozen.

Events

These immutable event records represent actions that occur over time. They include:

  • AccountCreated: Fired when an account is opened.
  • MoneyDeposit,MoneyWithdraw: For deposit and withdrawal operations.
  • MoneySent,MoneyRecieved: For transferring money between accounts.
  • AccountFrozen,AccountUnfrozen: Change the account's status.
  • AccountClosed: Indicates that the account has been terminated.

Failed to fetch :(

GistUrl : https://gist.githubusercontent.com/basdidon/69f8c2f00ff92a9a19f2892941db6174/raw/5a82b8b2e360fa76c08534317f25ab1a2069c8fb/events.cs

Open

BankAccountProjection

Failed to fetch :(

GistUrl : https://gist.githubusercontent.com/basdidon/69f8c2f00ff92a9a19f2892941db6174/raw/e63f8ba016410fc9adcb69a7e28aef216a346a11/BankAccountProjection.cs

Open

This class maps events to changes in the BankAccount state using Marten's SingleStreamProjection<T>

Withdrawal

  To prevent a Teller or Admin from arbitrarily withdrawing funds from an account owned by a regular user, additional logic is required. When a user intends to make a withdrawal, they must inform the Teller, who then sends a withdrawal request to the server. The server generates and sends a One-Time Password (OTP) to the user. The user provides the OTP to the Teller, who then submits a withdrawal confirmation request to complete the process.

Endpoints

MethodEndpointDescription
GET/accountsList accounts
POST/accountsCreate a new account
GET/accounts/{accountId}Get account by ID
GET/accounts/{accountId}/transactionsQuery all transactions by specific account
POST/accounts/{accountId}/depositDeposit funds
POST/accounts/{accountId}/withdrawInitiate withdrawal (pending state)
POST/withdrawal/{requestId}/confirmConfirm withdrawal
POST/accounts/{sourceAccountId}/transferTransfer funds
POST/accounts/{accountId}/freezeFreeze the account
POST/accounts/{accountId}/unfreezeUnfreeze the account

GET/accounts

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
userIdClaimsGuidYes-Represent user who send this request
ownerIdQueryParameterGuidNo-For Teller and Admin to List accounts that owned by specific user
pageQueryParameterintNo1Page number
pageSizeQueryParameterintNo20Number of items per page

API Behaviors

RoleConditionResponseNote
CustomerProvided ownerId403Forbiddencustomer cannot access other's accounts
Not provided ownerId200SuccessReturn items owned by the current user
Teller or AdminProvided ownerId200SuccessReturn accounts owned by that user
Not provided ownerId200SuccessReturn all accounts

POST/accounts

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
userIdClaimsGuidYes-Represent user who send this request
CustomerIdJsonBodyGuidYes-OwnerId of this account
initialBalanceJsonBodydecimalNo0initial balance of this account

API Behaviors

RoleConditionResponseNote
CustomerDefault403Forbidden
Teller or AdminUser is not exists404NotFound
CustomerId is not in customer role400BadRequestCannot create account for non customer role
InitialBalance less than 0400BadRequestInitialBalance must greater than or equal to 0
Default201Created

GET/accounts/{accountId}

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
userIdClaimsGuidYes-user who send this request
accountIdRouteParameterGuidYes-

API Behaviors

RoleConditionResponse
CustomerAccount not exists404NotFound
Account not owned by current user403Forbidden
Default200Success
Teller or AdminAccount not exists404NotFound
Default200Success

GET/accounts/{accountId}/transactions

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
userIdClaimsGuidYes-user who send this request
accountIdRouteParameterGuidYes-
pageQueryParameterintNo1Page number
pageSizeQueryParameterintNo20Number of items per page

API Behaviors

RoleConditionResponse
CustomerAccount not exists404NotFound
Account not owned by current user403Forbidden
Default200Success
Teller or AdminAccount not exists404NotFound
Default200Success

POST/accounts/{accountId}/deposit

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
accountIdRouteParameterGuidYes-process account
userIdClaimsGuidYes-user who send this request
amountJsonBodydecimalYes-deposit amount

API Behaviors

RoleConditionResponse
CustomerDefault403Forbidden
Teller or AdminAccount not exists404NotFound
Amount is less than or equal 0400BadRequest
Account is frozen403Forbidden
Default200Success

POST/accounts/{accountId}/withdraw

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
accountIdRouteParameterGuidYes-process account
userIdClaimsGuidYes-user who send this request
amountJsonBodydecimalYes-withdraw amount

API Behaviors

RoleConditionResponse
CustomerDefault403Forbidden
Teller or AdminAccount not exists404NotFound
Amount is less than or equal 0400BadRequest
Account is frozen403Forbidden
Default200Success

POST/withdrawal/{requestId}/confirm

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
requestIdRouteParameterGuidYes-withdraw request id
userIdClaimsGuidYes-user who send this request
otpJsonBodystringYes-one time password that sent to customer

API Behaviors

RoleConditionResponse
CustomerDefault403Forbidden
Teller or AdminWithdraw request not exists404NotFound
Withdraw request is revocked410Gone
Withdraw request is already process409Conflict
Withdraw request has expired410Gone
Retry attempts exceeded403Forbidden
Provided mismatch OTP400BadRequest
Account was not found404NotFound
Account is frozen403Forbidden
Insufficient fund403Forbidden
Default200Success

POST/accounts/{sourceAccountId}/transfer

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
sourceAccountIdRouteParameterGuidYes-The account from which funds will be transferred
userIdClaimsGuidYes-The user initiating the transfer
destinationAccountIdJsonBodyGuidYes-The account receiving the funds
amountJsonBodydecimalYes-The amount of money to transfer

API Behaviors

RoleConditionResponse
Customersource account or destination account is notfound404NotFound
source account not belong to current user403Forbidden
Insufficient fund400BadRequest
source account or destination account is frozen403Forbidden
Default200Success
Teller or AdminDefault403Forbidden

POST/accounts/{accountId}/freeze

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
accountIdRouteParameterGuidYes-The account will be freeze
userIdClaimsGuidYes-The user who send this request

API Behaviors

RoleConditionResponse
Customer or TellerDefault403Forbidden
AdminAccount was not found404NotFound
The account has already been frozen409Conflict
Default204NoContent

POST/accounts/{accountId}/unfreeze

Request Parameters

ParameterSourceTypeRequiredDefaultDesciption
accountIdRouteParameterGuidYes-The account will be unfreeze
userIdClaimsGuidYes-The user who send this request

API Behaviors

RoleConditionResponse
Customer or TellerDefault403Forbidden
AdminAccount was not found404NotFound
The account hasn't been frozen yet409Conflict
Default204NoContent