Skip to content

Wallet Service

Overview

The Wallet Service is a Java-based microservice designed to manage wallets, transactions, payouts, and virtual accounts. It is built using Spring Boot and follows a modular architecture to ensure scalability and maintainability.


Features

Feature Description
πŸ‘œ Wallet Management Create, update, and retrieve wallet details.
πŸ’³ Transaction Management Handle user transactions with support for pagination and filtering.
πŸ’Έ Payouts Initiate and manage payouts.
🏦 Virtual Accounts Manage virtual accounts for users.
πŸ“– OpenAPI Documentation Integrated API documentation using SpringDoc OpenAPI.
πŸ” Observability Includes logging and tracing with Loki and OpenTelemetry.

Technologies Used

Technology Purpose
β˜• Java 17 Programming language
🌱 Spring Boot 3.2.4 Framework for building microservices
🐘 PostgreSQL Database
πŸ› οΈ Flyway Database migrations
βœ‚οΈ Lombok Reducing boilerplate code
πŸ”„ MapStruct Object mapping
πŸ›‘οΈ Resilience4j Fault tolerance
πŸ“œ OpenAPI API documentation
πŸ“Š Micrometer Observability

Getting Started

Prerequisites

  • β˜• Java 17 or higher
  • 🐘 PostgreSQL database
  • πŸ› οΈ Gradle build tool

Setup Instructions

  1. Clone the repository:
    git clone https://github.com/olara-tech/wallet-service-java.git
    cd wallet-service-java
    
  2. Configure the database in src/main/resources/application.yml:
    spring:
      datasource:
        url: jdbc:postgresql://<DB_HOST>:<DB_PORT>/<DB_NAME>
        username: <DB_USERNAME>
        password: <DB_PASSWORD>
    
  3. Build the project:
    ./gradlew build
    
  4. Run the application:
    ./gradlew bootRun
    

Running Tests

To execute the test suite, run:

./gradlew test


API Endpoints

Wallets

Endpoint Method Description
/api/v1/wallets POST βž• Create a new wallet
/api/v1/wallets/{walletId} GET πŸ” Retrieve wallet details
/api/v1/wallets/{userId}/user GET πŸ” Retrieve wallets for a user
/api/v1/wallets/{walletId}/status PATCH ✏️ Update wallet status
/api/v1/wallets/{walletId}/balance GET πŸ’° Retrieve wallet balance

Payouts

Endpoint Method Description
/api/v1/payouts POST βž• Initiate a payout
/api/v1/payouts/{payoutId} GET πŸ” Retrieve payout details
/api/v1/payouts GET πŸ” Retrieve all payouts
/api/v1/payouts/{payoutId}/status PATCH ✏️ Update payout status

Connected Accounts

Endpoint Method Description
/api/v1/connected-accounts POST βž• Create a connected account
/api/v1/connected-accounts/{accountId} GET πŸ” Retrieve connected account details
/api/v1/connected-accounts GET πŸ” List all connected accounts
/api/v1/connected-accounts/{accountId} DELETE ❌ Delete a connected account

Create Connected Account

POST /api/v1/connected-accounts
{
  "userId": "b1e8c8e2-8e6b-4e9a-9b1e-2c8e2e8e6b4e",
  "accountName": "John Smith",
  "accountNumber": "GB29NWBK60161331926819",
  "bankCode": "NWBKGB2L",
  "bankName": "NatWest Bank",
  "currency": "GBP",
  "type": "IBAN",
  "country": "GB"
}
| Field | Type | Description | |---------------|--------|---------------------------------------------| | userId | UUID | The user's unique identifier | | accountName | String | Name of the account holder | | accountNumber | String | Account number or IBAN/SWIFT | | bankCode | String | Bank code or SWIFT code | | bankName | String | Name of the bank | | currency | String | Currency code (e.g., NGN, USD, EUR) | | type | String | Account type: IBAN, SWIFT, or LOCAL | | country | String | Country code (ISO 3166-1 alpha-2) |

Response

{
  "id": "ca-uuid-1234",
  "userId": "b1e8c8e2-8e6b-4e9a-9b1e-2c8e2e8e6b4e",
  "accountName": "John Smith",
  "accountNumber": "GB29NWBK60161331926819",
  "bankCode": "NWBKGB2L",
  "bankName": "NatWest Bank",
  "currency": "GBP",
  "type": "IBAN",
  "country": "GB",
  "created": "2025-08-26T12:00:00Z"
}
| Field | Type | Description | |---------------|--------|---------------------------------------------| | id | UUID | Connected account unique identifier | | userId | UUID | The user's unique identifier | | accountName | String | Name of the account holder | | accountNumber | String | Account number or IBAN/SWIFT | | bankCode | String | Bank code or SWIFT code | | bankName | String | Name of the bank | | currency | String | Currency code | | type | String | Account type: IBAN, SWIFT, or LOCAL | | country | String | Country code | | created | String | Creation timestamp (ISO 8601) |


