A comprehensive Laravel-based microservice for managing a scalable loyalty program system with achievements, badges, transactions, and cashback payments.
This API implements a microservice architecture with event-driven design for a robust loyalty program featuring:
- Event-Driven Architecture: RabbitMQ message queue integration for scalable event processing
- User Management: User registration, authentication, and role-based access control
- Loyalty Points: Earn, redeem, and track loyalty points with configurable rates
- Achievements: Unlockable achievements with flexible criteria and progress tracking
- Badges: Tiered badge system with multiple requirement types
- Transactions: Purchase tracking and processing with external transaction support
- Cashback Payments: Automated cashback processing with multiple payment providers
- Admin Dashboard: Comprehensive admin interface for program management
- Payment Integration: Real payment providers (Paystack, Flutterwave) with robust mock testing
Decision: Implemented as a well-structured service within a larger application rather than a separate microservice.
Rationale:
- Scalability: Service can be easily extracted to a separate service when needed
- Maintainability: Clear separation of concerns with dedicated services and contracts
- Development Speed: Faster development and testing within a single codebase
- Resource Efficiency: Reduced infrastructure complexity for initial implementation
Decision: Implemented RabbitMQ message queue for event processing with fallback to direct events.
Rationale:
- Scalability: Message queues allow horizontal scaling of event processors
- Reliability: Events are persisted and can be retried on failure
- Decoupling: Loose coupling between purchase processing and achievement/badge checking
- Performance: Asynchronous processing prevents blocking on main transaction flow
- Fault Tolerance: Fallback mechanism ensures system continues working if message queue fails
Implementation:
// Purchase events are published to RabbitMQ
$this->messageQueueService->publishPurchaseEvent($user, $transaction);
// Event consumers process achievements and badges asynchronously
$this->achievementService->checkAndUnlockAchievements($user);
$this->badgeService->checkAndUnlockBadges($user);Decision: Implemented a flexible payment provider system with interface-based design.
Rationale:
- Extensibility: Easy to add new payment providers without changing core logic
- Testing: Mock provider enables comprehensive testing without external dependencies
- Reliability: Multiple providers provide redundancy and failover options
- Compliance: Each provider handles their specific compliance requirements
Implementation:
interface PaymentProviderInterface {
public function initializePayment(User $user, float $amount, string $reference): array;
public function verifyPayment(string $reference): array;
public function processCashback(User $user, float $amount): array;
}Decision: Flexible criteria-based system with JSON configuration.
Rationale:
- Flexibility: Support for complex achievement requirements (multiple criteria)
- Maintainability: Easy to add new achievement types without code changes
- Performance: Efficient progress calculation and checking
- User Experience: Real-time progress tracking and notifications
Implementation:
// Achievement criteria examples
'criteria' => ['transaction_count' => 10] // Simple count
'criteria' => ['points_minimum' => 1000] // Points threshold
'criteria' => [ // Multiple criteria
'transaction_count' => 5,
'points_minimum' => 500
]Decision: Normalized database schema with proper relationships and indexing.
Rationale:
- Data Integrity: Foreign key constraints ensure data consistency
- Performance: Proper indexing for frequently queried fields
- Scalability: Efficient queries even with large datasets
- Maintainability: Clear relationships make the system easy to understand
Key Tables:
users- User information and authenticationloyalty_points- User point balances and historytransactions- Purchase transactions and point awardsachievements- Achievement definitions and criteriabadges- Badge definitions and requirementsuser_achievements- User achievement progress and unlocksuser_badges- User badge assignmentscashback_payments- Cashback payment records
Decision: RESTful API with comprehensive resource endpoints and proper HTTP status codes.
Rationale:
- Standards Compliance: Follows REST principles for predictable API behavior
- Documentation: Clear endpoint structure makes API self-documenting
- Client Integration: Easy integration with frontend applications
- Versioning: API versioning support for backward compatibility
Key Endpoints:
GET /api/users/{user}/achievements- User achievement progressGET /api/admin/users/achievements- Admin view of all usersPOST /api/transactions- Process purchase transactionsPOST /api/cashback/process- Process cashback payments
Decision: Comprehensive testing with unit, integration, and end-to-end tests.
Rationale:
- Quality Assurance: Ensures system reliability and prevents regressions
- Documentation: Tests serve as living documentation of system behavior
- Confidence: Safe refactoring and feature additions
- Coverage: High test coverage across all critical paths
Test Types:
- Unit Tests: Individual service and model testing
- Integration Tests: API endpoint and service interaction testing
- Feature Tests: Complete user journey testing
- Mock Testing: Payment provider testing with controlled scenarios
Decision: Comprehensive error handling with structured logging and graceful degradation.
Rationale:
- Reliability: System continues operating even when components fail
- Debugging: Detailed logs help identify and resolve issues
- Monitoring: Structured logs enable effective monitoring and alerting
- User Experience: Graceful error handling prevents system crashes
Implementation:
try {
$this->messageQueueService->publishPurchaseEvent($user, $transaction);
} catch (\Exception $e) {
Log::warning('Failed to publish to message queue, falling back to direct event');
event(new PurchaseProcessed($user, $transaction));
}Decision: Role-based access control with API authentication and input validation.
Rationale:
- Data Protection: Sensitive user data is properly protected
- Access Control: Admin functions are restricted to authorized users
- Input Validation: Prevents malicious input and data corruption
- Audit Trail: All actions are logged for security monitoring
Decision: Database query optimization, caching, and efficient data structures.
Rationale:
- Scalability: System performs well under high load
- User Experience: Fast response times improve user satisfaction
- Resource Efficiency: Optimized queries reduce server load
- Cost Effectiveness: Efficient resource usage reduces infrastructure costs
Optimizations:
- Database indexing on frequently queried fields
- Eager loading of related models to prevent N+1 queries
- Caching of payment provider configurations
- Queue-based processing for heavy operations
- Docker and Docker Compose
- PHP 8.4+ (if running locally)
- Composer (if running locally)
- PostgreSQL (if running locally)
-
Copy the environment file:
cp .env.example .env
-
Configure your environment variables:
# Database Configuration DB_CONNECTION=pgsql DB_HOST=postgres DB_PORT=5432 DB_DATABASE=bumpa_loyalty DB_USERNAME=bumpa_user DB_PASSWORD=your_secure_password # Application Configuration APP_NAME="Bumpa Loyalty API" APP_ENV=local APP_KEY=base64:your_app_key_here APP_DEBUG=true APP_URL=http://laravel.test # API Configuration API_KEY=your_api_key_here # Message Queue Configuration (RabbitMQ) QUEUE_CONNECTION=rabbitmq RABBITMQ_HOST=rabbitmq RABBITMQ_PORT=5672 RABBITMQ_USER=bumpa RABBITMQ_PASSWORD=bumpa123 RABBITMQ_VHOST=/ RABBITMQ_QUEUE=purchase_events RABBITMQ_EXCHANGE=loyalty_events # Payment Provider Configuration PAYMENT_PROVIDER=mock MOCK_PAYMENT_ENABLED=true MOCK_PAYMENT_SHOULD_FAIL=false MOCK_PAYMENT_FAILURE_RATE=0.0
The API can be started independently using Docker Compose:
# Start the API with all dependencies
docker compose --profile api up -d
# This will start:
# - Laravel API (laravel.test:80)
# - PostgreSQL database
# - Redis cache
# - RabbitMQ message queue (localhost:5672, Management UI: localhost:15672)
# - Horizon queue worker
# - Scheduler
# - Mailpit (http://localhost:8025) - Email testing
# - PgAdmin (optional)
# - Meilisearch (optional)
# After startup, run migrations and seed the database
docker compose exec laravel.test php artisan migrate:fresh --seedThis option runs both the Laravel API and Next.js client together. You need both repositories cloned in the correct structure.
First, ensure you have the correct project structure:
bumpa/
βββ api/ # Laravel API (this repository)
β βββ app/
β β βββ Contracts/ # Payment provider interfaces
β β βββ Events/ # Application events
β β βββ Http/ # Controllers, middleware, requests
β β βββ Jobs/ # Queue jobs
β β βββ Listeners/ # Event listeners
β β βββ Models/ # Eloquent models
β β βββ Notifications/ # Email notifications
β β βββ Observers/ # Model observers
β β βββ Policies/ # Authorization policies
β β βββ Providers/ # Service providers
β β βββ Services/ # Business logic services
β β βββ Traits/ # Reusable traits
β βββ bootstrap/ # Application bootstrap
β βββ config/ # Configuration files
β βββ database/
β β βββ factories/ # Model factories
β β βββ migrations/ # Database migrations
β β βββ seeders/ # Database seeders
β βββ docs/ # Documentation
β βββ routes/ # API routes
β βββ tests/ # Test suites
β βββ docker-compose.yml # Main compose file for full stack
β βββ ...
βββ client/ # Next.js Client (separate repository)
βββ app/ # Next.js app router
β βββ auth/ # Authentication pages
β βββ dashboard/ # Dashboard pages
β βββ api/ # API routes
βββ components/ # React components
β βββ admin/ # Admin-specific components
β βββ payment/ # Payment components
β βββ ui/ # Reusable UI components
βββ store/ # Redux store slices
βββ hooks/ # Custom React hooks
βββ lib/ # Utility libraries
βββ types/ # TypeScript type definitions
βββ __tests__/ # Test files
βββ e2e/ # End-to-end tests
βββ package.json
βββ docker-compose.yml
βββ ...
-
Clone the API repository:
git clone https://github.com/bbtests/loyalty-api.git api
-
Clone the Client repository:
# From the bumpa directory git clone https://github.com/bbtests/loyalty-client.git clientNote: The client repository is a Next.js application with TypeScript, featuring:
- Admin dashboard with user management
- Loyalty program interface
- Authentication system with NextAuth.js
- Redux Toolkit Query for state management
- shadcn/ui components with Tailwind CSS
-
Verify the structure:
ls -la # Should show: api/ and client/ directories # Navigate to api directory to run Docker commands cd api ls -la docker-compose.yml # Should show the main docker-compose.yml file
From the api/ directory (where the main docker-compose.yml is located):
# Navigate to the api directory
cd api
# Start both API and Client
docker compose --profile default up -d
# After startup, run migrations and seed the database
docker compose exec laravel.test php artisan migrate:fresh --seedThis will start:
- Laravel API (localhost:80)
- Next.js Client (localhost:3000)
- PostgreSQL database
- Redis cache
- Horizon queue worker
- Scheduler
- Mailpit (http://localhost:8025) - Email testing
Issue: "docker-compose.yml not found"
# Make sure you're in the api/ directory
pwd
# Should show: /path/to/bumpa/api
# Check if docker-compose.yml exists
ls -la docker-compose.ymlIssue: "Client not starting"
# From the api/ directory, verify client directory exists
ls -la ../client/
# Check if client has its own docker-compose.yml
ls -la ../client/docker-compose.ymlIssue: "Port conflicts"
# Check if ports are already in use
lsof -i :3000 # Next.js client
lsof -i :80 # Laravel API
lsof -i :8025 # MailpitIf you prefer to run the API locally without Docker:
composer install# Create database
createdb bumpa_loyalty
# Run migrations
php artisan migrate
# Seed the database
php artisan db:seed# Start Laravel development server
php artisan serve
# Start Horizon (for queue processing)
php artisan horizon
# Start scheduler (in another terminal)
php artisan schedule:workThe API comes with comprehensive seeders that create realistic test data:
# Run all enabled seeders (basic setup)
php artisan db:seed
# This runs:
# - RolePermissionSeeder: Creates roles and permissions
# - SuperAdminSeeder: Creates the super admin user
# - UserSeeder: Creates 15+ test users
# - BadgeSeeder: Creates tiered badges (Bronze, Silver, Gold, Platinum)
# - AchievementSeeder: Creates unlockable achievementsTo get a complete view of the loyalty system with realistic data, uncomment the additional seeders in database/seeders/DatabaseSeeder.php:
// In DatabaseSeeder.php, uncomment these lines:
LoyaltyPointSeeder::class,
UserAchievementSeeder::class,
UserBadgeSeeder::class,Then run the full seeding:
# Run all seeders including the commented ones
php artisan db:seed# Run specific seeders
php artisan db:seed --class=UserSeeder
php artisan db:seed --class=AchievementSeeder
php artisan db:seed --class=BadgeSeeder
php artisan db:seed --class=LoyaltyPointSeeder
php artisan db:seed --class=UserAchievementSeeder
php artisan db:seed --class=UserBadgeSeeder
php artisan db:seed --class=RolePermissionSeeder
php artisan db:seed --class=SuperAdminSeeder- Users: 15+ users with various roles and profiles
- Achievements: 5+ unlockable achievements with criteria
- Badges: 4+ tiered badges (Bronze, Silver, Gold, Platinum)
- Roles & Permissions: Admin, user roles with proper permissions
- Loyalty Points: Realistic point distributions across users
- User Achievements: Random achievement assignments and progress
- User Badges: Badge assignments based on point thresholds
- Complete Loyalty System: Full user progression with points, achievements, and badges
To quickly set up the complete loyalty system for demonstration:
# 1. Uncomment the additional seeders in DatabaseSeeder.php
# 2. Run fresh migration with full seeding
docker compose exec laravel.test php artisan migrate:fresh --seed
# This will give you:
# - Complete user base with realistic loyalty data
# - Users with various achievement progress
# - Users with different badge tiers
# - Realistic point distributions
# - Full loyalty program functionalityThe API uses Laravel Sanctum for authentication:
- Email:
[email protected] - Password:
P@ssword! - Role: Super Admin
When you run the seeders, we also create additional sample users you can use for testing:
- John Smith β Email:
[email protected], Password:password - Sarah Johnson β Email:
[email protected], Password:password - Mike Wilson β Email:
[email protected], Password:password
The seeder also generates 15 random users via factories.
Run just this seeder with:
php artisan db:seed --class=UserSeeder# Login to get access token
curl -X POST "http://laravel.test/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "P@ssword!"
}'
# Use token in subsequent requests
curl -X GET "http://laravel.test/api/v1/users" \
-H "Authorization: Bearer YOUR_TOKEN_HERE"POST /api/v1/auth/login- User loginPOST /api/v1/auth/logout- User logoutGET /api/v1/auth/me- Get current user
GET /api/v1/users- List users (paginated)GET /api/v1/users/{id}- Get user detailsPOST /api/v1/users- Create userPUT /api/v1/users/{id}- Update userDELETE /api/v1/users/{id}- Delete user
GET /api/v1/achievements- List achievementsGET /api/v1/achievements/{id}- Get achievementPOST /api/v1/achievements- Create achievementPUT /api/v1/achievements/{id}- Update achievementDELETE /api/v1/achievements/{id}- Delete achievement
GET /api/v1/badges- List badgesGET /api/v1/badges/{id}- Get badgePOST /api/v1/badges- Create badgePUT /api/v1/badges/{id}- Update badgeDELETE /api/v1/badges/{id}- Delete badge
GET /api/v1/loyalty-points- List loyalty pointsGET /api/v1/loyalty-points/{id}- Get loyalty pointsPOST /api/v1/loyalty-points- Create loyalty pointsPUT /api/v1/loyalty-points/{id}- Update loyalty pointsDELETE /api/v1/loyalty-points/{id}- Delete loyalty points
GET /api/v1/transactions- List transactionsGET /api/v1/transactions/{id}- Get transactionPOST /api/v1/transactions- Create transactionPUT /api/v1/transactions/{id}- Update transactionDELETE /api/v1/transactions/{id}- Delete transaction
GET /api/v1/cashback-payments- List cashback paymentsGET /api/v1/cashback-payments/{id}- Get cashback paymentPOST /api/v1/cashback-payments- Create cashback paymentPUT /api/v1/cashback-payments/{id}- Update cashback paymentDELETE /api/v1/cashback-payments/{id}- Delete cashback payment
The API uses Laravel Horizon for queue management and RabbitMQ for event-driven architecture:
# Start Horizon (if running locally)
php artisan horizon
# Start message queue consumer for purchase events
php artisan loyalty:consume-purchase-events
# Monitor queues
php artisan horizon:statusThe system uses RabbitMQ for event-driven processing:
- Purchase Events: When a transaction is processed, an event is published to RabbitMQ
- Event Consumers: Background workers consume events and process achievements/badges
- Fault Tolerance: If RabbitMQ is unavailable, the system falls back to direct event dispatch
- Scalability: Multiple consumers can process events in parallel
Access the RabbitMQ management interface at http://localhost:15672:
- Username:
bumpa - Password:
bumpa123
Monitor queues, exchanges, and message flow through the web interface.
The API supports multiple payment providers with a flexible architecture:
- Paystack - Nigerian payment gateway
- Flutterwave - Pan-African payment gateway
- Mock Provider - For testing and development
- Payment Initialization: Create payment requests
- Payment Verification: Verify completed payments
- Cashback Processing: Automated cashback transfers
- Error Handling: Comprehensive error scenarios and retry logic
- Mock Testing: Configurable mock responses for testing
The mock provider supports various testing scenarios:
# Configure mock behavior
MOCK_PAYMENT_ENABLED=true
MOCK_PAYMENT_SHOULD_FAIL=false
MOCK_PAYMENT_FAILURE_RATE=0.0
MOCK_PAYMENT_SUCCESS_RATE=0.95
MOCK_PAYMENT_DELAY_MS=300POST /api/v1/payments/initialize- Initialize paymentPOST /api/v1/payments/verify- Verify paymentPOST /api/v1/payments/cashback- Process cashbackGET /api/v1/payments/providers- List available providersGET /api/v1/payments/configuration- Get payment configuration
# Run all tests
php artisan test
# Run specific test suite
php artisan test --testsuite=Feature
php artisan test --testsuite=Unit
# Run specific test files
php artisan test tests/Unit/AchievementServiceTest.php
php artisan test tests/Unit/BadgeServiceTest.php
php artisan test tests/Unit/MessageQueueServiceTest.php
php artisan test tests/Unit/MockPaymentProviderTest.php
php artisan test tests/Feature/LoyaltyProgramIntegrationTest.php
php artisan test tests/Feature/PaymentIntegrationTest.php
# Generate coverage report
php artisan test --coverageThe test suite includes comprehensive coverage:
-
Unit Tests: Individual service and model testing
AchievementServiceTest- Achievement logic and criteria checkingBadgeServiceTest- Badge unlocking and tier progressionMessageQueueServiceTest- RabbitMQ integration and event publishingMockPaymentProviderTest- Payment provider testing with various scenarios
-
Integration Tests: API endpoint and service interaction testing
LoyaltyProgramIntegrationTest- Complete loyalty program flowPaymentIntegrationTest- Payment provider integration and cashback processing
-
Feature Tests: Complete user journey testing
- User achievement and badge progression
- Admin dashboard functionality
- Transaction processing and point awards
- Cashback payment processing
The API uses Mailpit for email testing and development. Mailpit is a lightweight SMTP testing tool that captures all outgoing emails without actually sending them.
When running with Docker, Mailpit is available at:
- Web Interface:
http://localhost:8025 - SMTP Server:
localhost:1025
- Email Capture: All outgoing emails are captured and displayed in the web interface
- Email Preview: View HTML and text versions of emails
- Email Search: Search through captured emails by sender, recipient, or content
- Email Download: Download emails as
.emlfiles for testing - SMTP Testing: Test email sending without external dependencies
Mailpit is automatically configured in the Docker setup. For local development, update your .env file:
# Mail Configuration for Mailpit
MAIL_MAILER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"The API sends various types of notifications:
- Achievement Unlocked: When users unlock new achievements
- Badge Earned: When users earn new badges
- Cashback Processed: When cashback payments are processed
- Welcome Emails: New user registration confirmations
- Password Reset: Password reset links and confirmations
- Access Mailpit: Navigate to
http://localhost:8025 - View Emails: All sent emails appear in the inbox
- Email Details: Click on any email to view:
- Sender and recipient information
- Email subject and content
- HTML and text versions
- Email headers and metadata
- Search & Filter: Use the search bar to find specific emails
- Download: Save emails as
.emlfiles for testing
The API includes customizable email templates for:
- Achievement notifications with badge icons
- Badge earned notifications with tier information
- Cashback payment confirmations with transaction details
- Welcome emails with onboarding information
# 1. Start the API with Mailpit
docker compose --profile api up -d
# 2. Trigger an action that sends an email (e.g., unlock achievement)
curl -X POST "http://laravel.test/api/v1/achievements/unlock" \
-H "Authorization: Bearer YOUR_TOKEN"
# 3. Check Mailpit for the notification
# Visit: http://localhost:8025For production, replace Mailpit with a real SMTP service:
# Production Mail Configuration
MAIL_MAILER=smtp
MAIL_HOST=smtp.your-provider.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=your-email-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="[email protected]"
MAIL_FROM_NAME="${APP_NAME}"Access the Telescope dashboard at http://laravel.test/telescope for:
- Request/Response monitoring
- Database query analysis
- Job queue monitoring
- Exception tracking
Access the Horizon dashboard at http://laravel.test/horizon for:
- Queue monitoring
- Job statistics
- Failed job management
- Queue configuration
# Run PHPStan for static analysis
./vendor/bin/phpstan analyse
# Run Laravel Pint for code formatting
./vendor/bin/pint# Access PgAdmin (if enabled)
# URL: http://localhost:8080
# Email: [email protected]
# Password: secret# Access Mailpit for email testing
# URL: http://localhost:8025
# All outgoing emails are captured here
# No login required - just view captured emails# Set production environment
APP_ENV=production
APP_DEBUG=false
# Generate application key
php artisan key:generate
# Optimize for production
php artisan config:cache
php artisan route:cache
php artisan view:cache# Start production queue workers
php artisan horizonThe API includes comprehensive documentation generated with Scribe:
# Generate API documentation
php artisan scribe:generate
# Access documentation at
# http://laravel.test/docs-
Database Connection Issues
# Check database connection php artisan tinker DB::connection()->getPdo();
-
Permission Issues
# Fix storage permissions chmod -R 775 storage bootstrap/cache -
Queue Not Processing
# Restart Horizon php artisan horizon:terminate php artisan horizon
# View application logs
tail -f storage/logs/laravel.log
# View Horizon logs
tail -f storage/logs/horizon.log- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Submit a pull request
This project is licensed under the MIT License.