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
- Clone the repository:
git clone https://github.com/olara-tech/wallet-service-java.git cd wallet-service-java - 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> - Build the project:
./gradlew build - 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"
}
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"
}
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"
}
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"
}
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"
}
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"
}
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"
}
List Payout Accounts
GET /api/v1/wallets/{walletId}/payout-accounts?page=0&size=10
Get Payout Account by ID
GET /api/v1/wallets/{walletId}/payout-accounts/{payoutAccountId}
Delete Payout Account
DELETE /api/v1/wallets/{walletId}/payout-accounts/{payoutAccountId}
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) |
| 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
}
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"
}
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"
}
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"
}
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"
}
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" }
}
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"
}
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"
}
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"
}
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
}
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
}
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"
}
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"
}
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.