Payout Accounts

Endpoint Method Description
/api/v1/payout-accounts POST βž• Create a payout account
/api/v1/wallets/{walletId}/payout-accounts GET πŸ” List payout accounts for wallet
/api/v1/wallets/{walletId}/payout-accounts/{payoutAccountId} GET πŸ” Get payout account by ID
/api/v1/wallets/{walletId}/payout-accounts/{payoutAccountId} DELETE ❌ Delete payout account

Create Payout Account

POST /api/v1/payout-accounts
{
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "accountNumber": "021000021",
  "bankCode": "CITIUS33",
  "bankName": "Citibank NA",
  "bankId": "b1234567-89ab-cdef-0123-456789abcdef",
  "currency": "USD",
  "accountName": "John Smith",
  "description": "Primary US checking account"
}
| Field | Type | Description | |--------------|--------|------------------------------| | walletId | UUID | Wallet to link payout account| | accountNumber| String | Account number | | bankCode | String | Bank code or SWIFT code | | bankName | String | Name of the bank | | bankId | UUID | Bank unique identifier | | currency | String | Currency code | | accountName | String | Account holder name | | description | String | Account description |

Response

{
  "id": "b2a1c3d4-5678-4e9a-9b1e-2c8e2e8e6b4e",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "accountName": "John Smith",
  "accountNumber": "021000021",
  "bankCode": "CITIUS33",
  "bankId": "b1234567-89ab-cdef-0123-456789abcdef",
  "bankName": "Citibank NA",
  "currency": "USD",
  "description": "Primary US checking account",
  "created": "2025-08-26T12:00:00Z"
}
| Field | Type | Description | |--------------|--------|------------------------------| | id | UUID | Payout account unique ID | | walletId | UUID | Wallet to link payout account| | accountName | String | Account holder name | | accountNumber| String | Account number | | bankCode | String | Bank code or SWIFT code | | bankId | UUID | Bank unique identifier | | bankName | String | Name of the bank | | currency | String | Currency code | | description | String | Account description | | created | String | Creation timestamp |

Transactions

Endpoint Method Description
/api/v1/transactions POST βž• Create a transaction (credit or debit)
/api/v1/transactions/{transactionId} GET πŸ” Retrieve transaction details by ID
/api/v1/transactions/{transactionId}/reference GET πŸ” Retrieve transaction details by reference
/api/v1/transactions/{transactionId}/status PATCH ✏️ Update transaction status
/api/v1/transactions GET πŸ” Retrieve paginated transactions (filter)

Create Transaction

Request

POST /api/v1/transactions
{
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "amount": 1000.00,
  "currency": "USD",
  "type": "DEPOSIT", // or "WITHDRAWAL"
  "reference": "OLARA-USD-DEPOSIT-<timestamp>",
  "description": "Deposit to wallet",
  "metadata": { "source": "bank_transfer" }
}

Field Type Description
walletId UUID Wallet to credit or debit
amount Number Transaction amount
currency String Currency code (e.g., USD, NGN)
type String Transaction type: DEPOSIT or WITHDRAWAL
reference String Unique transaction reference
description String Description of the transaction
metadata Map Optional metadata (key-value pairs)

Response

{
  "id": "txn-uuid-1234",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "amount": 1000.00,
  "currency": "USD",
  "type": "DEPOSIT",
  "reference": "OLARA-USD-DEPOSIT-<timestamp>",
  "status": "SUCCESS",
  "description": "Deposit to wallet",
  "metadata": { "source": "bank_transfer" },
  "created": "2025-07-05T12:00:00Z"
}

Update Transaction Status

Request

PATCH /api/v1/transactions/{transactionId}/status
{
  "status": "FAILED"
}

Response

{
  "id": "txn-uuid-1234",
  "status": "FAILED"
}

Get Transactions (Paginated & Filtered)

Request

GET /api/v1/transactions?walletId=<walletId>&status=SUCCESS&type=DEPOSIT&page=0&size=10

Response

{
  "content": [
    {
      "id": "txn-uuid-1234",
      "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
      "amount": 1000.00,
      "currency": "USD",
      "type": "DEPOSIT",
      "reference": "OLARA-USD-DEPOSIT-<timestamp>",
      "status": "SUCCESS",
      "description": "Deposit to wallet",
      "metadata": { "source": "bank_transfer" },
      "created": "2025-07-05T12:00:00Z"
    }
  ],
  "pageable": { "pageNumber": 0, "pageSize": 10 },
  "totalElements": 1,
  "totalPages": 1
}

Virtual Accounts

Endpoint Method Description
/api/v1/virtual-accounts POST βž• Create a virtual account
/api/v1/virtual-accounts/{accountId} GET πŸ” Retrieve virtual account details

