This project implements a loyalty management system featuring achievement tracking, tiered badge rewards, and an automated cashback system.
- PHP 8.2+ & Composer
- Node.js 18+ & npm
- MySQL (or your preferred database)
-
Install Dependencies
cd backend composer install -
Environment Configuration
cp .env.example .env php artisan key:generate
Ensure
DB_CONNECTIONis set up (default issqlite). -
Database Migration & Seeding This project uses migrations to define the schema and seeders to initialize achievements and badges.
php artisan migrate --seed
This will create the tables and seed
Beginner,Intermediate, andAdvancedbadges, along with purchase-based achievements. -
Start the Server
php artisan serve
-
Install Dependencies
cd frontend npm install -
Environment Configuration Create a
.envfile in thefrontendroot:VITE_API_URL=http://127.0.0.1:8000/api
-
Start Development Server
npm run dev
POST /api/register
- Body:
{ "name": "Taiwo Akerele", "email": "taiwo@example.com", "password": "password" } - Response:
201 Createdwith User object and Sanctum token.
POST /api/login
- Body:
{ "email": "taiwo@example.com", "password": "password" } - Response:
200 OKwith User object and Sanctum token.
POST /api/purchase (Requires Authenticated Token)
- Body:
{ "amount": 5000 } - Response:
{ "message": "Purchase successful", "balance": 4900, "cashback": "Congratulations! You unlocked the Beginner badge and earned ₦300 cashback." }
GET /api/users/{user_id}/achievements (Requires Authenticated Token)
- Parameters:
user_id(Integer validation enforced) - Response:
{ "unlocked_achievements": ["First Purchase", "3 Purchases"], "next_available_achievements": ["5 Purchases", "10 Purchases"], "current_badge": "Beginner", "next_badge": "Intermediate", "remaining_to_unlock_next_badge": 1 }
GET /api/transactions?page=1&limit=10 (Requires Authenticated Token)
- Parameters:
page(optional),limit(optional, defaults to 20) - Response:
{ "data": [ { "id": 1, "user_id": 3, "amount": "100.00", "type": "purchase", "created_at": "2026-04-16T19:41:41.000000Z" } ], "total": 1, "page": 1, "limit": 10, "totalPages": 1 }
The system is built on a decoupled, event-driven architecture using Laravel's core features to ensure scalability and separation of concerns.
A user performs an action (e.g., completes a purchase) on the React dashboard. This sends an authenticated POST /api/purchase request to the backend.
The PurchaseController validates the request and records the purchase in the database. Instead of embedding reward logic directly in the controller, it dispatches a UserPurchaseEvent.
- UserPurchaseEvent: Fired immediately after a purchase is recorded. It acts as a signal that the user's purchase state has changed.
- HandleUserPurchase (Listener): Reacts to the purchase event:
- Achievement Logic: It checks if the user's total purchase count meets any
Achievementthresholds (e.g., "1st Purchase"). If met, it attaches the achievement and fires anAchievementUnlockedEvent. - Badge Logic: It then checks if the user's total achievement count qualifies them for a new
Badge(e.g., "Beginner"). If a new badge is unlocked, it attaches it and fires aBadgeUnlockedEvent.
- Achievement Logic: It checks if the user's total purchase count meets any
- HandleBadgeUnlocked (Listener): Listens specifically for when a user earns a new badge.
- Cashback Trigger: Upon a badge being unlocked, the system automatically triggers a ₦300 Cashback payment.
- Transaction Recording: It increments the user's balance and logs a
cashbacktype transaction for auditability and transparency.
| Component | Responsibility |
|---|---|
| UserPurchaseEvent | Dispatched when a new purchase is successfully recorded. |
| AchievementUnlockedEvent | Dispatched when a user reaches a specific achievement threshold. |
| BadgeUnlockedEvent | Dispatched when a user qualifies for a new level/badge. |
| HandleUserPurchase | Evaluates purchase counts and achievement progress to trigger rewards. |
| HandleBadgeUnlocked | Processes the automated ₦300 cashback whenever a badge is unlocked. |
sequenceDiagram
participant Frontend
participant PurchaseController
participant AchievementController
participant UserPurchaseEvent
participant HandleUserPurchase
participant BadgeUnlockedEvent
participant HandleBadgeUnlocked
rect rgb(240, 240, 240)
Note over Frontend, HandleBadgeUnlocked: 1. Purchase & Reward Processing (Event-Driven)
Frontend->>PurchaseController: POST /api/purchase
PurchaseController->>UserPurchaseEvent: Dispatch(User, Purchase)
UserPurchaseEvent->>HandleUserPurchase: handle()
Note over HandleUserPurchase: Evaluate achievement thresholds & badge levels
HandleUserPurchase->>BadgeUnlockedEvent: Dispatch(User, Badge)
BadgeUnlockedEvent->>HandleBadgeUnlocked: handle()
Note over HandleBadgeUnlocked: Credit ₦300 cashback & log transaction
end
rect rgb(230, 240, 255)
Note over Frontend, AchievementController: 2. Progress Retrieval (State Fetching)
Frontend->>AchievementController: GET /api/users/{user}/achievements
AchievementController->>AchievementController: Validate & Fetch user progress
AchievementController-->>Frontend: JSON: {unlocked_achievements, next_badge, current_badge, ...}
end
- Automated Cashback: Logic-driven rewards triggered upon badge acquisition.
- Strict Validations: Integer-based user validation and cross-user data protection.
- Dynamic Dashboard: Real-time balance updates and slide-in notifications for rewards.