A production-grade REST API service designed for financial transaction processing with real-time fraud detection, multi-currency support, and comprehensive spending analytics. Built to handle the core requirements of digital wallet and payment card platforms.
- Overview
- System Architecture
- Key Features
- Technical Stack
- API Documentation
- Business Logic & Rules
- Fraud Detection System
- Database Design
- Getting Started
- Testing Strategy
- Performance Considerations
- Security Considerations
- Trade-offs & Future Improvements
This service provides the core transaction processing capabilities required for fintech applications, specifically designed for digital wallet platforms similar to BigPay, Revolut, or GrabPay. It handles the complete transaction lifecycle from validation through completion, including balance management, fraud detection, and refund processing.
Digital payment platforms need to:
- Process thousands of transactions per second reliably
- Prevent fraud in real-time without blocking legitimate transactions
- Support multiple currencies and conversion
- Provide instant balance updates
- Maintain transaction integrity under concurrent operations
- Offer analytics for business intelligence
This API implements a transaction processing engine with:
- Synchronous processing for immediate transaction validation and fraud scoring
- Balance locking mechanisms to prevent race conditions
- Rule-based fraud detection with configurable thresholds
- Event-driven architecture ready for async processing extensions
- RESTful design following industry best practices
┌─────────────────────────────────────────────────────────────┐
│ Client Applications │
│ (Mobile Apps, Web, Partner APIs) │
└───────────────────────┬─────────────────────────────────────┘
│
│ HTTPS/REST
│
┌───────────────────────▼─────────────────────────────────────┐
│ API Gateway Layer │
│ (FastAPI / Rate Limiting) │
└───────────────────────┬─────────────────────────────────────┘
│
┌───────────────┼───────────────┐
│ │ │
┌───────▼──────┐ ┌─────▼──────┐ ┌─────▼──────────┐
│ Transaction │ │ Fraud │ │ Analytics │
│ Service │ │ Detection │ │ Service │
│ │ │ Engine │ │ │
└───────┬──────┘ └─────┬──────┘ └─────┬──────────┘
│ │ │
└───────────────┼───────────────┘
│
┌───────────────────────▼─────────────────────────────────────┐
│ Data Access Layer │
│ (SQLAlchemy / Repository Pattern) │
└───────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────▼─────────────────────────────────────┐
│ Database Layer │
│ (SQLite for dev / PostgreSQL for prod) │
└─────────────────────────────────────────────────────────────┘
- Repository Pattern: Abstracts data access logic from business logic
- Service Layer Pattern: Encapsulates business rules and transaction logic
- DTO Pattern: Uses Pydantic models for request/response validation
- Factory Pattern: Creates transactions with proper validation
- Strategy Pattern: Pluggable fraud detection rules
- Atomic Operations: ACID-compliant transaction handling
- Idempotency: Duplicate transaction detection within 5-minute windows
- Status Management: Complete transaction lifecycle (pending → completed → failed → reversed)
- Multi-currency Support: Real-time conversion between MYR, SGD, USD
- Transaction Types: Purchase, refund, top-up, withdrawal
- Real-time Balance Calculation: Accurate balance across all transaction types
- Concurrency Control: Prevents race conditions during simultaneous transactions
- Overdraft Prevention: Strict validation against available balance
- Multi-currency Wallets: Per-user currency preference with conversion
- Velocity Checks: Monitors transaction frequency patterns
- Amount Analysis: Flags unusual transaction amounts
- Time-based Rules: Detects off-hours suspicious activity
- Scoring System: 0-100 fraud score with configurable thresholds
- Non-blocking Flags: Flags transactions without automatic rejection
- Spending by Category: Aggregates transactions by merchant category
- Time-series Analysis: Daily, weekly, monthly spending patterns
- User Behavior Metrics: Average transaction size, frequency
- Fraud Statistics: Aggregated fraud scores and flag distribution
- Full/Partial Refunds: Support for complete or partial transaction reversals
- Balance Restoration: Automatic wallet balance adjustment
- Audit Trail: Complete refund history and reasoning
- Double-refund Prevention: Validates refund eligibility
- FastAPI 0.104+: High-performance async web framework
- Automatic OpenAPI documentation
- Native Pydantic integration
- Async/await support for future scalability
- Built-in request validation
- SQLAlchemy 2.0+: ORM with async support
- SQLite: Development database (easily swapped for PostgreSQL)
- Alembic: Database migration management
- Pydantic 2.0+: Request/response validation and serialization
- Type Hints: Full Python typing for IDE support and runtime checks
- pytest: Test framework with fixtures and parametrization
- pytest-asyncio: Async test support
- pytest-cov: Code coverage reporting
- faker: Test data generation
- Black: Code formatting
- isort: Import sorting
- pylint/flake8: Code linting
- mypy: Static type checking
http://localhost:8000/api/v1
Currently using API key authentication (header: X-API-Key). Production ready for OAuth2/JWT integration.
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /transactions |
Create new transaction | Required |
| GET | /transactions/{id} |
Get transaction details | Required |
| GET | /transactions |
List transactions with filters | Required |
| GET | /users/{user_id}/balance |
Get current balance | Required |
| POST | /transactions/{id}/refund |
Process refund | Required |
| GET | /analytics/spending |
Get spending analytics | Required |
| GET | /health |
Health check | None |
Request:
curl -X POST http://localhost:8000/api/v1/transactions \
-H "Content-Type: application/json" \
-H "X-API-Key: your-api-key" \
-d '{
"user_id": "user-001",
"amount": 45.50,
"currency": "MYR",
"merchant_name": "Starbucks Pavilion KL",
"merchant_category": "food_beverage",
"transaction_type": "purchase"
}'Response (201 Created):
{
"transaction_id": "txn-123e4567-e89b-12d3-a456-426614174000",
"user_id": "user-001",
"amount": 45.50,
"currency": "MYR",
"merchant_name": "Starbucks Pavilion KL",
"merchant_category": "food_beverage",
"transaction_type": "purchase",
"status": "completed",
"fraud_score": 15,
"fraud_flags": ["OFF_HOURS"],
"created_at": "2024-12-11T23:30:00Z",
"updated_at": "2024-12-11T23:30:00Z"
}Error Response (400 Bad Request):
{
"error": "INSUFFICIENT_BALANCE",
"message": "User balance (100.00 MYR) is insufficient for transaction (150.00 MYR)",
"details": {
"available_balance": 100.00,
"required_amount": 150.00,
"currency": "MYR"
},
"timestamp": "2024-12-11T23:30:00Z",
"path": "/api/v1/transactions"
}GET /transactions supports:
user_id: Filter by userstatus: Filter by transaction status (pending, completed, failed, reversed)merchant_category: Filter by categorystart_date: ISO 8601 datetimeend_date: ISO 8601 datetimelimit: Results per page (default: 50, max: 100)offset: Pagination offset (default: 0)sort_by: Sort field (created_at, amount)sort_order: asc or desc
Example:
GET /api/v1/transactions?user_id=user-001&status=completed&start_date=2024-12-01T00:00:00Z&limit=20&sort_by=created_at&sort_order=desc-
Amount Validation
- Minimum: 0.01 (any currency)
- Maximum: 50,000 MYR equivalent per transaction
- Must be positive for purchases, negative for refunds
-
Balance Validation
- Purchase amount + existing pending transactions ≤ available balance
- Balance must never go negative
- Atomic balance updates to prevent race conditions
-
Daily Spending Limits
- Maximum 100,000 MYR equivalent per user per day
- Calculated from 00:00:00 to 23:59:59 user's local timezone
- Includes completed and pending transactions
- Excludes refunds and top-ups
-
User Status Validation
- User must have
activestatus - Suspended users cannot create transactions
- Closed accounts are permanently blocked
- User must have
-
Currency Validation
- Supported: MYR, SGD, USD
- Automatic conversion to user's wallet currency
- Exchange rates applied at transaction time
Fixed exchange rates (would be dynamic in production):
EXCHANGE_RATES = {
"MYR": 1.0,
"SGD": 3.50, # 1 SGD = 3.50 MYR
"USD": 4.70 # 1 USD = 4.70 MYR
}Conversion Formula:
amount_in_myr = amount * EXCHANGE_RATES[currency]
Use Cases:
- User has MYR wallet, spends in SGD → Convert SGD to MYR for balance check
- Daily limit comparison → Always convert to MYR equivalent
- Analytics reporting → Can report in user's preferred currency
-
Eligibility
- Only
completedtransactions can be refunded - Cannot refund a transaction that's already been refunded
- Refund amount must not exceed original transaction amount
- Only
-
Processing
- Creates new transaction with
refundtype - Links to original transaction via
original_transaction_id - Restores balance immediately
- Original transaction status remains
completed - Refund transaction gets
completedstatus
- Creates new transaction with
-
Partial Refunds
- Supported via
refund_amountparameter - Multiple partial refunds allowed until full amount refunded
- Tracks total refunded amount per transaction
- Supported via
The fraud detection engine runs synchronously during transaction creation, calculating a fraud score based on multiple rule checks. This is a non-blocking system - high fraud scores flag transactions for review but don't automatically reject them.
High Velocity (20 points)
if transactions_in_last_10_minutes > 5:
flags.append("HIGH_VELOCITY")
fraud_score += 20Suspicious Velocity (20 points)
if transactions_in_last_hour > 10:
flags.append("SUSPICIOUS_VELOCITY")
fraud_score += 20Rationale: Legitimate users rarely make more than 5 transactions in 10 minutes. Credit card testing often shows rapid-fire transactions.
Unusual Amount (20 points)
user_avg_transaction = calculate_user_average(user_id, last_30_days)
if amount > (user_avg_transaction * 3):
flags.append("UNUSUAL_AMOUNT")
fraud_score += 20High Value (20 points)
if amount_in_myr > 10000:
flags.append("HIGH_VALUE")
fraud_score += 20Rationale: Sudden large transactions that deviate from user's spending pattern may indicate account compromise.
Off Hours (20 points)
if 23 <= transaction_hour or transaction_hour < 5:
flags.append("OFF_HOURS")
fraud_score += 20Rationale: Transactions between 11 PM - 5 AM are statistically more likely to be fraudulent in SEA region.
if same_user_and_amount_and_merchant_within_5_minutes:
flags.append("POSSIBLE_DUPLICATE")
# Note: Does not add to fraud score, just flagsRationale: Legitimate duplicate submissions (double-click, network retry) should not penalize the user, but should be flagged for monitoring.
| Score Range | Action | Status |
|---|---|---|
| 0-39 | Auto-approve | completed |
| 40-59 | Auto-approve with monitoring | completed |
| 60-79 | Hold for review | pending |
| 80-100 | High risk, immediate review | pending |
- Machine Learning Models: Replace rule-based with ML models trained on historical data
- Geolocation Checks: Validate transaction location against user's typical locations
- Device Fingerprinting: Track and validate device IDs
- Behavioral Biometrics: Typing patterns, transaction patterns
- External Fraud APIs: Integration with services like Sift, Forter
- Real-time Blocking: Configurable auto-block for scores > 90
-- Users Table
CREATE TABLE users (
user_id VARCHAR(36) PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
full_name VARCHAR(255) NOT NULL,
wallet_balance DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
currency VARCHAR(3) NOT NULL DEFAULT 'MYR',
status VARCHAR(20) NOT NULL DEFAULT 'active',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- Transactions Table
CREATE TABLE transactions (
transaction_id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
amount DECIMAL(15, 2) NOT NULL,
currency VARCHAR(3) NOT NULL,
merchant_name VARCHAR(255) NOT NULL,
merchant_category VARCHAR(50) NOT NULL,
transaction_type VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL,
fraud_score INTEGER NOT NULL DEFAULT 0,
fraud_flags TEXT, -- JSON array stored as text
original_transaction_id VARCHAR(36), -- For refunds
metadata TEXT, -- JSON for additional data
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id),
FOREIGN KEY (original_transaction_id) REFERENCES transactions(transaction_id)
);
-- Indexes for Performance
CREATE INDEX idx_transactions_user_id ON transactions(user_id);
CREATE INDEX idx_transactions_status ON transactions(status);
CREATE INDEX idx_transactions_created_at ON transactions(created_at);
CREATE INDEX idx_transactions_user_created ON transactions(user_id, created_at);
CREATE INDEX idx_transactions_fraud_score ON transactions(fraud_score);- Decimal for Money: Using
DECIMAL(15,2)instead ofFLOATto avoid floating-point precision issues - UUID as String: VARCHAR(36) for compatibility and readability
- JSON in TEXT: Storing fraud_flags and metadata as JSON strings (SQLite limitation, would use JSONB in PostgreSQL)
- Soft Deletes: Status field instead of hard deletes for audit trail
- Timestamps: Both created_at and updated_at for complete audit trail
- User-based queries:
idx_transactions_user_idfor fast user transaction lookups - Status filtering:
idx_transactions_statusfor operational queries - Time-range queries:
idx_transactions_created_atfor analytics - Composite index:
idx_transactions_user_createdfor common query pattern (user + date range) - Fraud analysis:
idx_transactions_fraud_scorefor risk monitoring queries
- Python 3.11 or higher
- pip (Python package manager)
- Virtual environment tool (venv or virtualenv)
- Git
- Clone the repository
git clone https://github.com/yourusername/transaction-processing-api.git
cd transaction-processing-api- Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate- Install dependencies
pip install -r requirements.txt- Set up environment variables
cp .env.example .env
# Edit .env with your configurations- Initialize database
python scripts/init_db.py
python scripts/seed_data.py # Optional: Load sample data- Run the application
uvicorn app.main:app --reload --port 8000- Access API documentation
http://localhost:8000/docs # Swagger UI
http://localhost:8000/redoc # ReDoc
# Application
APP_NAME=Transaction Processing API
APP_VERSION=1.0.0
DEBUG=true
# Database
DATABASE_URL=sqlite:///./transaction_db.sqlite
# For PostgreSQL: postgresql://user:password@localhost:5432/dbname
# Security
SECRET_KEY=your-secret-key-here
API_KEY=your-api-key-here
# Fraud Detection
FRAUD_VELOCITY_THRESHOLD=5
FRAUD_HIGH_VALUE_THRESHOLD=10000
FRAUD_SCORE_REVIEW_THRESHOLD=60
# Currency Exchange (in production, fetch from external API)
EXCHANGE_RATE_SGD_TO_MYR=3.50
EXCHANGE_RATE_USD_TO_MYR=4.70
# Limits
MAX_TRANSACTION_AMOUNT=50000
DAILY_SPENDING_LIMIT=100000
MAX_TRANSACTIONS_PER_REQUEST=100tests/
├── unit/
│ ├── test_transaction_service.py
│ ├── test_fraud_detection.py
│ ├── test_balance_calculation.py
│ └── test_validators.py
├── integration/
│ ├── test_transaction_api.py
│ ├── test_refund_flow.py
│ └── test_analytics_api.py
├── fixtures/
│ ├── database.py
│ └── sample_data.py
└── conftest.py
# Run all tests
pytest
# Run with coverage
pytest --cov=app --cov-report=html
# Run specific test file
pytest tests/unit/test_transaction_service.py
# Run tests matching pattern
pytest -k "test_fraud"
# Run with verbose output
pytest -v
# Run only integration tests
pytest tests/integration/- Unit Tests: 80%+ coverage
- Integration Tests: All critical paths
- Edge Cases: Boundary conditions, error scenarios
-
Transaction Creation
- Valid transaction succeeds
- Invalid amount rejected
- Insufficient balance rejected
- Daily limit exceeded rejected
- Duplicate detection works
-
Fraud Detection
- High velocity triggers flag
- Unusual amount triggers flag
- Off-hours transactions flagged
- Fraud score calculated correctly
- High fraud score sets pending status
-
Balance Management
- Balance decreases on purchase
- Balance increases on refund
- Concurrent transactions handled
- Balance never goes negative
-
Refund Processing
- Full refund succeeds
- Partial refund succeeds
- Cannot refund twice
- Cannot refund pending transaction
- Balance restored correctly
-
Analytics
- Category totals correct
- Date filtering works
- Multiple users separated
- Currency conversion in analytics
- Transaction Creation: ~50-100ms (including fraud checks)
- Balance Calculation: ~10-30ms (optimized with indexed queries)
- Analytics Queries: ~100-500ms (depending on date range)
- Database: SQLite suitable for ~1000 req/min
-
Database Indexing
- Composite indexes on frequently queried columns
- Covers 95% of queries without table scans
-
Eager Loading
- Load related user data with transactions
- Reduces N+1 query problems
-
Caching Strategy (Ready for implementation)
- User balance caching (Redis)
- Exchange rates caching (1-hour TTL)
- Fraud rule caching (config-based)
-
Query Optimization
- Pagination on all list endpoints
- Maximum result limits enforced
- Date range queries indexed
Phase 1: Current (Single Server)
- Handles 1,000-5,000 req/min
- SQLite database
- Synchronous processing
Phase 2: Horizontal Scaling (3-6 months)
- PostgreSQL with read replicas
- Redis for caching and rate limiting
- Load balancer (Nginx/HAProxy)
- Handles 10,000-50,000 req/min
Phase 3: Distributed System (6-12 months)
- Microservices architecture
- Message queue for async processing (RabbitMQ/Kafka)
- Separate fraud detection service
- Analytics data warehouse
- Handles 100,000+ req/min
Current Bottlenecks:
- Fraud detection requires multiple queries per transaction
- Analytics aggregations on large datasets
- Concurrent balance updates
Solutions:
# 1. Batch fraud checks
fraud_data = get_user_transaction_summary(user_id) # Single query
apply_all_fraud_rules(fraud_data) # In-memory processing
# 2. Materialized views for analytics
CREATE MATERIALIZED VIEW daily_spending_summary AS
SELECT user_id, DATE(created_at), SUM(amount) ...
# 3. Optimistic locking for balance updates
UPDATE users
SET wallet_balance = wallet_balance - amount,
version = version + 1
WHERE user_id = ? AND version = ?-
Input Validation
- Pydantic models validate all inputs
- SQL injection prevention via ORM
- XSS prevention in API responses
-
Authentication
- API key authentication (current)
- Rate limiting per API key
- Request signing (planned)
-
Data Protection
- Sensitive data encryption at rest (planned)
- TLS/HTTPS in production
- PII masking in logs
-
Audit Trail
- All transactions logged
- Immutable transaction records
- Timestamp tracking
Immediate (Pre-production)
- Implement JWT authentication
- Add rate limiting per endpoint
- Enable HTTPS/TLS
- Input sanitization hardening
- Secrets management (AWS Secrets Manager/HashiCorp Vault)
Short-term (3 months)
- Two-factor authentication
- OAuth2 integration
- Encryption at rest
- PCI DSS compliance measures
- Security headers (CORS, CSP, HSTS)
Long-term (6 months)
- Anomaly detection for API usage
- DDoS protection
- Penetration testing
- Bug bounty program
- SOC 2 compliance
- Current API Key Auth: Simple but not enterprise-grade. JWT/OAuth2 needed for production.
- No Request Signing: Replay attacks possible. Need HMAC signing.
- In-memory Secrets: Environment variables expose secrets. Need proper secrets management.
- No Rate Limiting: Currently vulnerable to abuse. Redis-based rate limiting planned.
-
Synchronous Processing vs Async
- Current: Synchronous transaction processing
- Trade-off: Simpler code, easier debugging, but lower throughput
- Future: Async processing with message queues for higher throughput
-
SQLite vs PostgreSQL
- Current: SQLite for development simplicity
- Trade-off: Easy setup, but limited concurrency
- Future: PostgreSQL for production with connection pooling
-
Rule-based Fraud Detection vs ML
- Current: Simple rule-based system
- Trade-off: Explainable and maintainable, but less accurate
- Future: ML models trained on historical fraud patterns
-
Monolithic vs Microservices
- Current: Monolithic application
- Trade-off: Faster development, simpler deployment
- Future: Split into microservices (transaction service, fraud service, analytics service)
-
Fixed Exchange Rates vs Real-time
- Current: Fixed rates in config
- Trade-off: Simple and predictable, but not market-accurate
- Future: Integration with forex API (XE.com, Fixer.io)
- Add transaction status webhook notifications
- Implement idempotency keys for POST requests
- Add request/response logging middleware
- Create admin dashboard for fraud review
- Add bulk transaction import endpoint
- Implement async processing with Celery
- Add Redis caching layer
- Migrate to PostgreSQL
- Build ML fraud detection model
- Add WebSocket support for real-time balance updates
- Implement transaction categories auto-classification
- Add merchant verification system
- Multi-region deployment
- Blockchain integration for audit trail
- AI-powered spending insights
- Open Banking API integration
- Cross-border payment support
- Cryptocurrency wallet integration
- GraphQL API alongside REST
-
Concurrent Transactions: Current implementation may have race conditions under very high concurrency. Need distributed locks (Redis/Redlock).
-
Analytics Performance: Aggregation queries slow down with millions of transactions. Need data warehouse (BigQuery, Snowflake).
-
Fraud Detection Accuracy: Rule-based system has high false positive rate. Need ML models with continuous training.
-
No Rollback Mechanism: Failed transactions don't have automated compensation. Need saga pattern implementation.
-
Limited Currency Support: Only MYR, SGD, USD. Need comprehensive forex support for global operations.
Contributions are welcome! Please read CONTRIBUTING.md for details on our code of conduct and the process for submitting pull requests.
- Fork the repository
- Create feature branch (
git checkout -b feature/amazing-feature) - Write tests for new functionality
- Ensure all tests pass (
pytest) - Ensure code coverage >80% (
pytest --cov) - Format code (
black . && isort .) - Commit changes (
git commit -m 'Add amazing feature') - Push to branch (
git push origin feature/amazing-feature) - Open Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Author: Amin Haiqal
Email: aminhaiqal15@gmail.com
LinkedIn: Your LinkedIn
Portfolio: Your Portfolio
- Inspired by real-world fintech platforms (BigPay, Revolut, Wise)
- Built as a technical showcase for backend engineering interviews
- FastAPI framework and excellent documentation
- SQLAlchemy for robust ORM capabilities
Note: This is a demonstration project built for technical interviews. While it implements production-grade patterns and practices, additional hardening would be required for actual production deployment, including comprehensive security audits, load testing, and regulatory compliance verification.