Observability

Feature Description
πŸ“œ Logging Configured with Loki for centralized logging.
πŸ“‘ Tracing Integrated with OpenTelemetry for distributed tracing.

πŸš€ Sample Requests & Responses

πŸ‘œ Create Wallet

Request

POST /api/v1/wallets
{
  "fullName": "Jane Doe",
  "currency": "NGN",
  "productType": "REGULAR_EQUITY_TRADING"
}

🏷️ Field πŸ—ƒοΈ Type πŸ“ Description
fullName String The user's full name
currency String Wallet currency (e.g., NGN, USD)
productType String Product type (e.g., REGULAR_EQUITY_TRADING)

Response

{
  "id": "a1234567-89ab-cdef-0123-456789abcdef",
  "userId": "b1e8c8e2-8e6b-4e9a-9b1e-2c8e2e8e6b4e",
  "tenantId": "123e4567-e89b-12d3-a456-426614174000",
  "currency": "NGN",
  "productType": "REGULAR_EQUITY_TRADING",
  "fullName": "Jane Doe",
  "status": "INACTIVE",
  "currentBalance": 0.00,
  "availableToTrade": 0.00,
  "availableToWithdraw": 0.00,
  "unsettledCash": 0.00,
  "reservedCash": 0.00,
  "created": "2025-06-01T12:00:00Z"
}

🏷️ Field πŸ—ƒοΈ Type πŸ“ Description
id UUID Wallet unique identifier
userId UUID The user's unique identifier
tenantId UUID Tenant identifier
currency String Wallet currency
productType String Product type
fullName String The user's full name
status String Wallet status
currentBalance Number Current wallet balance
availableToTrade Number Amount available to trade
availableToWithdraw Number Amount available to withdraw
unsettledCash Number Unsettled cash
reservedCash Number Reserved cash
created String Creation timestamp (ISO 8601)

Error (Duplicate Wallet)

{
  "error": "Wallet already exists for this user, currency, and product type",
  "status": 409
}


πŸ’Έ Initiate Payout

Request

POST /api/v1/payouts
{
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "beneficiaryAccountNumber": "1234567890",
  "beneficiaryBankCode": "044",
  "beneficiaryName": "Jane Doe",
  "amount": 5000.00,
  "currency": "NGN",
  "reference": "PO123456789",
  "narration": "Vendor payment",
  "provider": "FLUTTERWAVE"
}

🏷️ Field πŸ—ƒοΈ Type πŸ“ Description
walletId UUID Wallet from which payout is made
beneficiaryAccountNumber String Beneficiary's bank account number
beneficiaryBankCode String Bank code of the beneficiary
beneficiaryName String Name of the beneficiary
amount Number Amount to payout
currency String Currency code (e.g., NGN)
reference String Unique reference for the payout
narration String Description or reason for the payout
provider String Payment provider (e.g., FLUTTERWAVE)

Response

{
  "id": "payout-uuid-1234",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "amount": 5000.00,
  "currency": "NGN",
  "status": "PROCESSING",
  "providerReference": "FLW-REF-123456",
  "fee": 50.00,
  "created": "2025-06-01T12:10:00Z"
}

🏷️ Field πŸ—ƒοΈ Type πŸ“ Description
id UUID Payout unique identifier
walletId UUID Wallet from which payout was made
amount Number Amount paid out
currency String Currency code
status String Payout status (e.g., PROCESSING, COMPLETED)
providerReference String Reference from the payment provider
fee Number Fee charged for the payout
created String Creation timestamp (ISO 8601)

Error (Duplicate Reference / Idempotency)

{
  "id": "payout-uuid-1234",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "amount": 5000.00,
  "currency": "NGN",
  "status": "PROCESSING",
  "providerReference": "FLW-REF-123456",
  "fee": 50.00,
  "created": "2025-06-01T12:10:00Z"
}

Error (Insufficient Balance)

{
  "error": "Insufficient balance in wallet: 100.00 but requested amount + fee is 5050.00",
  "status": 400
}


🏦 Payout Account Endpoints

Create Payout Account

POST /api/v1/payout-accounts
{
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "accountNumber": "1234567890",
  "bankCode": "044",
  "bankName": "Citibank NA",
  "bankId": "b1234567-89ab-cdef-0123-456789abcdef",
  "currency": "USD",
  "accountName": "Jane Doe",
  "description": "Primary US checking account"
}
| Field | Type | Description | |--------------|--------|------------------------------| | walletId | UUID | Wallet to link payout account| | accountNumber| String | Account number | | bankCode | String | Bank code | | bankName | String | Name of the bank | | bankId | UUID | Bank unique identifier | | currency | String | Currency code | | accountName | String | Account holder name | | description | String | Account description |

Response

