Skip to content

denismwangi/telco-carrier-grade

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mini Telco Wallets - M-Pesa Style Wallet System

A realistic, carrier-grade M-Pesa-inspired wallet system built with Spring Boot, Kafka, and MySQL. This system demonstrates core mobile money functionality including wallet management, money transfers, transaction processing, and notifications.

Features

Wallet Management - Create and manage digital wallets ✅ Money Transfers - Send money between wallets with validation ✅ Async Processing - Transaction processing via Kafka message queues ✅ Double-Spending Prevention - Pessimistic locking and optimistic locking (versioning) ✅ Idempotency - Duplicate transaction prevention using idempotency keys ✅ Retry Support - Built-in Kafka retry mechanisms ✅ SMS Notifications - Simulated async notifications for transaction events ✅ Ledger Accounting - Double-entry bookkeeping for audit trails

Architecture

Client
  ↓
API Gateway (REST Controllers)
  ↓
Transaction Service ──▶ Kafka (txn.created)
                           ↓
                    Ledger Service
                     (Process Transaction)
                           ↓
                    Kafka (txn.completed)
                           ↓
                  Notification Service
                   (Send SMS)

Components

  • Transaction Service: Initiates transfers and publishes to Kafka
  • Ledger Service: Consumes transactions, updates balances with pessimistic locks
  • Notification Service: Sends async SMS notifications
  • Wallet Service: Manages wallet operations

Technology Stack

  • Spring Boot 4.0.1 (Java 17)
  • Apache Kafka - Async messaging
  • MySQL 8.0 - Persistent storage
  • Flyway - Database migrations
  • Redis - Session management
  • Lombok - Boilerplate reduction

Getting Started

Prerequisites

  • Java 17+
  • Maven 3.6+
  • MySQL 8.0+ (running locally)

Infrastructure Setup

You have three options for running the required infrastructure:

Option 1: Docker Desktop (Recommended - Easiest)

  1. Install Docker Desktop for Mac: https://www.docker.com/products/docker-desktop/
  2. Install and start Docker Desktop
  3. Run from project root:
    docker compose up -d

This starts:

  • MySQL on port 3306
  • Kafka on port 9092
  • Zookeeper on port 2181
  • Redis on port 6379

Option 2: Local Installation via Homebrew

Install MySQL:

brew install mysql
brew services start mysql

Install Kafka (includes Zookeeper):

brew install kafka

brew install zookeeper

# Start Zookeeper first
brew services start zookeeper

# Then start Kafka
brew services start kafka

Install Redis (Optional):

brew install redis
brew services start redis

Option 3: MySQL Only (Simplified Mode)

If you only have MySQL installed:

  • The application will run in synchronous mode
  • Kafka auto-configuration is disabled in application.yml
  • Wallet creation and balance queries work
  • Transaction initiation works, but async processing (ledger updates, notifications) won't run
  • Good for development/testing wallet APIs

Note: The database telco_wallets will be created automatically via createDatabaseIfNotExist=true in the JDBC URL.

2. Build the Application

./mvnw clean install

3. Run the Application

./mvnw spring-boot:run

The application will start on http://localhost:8080

API Endpoints

Create Wallet

Endpoint: POST /api/wallets

Request:

{
  "phoneNumber": "254712345678"
}

Validation:

  • Phone number format: ^254[0-9]{9}$ (Kenyan format)
  • Must be unique

Response (201 Created):

{
  "id": 1,
  "phoneNumber": "254712345678",
  "balance": 0.00,
  "currency": "KES",
  "status": "ACTIVE"
}

Example:

curl -X POST http://localhost:8080/api/wallets \
  -H "Content-Type: application/json" \
  -d '{"phoneNumber":"254712345678"}'

Deposit Money

Endpoint: POST /api/wallets/deposit

Request:

{
  "phoneNumber": "254712345678",
  "amount": 5000.00,
  "description": "Initial deposit"
}