{
  "id": "b2a1c3d4-5678-4e9a-9b1e-2c8e2e8e6b4e",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "accountName": "John Smith",
  "accountNumber": "021000021",
  "bankCode": "CITIUS33",
  "bankId": "b1234567-89ab-cdef-0123-456789abcdef",
  "bankName": "Citibank NA",
  "currency": "USD",
  "description": "Primary US checking account",
  "created": "2025-06-01T12:10:00Z"
}
| Field | Type | Description | |--------------|--------|------------------------------| | id | UUID | Payout account unique ID | | walletId | UUID | Wallet to link payout account| | accountName | String | Account holder name | | accountNumber| String | Account number | | bankCode | String | Bank code | | bankId | UUID | Bank unique identifier | | bankName | String | Name of the bank | | currency | String | Currency code | | description | String | Account description | | created | String | Creation timestamp |

Response

{
  "id": "b2a1c3d4-5678-4e9a-9b1e-2c8e2e8e6b4e",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "accountName": "John Smith",
  "accountNumber": "021000021",
  "bankCode": "CITIUS33",
  "bankId": "b1234567-89ab-cdef-0123-456789abcdef",
  "bankName": "Citibank NA",
  "currency": "USD",
  "description": "Primary US checking account",
  "created": "2025-06-01T12:10:00Z"
}
| Field | Type | Description | |--------------|--------|------------------------------| | id | UUID | Payout account unique ID | | walletId | UUID | Wallet to link payout account| | accountName | String | Account holder name | | accountNumber| String | Account number | | bankCode | String | Bank code | | bankId | UUID | Bank unique identifier | | bankName | String | Name of the bank | | currency | String | Currency code | | description | String | Account description | | created | String | Creation timestamp |

List Payout Accounts

GET /api/v1/wallets/{walletId}/payout-accounts?page=0&size=10
Returns paginated list of payout accounts for the wallet.

Get Payout Account by ID

GET /api/v1/wallets/{walletId}/payout-accounts/{payoutAccountId}
Returns details of a specific payout account.

Delete Payout Account

DELETE /api/v1/wallets/{walletId}/payout-accounts/{payoutAccountId}
Deletes the specified payout account.

Error (Wallet Not Found)

{
  "error": "Wallet not found",
  "status": 404
}


🏦 Create Virtual Account

Request

POST /api/v1/virtual-accounts
{
  "userId": "b1e8c8e2-8e6b-4e9a-9b1e-2c8e2e8e6b4e",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "accountName": "Jane Doe",
  "currency": "NGN",
  "provider": "FLUTTERWAVE"
}

🏷️ Field πŸ—ƒοΈ Type πŸ“ Description
userId UUID The user's unique identifier
walletId UUID Wallet to link the virtual account
accountName String Name for the virtual account
currency String Currency code
provider String Payment provider

Response

{
  "id": "va-uuid-1234",
  "accountNumber": "1234567890",
  "routingNumber": "044",
  "bankName": "Kuda Bank",
  "currency": "NGN",
  "status": "active",
  "accountHolderName": "Jane Doe",
  "accountHolderType": "individual",
  "email": "jane@example.com",
  "phone": "+2348012345678"
}

🏷️ Field πŸ—ƒοΈ Type πŸ“ Description
id UUID Virtual account unique identifier
accountNumber String Virtual account number
routingNumber String Bank routing number
bankName String Name of the bank
currency String Currency code
status String Account status (e.g., active)
accountHolderName String Name of the account holder
accountHolderType String Type of account holder (e.g., individual)
email String Email of the account holder
phone String Phone number of the account holder

API Endpoints (Updated July 2025)

Wallet Exchange

Endpoint Method Description
/api/v1/wallet-exchange/initialize POST Initialize a wallet exchange (now returns fxRate and convertedAmount)
/api/v1/wallet-exchange POST Perform a wallet exchange
/api/v1/wallet-exchange/rate GET Get FX rate and converted amount for a currency pair
/api/v1/wallet-exchange GET List wallet exchanges
/api/v1/wallet-exchange/{id} GET Get wallet exchange by ID
/api/v1/wallet-exchange/{id}/status PATCH Update wallet exchange status

Initialize Exchange

Request

POST /api/v1/wallet-exchange/initialize
{
  "sourceWalletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "targetWalletId": "b1234567-89ab-cdef-0123-456789abcdef",
  "amount": 1000.00
}

Response

{
  "sourceWalletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "targetWalletId": "b1234567-89ab-cdef-0123-456789abcdef",
  "sourceWalletName": "USD Wallet",
  "targetWalletName": "NGN Wallet",
  "amount": 1000.00,
  "fee": 10.00,
  "totalAmount": 1010.00,
  "reference": "OLARA-USD-XCH-<timestamp>",
  "fxRate": 1500.00,
  "convertedAmount": 1500000.00
}

Get FX Rate

Request

GET /api/v1/wallet-exchange/rate?from=USD&to=NGN&amount=100

Response

{
  "rate": 1500.00,
  "convertedAmount": 150000.00,
  "from": "USD",
  "to": "NGN",
  "amount": 100
}

Payouts (Fee Calculation and Balance Check)

  • Payouts now require wallet balance to cover both the requested amount and the calculated fee.
  • Fee is calculated using the FeeClient and included in the response.

Request

POST /api/v1/payouts
{
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "beneficiaryAccountNumber": "1234567890",
  "beneficiaryBankCode": "044",
  "beneficiaryName": "Jane Doe",
  "amount": 5000.00,
  "currency": "NGN",
  "reference": "OLARA-USD-PYO-<timestamp>",
  "narration": "Vendor payment",
  "provider": "WISE"
}

Response

{
  "id": "payout-uuid-1234",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "amount": 5000.00,
  "currency": "NGN",
  "status": "PROCESSING",
  "providerReference": "OLARA-USD-PYO-<timestamp>",
  "fee": 50.00,
  "created": "2025-06-01T12:10:00Z"
}

Error (Insufficient Balance)

{
  "error": "Insufficient balance in wallet: 100.00 but requested amount + fee is 5050.00",
  "status": 400
}


Error Handling & Idempotency

All create endpoints use distributed locking and idempotency. Duplicate requests (same key fields) will return the existing resource or a conflict error, not create duplicates. All endpoints return structured error responses, e.g.:

{
  "error": "Wallet already exists for this user, currency, and product type",
  "status": 409
}
Optimistic lock and lock acquisition errors are also handled and surfaced with clear messages and appropriate HTTP status codes.


Swagger/OpenAPI Headers

All endpoints document these headers: - userId (UUID, default: 123e4567-e89b-12d3-a456-426614174000) - corporate-tenantId (UUID, default: 123e4567-e89b-12d3-a456-426614174000)


License

This project is licensed under the πŸ“ Apache 2.0 License. See the LICENSE file for details.


Contributing

Contributions are welcome! Please fork the repository and submit a pull request.


Contact

For inquiries, please contact πŸ“§ support@olara-tech.com.


Admin API Endpoints

These endpoints are for administrative operations and may require elevated permissions.

Admin Wallets

Endpoint Method Description
/api/v1/admin/wallets POST Create a wallet
/api/v1/admin/wallets/{walletId} GET Retrieve wallet details
/api/v1/admin/wallets GET List wallets
/api/v1/admin/wallets/{walletId}/status PUT Update wallet status

Request

POST /api/v1/admin/wallets
{
  "fullName": "Jane Doe",
  "currency": "NGN",
  "productType": "REGULAR_EQUITY_TRADING"
}
| Field | Type | Description | |-----------|--------|------------------------------------| | fullName | String | The user's full name | | currency | String | Wallet currency (e.g., NGN, USD) | | productType| String| Product type |

Response

{
  "id": "a1234567-89ab-cdef-0123-456789abcdef",
  "userId": "b1e8c8e2-8e6b-4e9a-9b1e-2c8e2e8e6b4e",
  "currency": "NGN",
  "productType": "REGULAR_EQUITY_TRADING",
  "fullName": "Jane Doe",
  "status": "ACTIVE",
  "currentBalance": 10000.00,
  "created": "2025-06-01T12:00:00Z"
}
| Field | Type | Description | |--------------|--------|-------------------------------| | id | UUID | Wallet unique identifier | | userId | UUID | The user's unique identifier | | currency | String | Wallet currency | | productType | String | Product type | | fullName | String | The user's full name | | status | String | Wallet status | | currentBalance|Number | Current wallet balance | | created | String | Creation timestamp |

Error (Duplicate Wallet)

{
  "error": "Wallet already exists for this user, currency, and product type",
  "status": 409
}

Admin Payouts

Endpoint Method Description
/api/v1/admin/payouts/initialize POST Initialize payout
/api/v1/admin/payouts POST Create payout
/api/v1/admin/payouts GET List payouts
/api/v1/admin/payouts/{payoutId} GET Retrieve payout details
/api/v1/admin/payouts PUT Update payout

Request

POST /api/v1/admin/payouts
{
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "beneficiaryAccountNumber": "1234567890",
  "beneficiaryBankCode": "044",
  "beneficiaryName": "Jane Doe",
  "amount": 5000.00,
  "currency": "NGN",
  "reference": "ADMIN-PYO-<timestamp>",
  "narration": "Vendor payment",
  "provider": "WISE"
}
| Field | Type | Description | |----------------------|--------|------------------------------------------| | walletId | UUID | Wallet from which payout is made | | beneficiaryAccountNumber|String| Beneficiary's bank account number | | beneficiaryBankCode | String | Bank code of the beneficiary | | beneficiaryName | String | Name of the beneficiary | | amount | Number | Amount to payout | | currency | String | Currency code (e.g., NGN) | | reference | String | Unique reference for the payout | | narration | String | Description or reason for the payout | | provider | String | Payment provider |

Response

{
  "id": "admin-payout-uuid-1234",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "amount": 5000.00,
  "currency": "NGN",
  "status": "PROCESSING",
  "providerReference": "ADMIN-PYO-<timestamp>",
  "fee": 50.00,
  "created": "2025-06-01T12:10:00Z"
}
| Field | Type | Description | |--------------|--------|----------------------------------------------| | id | UUID | Payout unique identifier | | walletId | UUID | Wallet from which payout was made | | amount | Number | Amount paid out | | currency | String | Currency code | | status | String | Payout status | | providerReference|String| Reference from the payment provider | | fee | Number | Fee charged for the payout | | created | String | Creation timestamp |

Error (Insufficient Balance)

{
  "error": "Insufficient balance in wallet: 100.00 but requested amount + fee is 5050.00",
  "status": 400
}

Admin Transactions

Endpoint Method Description
/api/v1/admin/transactions POST Create transaction
/api/v1/admin/transactions/{transactionId} GET Get transaction by ID
/api/v1/admin/transactions GET List transactions
/api/v1/admin/transactions/{transactionId}/status PUT Update transaction status

Request

POST /api/v1/admin/transactions
{
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "amount": 1000.00,
  "currency": "USD",
  "type": "DEPOSIT",
  "reference": "ADMIN-USD-DEPOSIT-<timestamp>",
  "description": "Deposit to wallet",
  "metadata": { "source": "admin_panel" }
}
| Field | Type | Description | |--------------|--------|---------------------------------------------| | walletId | UUID | Wallet to credit or debit | | amount | Number | Transaction amount | | currency | String | Currency code (e.g., USD, NGN) | | type | String | Transaction type: DEPOSIT or WITHDRAWAL | | reference | String | Unique transaction reference | | description | String | Description of the transaction | | metadata | Map | Optional metadata (key-value pairs) |

Response

{
  "id": "admin-txn-uuid-1234",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "amount": 1000.00,
  "currency": "USD",
  "type": "DEPOSIT",
  "reference": "ADMIN-USD-DEPOSIT-<timestamp>",
  "status": "SUCCESS",
  "description": "Deposit to wallet",
  "metadata": { "source": "admin_panel" },
  "created": "2025-07-05T12:00:00Z"
}
| Field | Type | Description | |--------------|--------|---------------------------------------------| | id | UUID | Transaction unique identifier | | walletId | UUID | Wallet to credit or debit | | amount | Number | Transaction amount | | currency | String | Currency code | | type | String | Transaction type | | reference | String | Transaction reference | | status | String | Transaction status | | description | String | Description of the transaction | | metadata | Map | Metadata | | created | String | Creation timestamp |

Error (Duplicate Reference / Idempotency)

{
  "error": "Transaction already exists for this reference",
  "status": 409
}

Admin Virtual Accounts

Endpoint Method Description
/api/v1/admin/virtual-accounts POST Create virtual account
/api/v1/admin/virtual-accounts/{virtualAccountId} GET Get virtual account by ID
/api/v1/admin/virtual-accounts/wallet/{walletId} GET Get virtual accounts by wallet
/api/v1/admin/virtual-accounts/accountNumber/{accountNumber} GET Get by account number
/api/v1/admin/virtual-accounts/accountReference/{accountReference} GET Get by account reference
/api/v1/admin/virtual-accounts/{virtualAccountId} DELETE Delete virtual account

Request

POST /api/v1/admin/virtual-accounts
{
  "userId": "b1e8c8e2-8e6b-4e9a-9b1e-2c8e2e8e6b4e",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "accountName": "Jane Doe",
  "currency": "NGN",
  "provider": "FLUTTERWAVE"
}
| Field | Type | Description | |------------|--------|------------------------------------| | userId | UUID | The user's unique identifier | | walletId | UUID | Wallet to link the virtual account | | accountName| String | Name for the virtual account | | currency | String | Currency code | | provider | String | Payment provider |

Response

{
  "id": "admin-va-uuid-1234",
  "accountNumber": "1234567890",
  "routingNumber": "044",
  "bankName": "Kuda Bank",
  "currency": "NGN",
  "status": "active",
  "accountHolderName": "Jane Doe",
  "accountHolderType": "individual",
  "email": "jane@example.com",
  "phone": "+2348012345678"
}
| Field | Type | Description | |------------------|--------|---------------------------------------------| | id | UUID | Virtual account unique identifier | | accountNumber | String | Virtual account number | | routingNumber | String | Bank routing number | | bankName | String | Name of the bank | | currency | String | Currency code | | status | String | Account status | | accountHolderName| String | Name of the account holder | | accountHolderType| String | Type of account holder | | email | String | Email of the account holder | | phone | String | Phone number of the account holder |

Error (Wallet Not Found)

{
  "error": "Wallet not found",
  "status": 404
}

Admin Wallet Exchange

Endpoint Method Description
/api/v1/admin/wallet-exchange/initialize POST Initialize wallet exchange
/api/v1/admin/wallet-exchange POST Create wallet exchange
/api/v1/admin/wallet-exchange GET List wallet exchanges
/api/v1/admin/wallet-exchange/{exchangeId}/status PUT Update exchange status

Request

POST /api/v1/admin/wallet-exchange/initialize
{
  "sourceWalletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "targetWalletId": "b1234567-89ab-cdef-0123-456789abcdef",
  "amount": 1000.00
}
| Field | Type | Description | |-----------------|--------|---------------------------------------------| | sourceWalletId | UUID | Wallet to debit | | targetWalletId | UUID | Wallet to credit | | amount | Number | Amount to exchange |

Response

{
  "sourceWalletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "targetWalletId": "b1234567-89ab-cdef-0123-456789abcdef",
  "sourceWalletName": "USD Wallet",
  "targetWalletName": "NGN Wallet",
  "amount": 1000.00,
  "fee": 10.00,
  "totalAmount": 1010.00,
  "reference": "ADMIN-USD-XCH-<timestamp>",
  "fxRate": 1500.00,
  "convertedAmount": 1500000.00
}
| Field | Type | Description | |-----------------|--------|---------------------------------------------| | sourceWalletId | UUID | Wallet to debit | | targetWalletId | UUID | Wallet to credit | | sourceWalletName| String | Name of source wallet | | targetWalletName| String | Name of target wallet | | amount | Number | Amount to exchange | | fee | Number | Fee charged | | totalAmount | Number | Total amount debited | | reference | String | Exchange reference | | fxRate | Number | FX rate | | convertedAmount | Number | Converted amount |

Error (Insufficient Balance)

{
  "error": "Insufficient balance in wallet: 100.00 but requested amount + fee is 5050.00",
  "status": 400
}

Admin Payout Accounts

Endpoint Method Description
/api/v1/admin/payout-accounts POST Create payout account
/api/v1/admin/payout-accounts/{payoutAccountId} DELETE Delete payout account
/api/v1/admin/payout-accounts GET List payout accounts
/api/v1/admin/payout-accounts/{payoutAccountId} GET Get payout account by ID

Request

POST /api/v1/admin/payout-accounts?userId=a1234567-89ab-cdef-0123-456789abcdef
{
"walletId": "b1234567-89ab-cdef-0123-456789abcdef",
"accountNumber": "021000021",
"bankCode": "CITIUS33",
"bankName": "Citibank NA",
"bankId": "citi-ny-001",
"currency": "USD",
"accountName": "John Smith",
"description": "Primary US checking account"
}
| Field | Type | Description | |--------------|--------|------------------------------| | userId | UUID | User performing the operation| | walletId | UUID | Wallet to link payout account| | accountNumber| String | Account number | | bankCode | String | Bank code | | bankName | String | Name of the bank | | bankId | UUID | Bank unique identifier | | currency | String | Currency code | | accountName | String | Account holder name | | description | String | Account description |

Response

{
  "id": "admin-payout-account-uuid-1234",
  "walletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "accountNumber": "1234567890",
  "bankCode": "044",
  "accountName": "Jane Doe",
  "created": "2025-06-01T12:10:00Z"
}
| Field | Type | Description | |--------------|--------|------------------------------| | id | UUID | Payout account unique ID | | walletId | UUID | Wallet to link payout account| | accountNumber| String | Account number | | bankCode | String | Bank code | | accountName | String | Account holder name | | created | String | Creation timestamp |

Error (Wallet Not Found)

{
  "error": "Wallet not found",
  "status": 404
}

Wallet-to-Wallet Transfer

Endpoint Method Description
/api/v1/transactions/transfer POST Transfer funds between two wallets (same currency, with fee and distributed locking)

Transfer Funds

Request

POST /api/v1/transactions/transfer
{
  "sourceWalletId": "a1234567-89ab-cdef-0123-456789abcdef",
  "targetWalletId": "b1234567-89ab-cdef-0123-456789abcdef",
  "amount": 1000.00,
  "metadata": { "purpose": "peer_payment" }
}

Field Type Description
sourceWalletId UUID Wallet to debit
targetWalletId UUID Wallet to credit
amount Number Amount to transfer (must be positive)
metadata Map Optional metadata (key-value pairs)

Response