Validation:

  • Amount: min 1.0, max 300,000.0
  • Phone number must exist

Response (200 OK):

{
  "id": 1,
  "phoneNumber": "254712345678",
  "balance": 5000.00,
  "currency": "KES",
  "status": "ACTIVE"
}

Example:

curl -X POST http://localhost:8080/api/wallets/deposit \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "254712345678",
    "amount": 5000.00,
    "description": "Initial deposit"
  }'

Key Features:

  • Uses pessimistic locking (FOR UPDATE) to prevent race conditions
  • Thread-safe for concurrent deposits
  • Immediate balance update

Get Wallet Balance

Endpoint: GET /api/wallets/{phoneNumber}

Response (200 OK):

{
  "id": 1,
  "phoneNumber": "254712345678",
  "balance": 5000.00,
  "currency": "KES",
  "status": "ACTIVE"
}

Example:

curl http://localhost:8080/api/wallets/254712345678

Transfer Money

Endpoint: POST /api/transactions/transfer

Request:

{
  "senderPhoneNumber": "254712345678",
  "receiverPhoneNumber": "254787654321",
  "amount": 1000.00,
  "idempotencyKey": "unique-key-123",
  "description": "Payment for services"
}

Validation:

  • Amount: min 1.0, max 300,000.0
  • Both phone numbers must exist
  • Sender must have sufficient balance
  • idempotencyKey is required (prevents duplicate transactions)

Response (201 Created):

{
  "transactionId": "TXN-abc123...",
  "senderPhoneNumber": "254712345678",
  "receiverPhoneNumber": "254787654321",
  "amount": 1000.00,
  "currency": "KES",
  "status": "PENDING",
  "description": "Payment for services",
  "createdAt": "2026-01-16T10:30:00"
}

Example:

curl -X POST http://localhost:8080/api/transactions/transfer \
  -H "Content-Type: application/json" \
  -d '{
    "senderPhoneNumber": "254712345678",
    "receiverPhoneNumber": "254787654321",
    "amount": 1000.00,
    "idempotencyKey": "unique-key-123",
    "description": "Payment for services"
  }'

Note: If Kafka is enabled, the transaction will be processed asynchronously. Otherwise, it remains in PENDING status.


Get Transaction Status

Endpoint: GET /api/transactions/{transactionId}

Response (200 OK):

{
  "transactionId": "TXN-abc123...",
  "senderPhoneNumber": "254712345678",
  "receiverPhoneNumber": "254787654321",
  "amount": 1000.00,
  "currency": "KES",
  "status": "COMPLETED",
  "description": "Payment for services",
  "createdAt": "2026-01-16T10:30:00"
}

Example:

curl http://localhost:8080/api/transactions/TXN-abc123...

Transaction Status Values:

  • PENDING - Transaction created, awaiting processing
  • PROCESSING - Currently being processed by ledger service
  • COMPLETED - Successfully completed
  • FAILED - Processing failed (check failureReason)

Testing the Flow

1. Create Wallets

# Create first wallet
curl -X POST http://localhost:8080/api/wallets \
  -H "Content-Type: application/json" \
  -d '{"phoneNumber":"254712345678"}'

# Create second wallet
curl -X POST http://localhost:8080/api/wallets \
  -H "Content-Type: application/json" \
  -d '{"phoneNumber":"254787654321"}'

2. Deposit Money

# Deposit to first wallet
curl -X POST http://localhost:8080/api/wallets/deposit \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "254712345678",
    "amount": 10000.00,
    "description": "Initial deposit"
  }'

# Deposit to second wallet
curl -X POST http://localhost:8080/api/wallets/deposit \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "254787654321",
    "amount": 5000.00,
    "description": "Initial deposit"
  }'

3. Check Balances

curl http://localhost:8080/api/wallets/254712345678
curl http://localhost:8080/api/wallets/254787654321