{
  "debitTransactionId": "txn-uuid-1",
  "creditTransactionId": "txn-uuid-2",
  "amount": 1000.00,
  "fee": 10.00,
  "reference": "OLARA-USD-TRF-<timestamp>"
}

Field Type Description
debitTransactionId UUID Transaction ID for the debit
creditTransactionId UUID Transaction ID for the credit
amount Number Amount transferred
fee Number Fee charged for the transfer
reference String Transfer reference

Error Cases - Source or target wallet not found (404) - Wallets have different currencies (400) - Insufficient balance in source wallet (400) - Transfer amount not positive (400) - Lock acquisition or concurrency error (500)

Note: This endpoint uses distributed locking on both wallets (by wallet ID order) to ensure concurrency safety and prevent race conditions. Transfers are atomic and idempotent.


Valid IBAN Test Accounts

Below are sample valid IBANs for various countries (for testing and demo purposes):

Europe

Country IBAN Example
Albania AL47 2121 1009 0000 0002 3569 8741
Andorra AD12 0001 2030 2003 5910 0100
Austria AT61 1904 3002 3457 3201
Belgium BE68 5390 0754 7034
Bosnia BA39 1290 0794 0102 8494
Bulgaria BG80 BNBG 9661 1020 3456 78
Croatia HR12 1001 0051 8630 0016 0
Cyprus CY17 0020 0128 0000 0012 0052 7600
Czech Republic CZ65 0800 0000 1920 0014 5399
Denmark DK50 0040 0440 1162 43
Estonia EE38 2200 2210 2014 5685
Finland FI21 1234 5600 0007 85
France FR14 2004 1010 0505 0001 3M02 606
Germany DE89 3704 0044 0532 0130 00
Greece GR16 0110 1250 0000 0001 2300 695
Hungary HU42 1177 3016 1111 1018 0000 0000
Iceland IS14 0159 2600 7654 5510 7303 39
Ireland IE29 AIBK 9311 5212 3456 78
Italy IT60 X054 2811 1010 0000 0123 456
Latvia LV80 BANK 0000 4351 9500 1
Liechtenstein LI21 0881 0000 2324 013A A
Lithuania LT12 1000 0111 0100 1000
Luxembourg LU28 0019 4006 4475 0000
Malta MT84 MALT 0110 0001 2345 MTLC AST0 01S
Netherlands NL91 ABNA 0417 1643 00
Norway NO93 8601 1117 947
Poland PL61 1090 1014 0000 0712 1981 2874
Portugal PT50 0002 0123 1234 5678 9015 4
Romania RO49 AAAA 1B31 0075 9384 0000
Serbia RS35 2600 0560 1001 6113 79
Slovakia SK31 1200 0000 1987 4263 7541
Slovenia SI56 2633 0001 2039 086
Spain ES91 2100 0418 4502 0005 1332
Sweden SE45 5000 0000 0583 9825 7466
Switzerland CH93 0076 2011 6238 5295 7
United Kingdom GB29 NWBK 6016 1331 9268 19

Middle East

Country IBAN Example
Israel IL62 0108 0000 0009 9999 999
Jordan JO94 CBJO 0010 0000 0000 0131 0003 02
Kuwait KW81 CBKU 0000 0000 0000 1234 5601 01
Lebanon LB62 0999 0000 0001 0019 0122 9114
Qatar QA58 DOHB 0000 1234 5678 90AB CDEF G
Saudi Arabia SA03 8000 0000 6080 1016 7519
United Arab Emirates AE07 0331 2345 6789 0123 456
#
## Account Validation Endpoints
Endpoint Method Description
/api/v1/account-validation/iban/{iban} GET Validate IBAN using regex and openiban.com
/api/v1/account-validation/swift/{swift} GET Validate SWIFT/BIC using regex (format only)

IBAN Validation

Request

GET /api/v1/account-validation/iban/GB82WEST12345698765432

Response (Valid IBAN)

{
  "valid": true,
  "message": "Valid IBAN",
  "details": {
    "valid": true,
    "iban": "GB82WEST12345698765432",
    "bankData": { /* ...bank info... */ },
    "messages": ["Valid IBAN"]
  }
}

Response (Invalid IBAN)

{
  "valid": false,
  "message": "Invalid IBAN format",
  "details": null
}

SWIFT/BIC Validation

Request

GET /api/v1/account-validation/swift/DEUTDEFF

Response (Valid SWIFT/BIC)

{
  "valid": true,
  "message": "Valid SWIFT/BIC format",
  "details": null
}

Response (Invalid SWIFT/BIC)

{
  "valid": false,
  "message": "Invalid SWIFT/BIC format",
  "details": null
}

Validation Logic

  • IBAN: Initial regex validation, then remote validation. All official IBAN countries are supported.
  • SWIFT/BIC: Regex format validation only. No live directory check (no reliable free API exists). Optionally, a static list can be integrated for further validation.