Expected: Wallet 1 has KES 10,000, Wallet 2 has KES 5,000

4. Send Money

curl -X POST http://localhost:8080/api/transactions/transfer \
  -H "Content-Type: application/json" \
  -d '{
    "senderPhoneNumber": "254712345678",
    "receiverPhoneNumber": "254787654321",
    "amount": 1000,
    "idempotencyKey": "test-txn-001",
    "description": "Test transfer"
  }'

Response includes transaction ID

3. Verify Transaction Status

curl http://localhost:8080/api/transactions/{transactionId}

Status should be COMPLETED

4. Verify Updated Balances

curl http://localhost:8080/api/wallets/254712345678  # Should show 9,000
curl http://localhost:8080/api/wallets/254787654321  # Should show 6,000

5. Test Idempotency (Duplicate Prevention)

# Send same request again with same idempotency key
curl -X POST http://localhost:8080/api/transactions/transfer \
  -H "Content-Type: application/json" \
  -d '{
    "senderPhoneNumber": "254712345678",
    "receiverPhoneNumber": "254787654321",
    "amount": 1000,
    "idempotencyKey": "test-txn-001",
    "description": "Test transfer"
  }'

Expected: Returns existing transaction, no duplicate charge

6. Test Insufficient Balance

curl -X POST http://localhost:8080/api/transactions/transfer \
  -H "Content-Type: application/json" \
  -d '{
    "senderPhoneNumber": "254787654321",
    "receiverPhoneNumber": "254712345678",
    "amount": 100000,
    "idempotencyKey": "test-txn-002",
    "description": "Large transfer"
  }'

Expected: HTTP 400 - Insufficient balance error

Key Implementation Details

Double-Spending Prevention

Pessimistic Locking: Uses @Lock(LockModeType.PESSIMISTIC_WRITE) to lock wallet rows during transaction processing

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT w FROM Wallet w WHERE w.id = :id")
Optional<Wallet> findByIdForUpdate(@Param("id") Long id);

Optimistic Locking: Uses @Version on Wallet entity for concurrent modification detection

Idempotency

Each transfer requires unique idempotencyKey. Duplicate keys return existing transaction:

if (transactionRepository.existsByIdempotencyKey(request.getIdempotencyKey())) {
    return existingTransaction;
}

Async Processing Flow

  1. Client → Transaction Service creates PENDING transaction
  2. Transaction Service → Kafka publishes txn.created event
  3. Kafka → Ledger Service consumes event
  4. Ledger Service processes with locks, updates balances
  5. Ledger Service → Kafka publishes txn.completed event
  6. Kafka → Notification Service sends SMS notifications

Kafka Topics

  • txn.created - New transactions to process
  • txn.completed - Successfully completed transactions
  • txn.failed - Failed transactions with reasons
  • notification.request - SMS notification requests

Database Schema

  • wallets - User wallet data with balances
  • transactions - Transaction records with status
  • ledger_entries - Double-entry bookkeeping audit trail
  • notifications - SMS notification history

Monitoring

Check logs for transaction flow:

# Application logs
tail -f logs/spring.log

# Kafka topics
docker exec -it telco-kafka kafka-console-consumer \
  --bootstrap-server localhost:9092 \
  --topic txn.created --from-beginning

Production Considerations

This is a realistic implementation demonstrating core concepts. For production:

  • Add authentication/authorization (JWT, OAuth2)
  • Implement rate limiting and throttling
  • Add comprehensive monitoring (Prometheus, Grafana)
  • Implement circuit breakers (Resilience4j)
  • Add distributed tracing (Zipkin, Jaeger)
  • Implement proper secret management
  • Add comprehensive integration tests
  • Configure Kafka for high availability (replication factor > 1)
  • Implement transaction reversal workflows
  • Add KYC validation
  • Implement PIN/password verification
  • Add fraud detection mechanisms

License

MIT License

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages