12 KiB
12 KiB
🎟️ Complete Coupon System Guide
📦 What's Included
Backend
- Admin Coupon Controller - Full CRUD + Statistics
- User Coupon Controller - Validation & Application
- Coupon Routes - Both admin and user endpoints
Frontend
- CouponApply Component - For checkout page
- Available Coupons Modal - Browse & select coupons
🚀 Setup Instructions
Step 1: Backend Setup
1.1 Add Controllers
Create two controller files:
File 1: src/controllers/admin/couponController.js
(Use the couponController_Admin.js file)
File 2: src/controllers/couponController.js
(Use the couponController_User.js file)
1.2 Add Routes
Create: src/routes/couponRoutes.js
(Use the couponRoutes.js file)
1.3 Register Routes in App
In your server.js or app.js:
const couponRoutes = require('./routes/couponRoutes');
app.use('/api/coupons', couponRoutes);
Step 2: Frontend Setup
2.1 Create Component
Create: src/components/CouponApply.jsx
(Use the CouponApply.jsx file)
2.2 Use in Checkout Page
// pages/Checkout.jsx
import CouponApply from '../components/CouponApply';
import { useState } from 'react';
const Checkout = () => {
const [appliedCoupon, setAppliedCoupon] = useState(null);
const [orderTotal, setOrderTotal] = useState(2599);
const handleCouponApplied = (couponData) => {
setAppliedCoupon(couponData);
setOrderTotal(couponData.finalAmount);
};
const handleCouponRemoved = () => {
setAppliedCoupon(null);
setOrderTotal(2599); // Reset to original amount
};
return (
<div className="max-w-4xl mx-auto p-6">
{/* Cart Items */}
{/* Coupon Section */}
<CouponApply
orderAmount={orderTotal}
onCouponApplied={handleCouponApplied}
onCouponRemoved={handleCouponRemoved}
/>
{/* Order Summary */}
<div className="mt-6 bg-white p-6 rounded-lg">
<div className="flex justify-between mb-2">
<span>Subtotal:</span>
<span>₹2,599</span>
</div>
{appliedCoupon && (
<div className="flex justify-between mb-2 text-green-600">
<span>Discount ({appliedCoupon.couponCode}):</span>
<span>-₹{appliedCoupon.discountAmount}</span>
</div>
)}
<div className="flex justify-between text-xl font-bold border-t pt-2">
<span>Total:</span>
<span>₹{orderTotal.toLocaleString()}</span>
</div>
</div>
</div>
);
};
📡 API Endpoints
Admin Endpoints
1. Get All Coupons
GET /api/coupons/admin?page=1&limit=20&isActive=true&type=PERCENTAGE&search=SAVE
Authorization: Bearer <admin_token>
Response:
{
"status": true,
"message": "Coupons fetched successfully",
"data": {
"coupons": [
{
"id": "...",
"code": "SAVE20",
"description": "20% off on all items",
"type": "PERCENTAGE",
"value": 20,
"minOrderAmount": 1000,
"maxUses": 100,
"usedCount": 25,
"usagePercentage": 25,
"isExpired": false,
"remainingUses": 75,
"isActive": true,
"validFrom": "2024-01-01",
"validUntil": "2024-12-31"
}
],
"pagination": {
"total": 50,
"page": 1,
"limit": 20,
"pages": 3
}
}
}
2. Create Coupon
POST /api/coupons/admin
Authorization: Bearer <admin_token>
Content-Type: application/json
{
"code": "SAVE20",
"description": "Get 20% off on orders above ₹1000",
"type": "PERCENTAGE",
"value": 20,
"minOrderAmount": 1000,
"maxUses": 100,
"validFrom": "2024-01-01T00:00:00Z",
"validUntil": "2024-12-31T23:59:59Z",
"isActive": true
}
Coupon Types:
PERCENTAGE- Percentage discount (e.g., 20%)FIXED_AMOUNT- Fixed amount off (e.g., ₹500)FREE_SHIPPING- Free shipping
3. Update Coupon
PUT /api/coupons/admin/:id
Authorization: Bearer <admin_token>
{
"value": 25,
"maxUses": 150
}
4. Toggle Coupon Status
PATCH /api/coupons/admin/:id/toggle
Authorization: Bearer <admin_token>
5. Get Coupon Statistics
GET /api/coupons/admin/stats
Authorization: Bearer <admin_token>
Response:
{
"status": true,
"data": {
"totalCoupons": 50,
"activeCoupons": 35,
"expiredCoupons": 10,
"totalRedemptions": 2450,
"mostUsedCoupons": [...]
}
}
User Endpoints
1. Get Available Coupons
GET /api/coupons/available?orderAmount=2599
Response:
{
"status": true,
"data": [
{
"code": "SAVE20",
"description": "20% off on orders above ₹1000",
"type": "PERCENTAGE",
"value": 20,
"minOrderAmount": 1000,
"discountPreview": "20% OFF",
"remainingUses": 75,
"validUntil": "2024-12-31"
}
]
}
2. Validate Coupon
POST /api/coupons/validate
Content-Type: application/json
{
"code": "SAVE20",
"orderAmount": 2599
}
Response:
{
"status": true,
"message": "Coupon applied successfully",
"data": {
"couponCode": "SAVE20",
"couponType": "PERCENTAGE",
"discountAmount": 519.80,
"originalAmount": 2599,
"finalAmount": 2079.20,
"savings": 519.80,
"savingsPercentage": 20
}
}
3. Apply Coupon to Order
POST /api/coupons/apply
Authorization: Bearer <token>
{
"couponCode": "SAVE20",
"orderId": "order_123"
}
4. Remove Coupon
POST /api/coupons/remove
Authorization: Bearer <token>
{
"orderId": "order_123"
}
💡 Usage Examples
Example 1: Admin Creates Percentage Coupon
const createCoupon = async () => {
const response = await axios.post(
'http://localhost:3000/api/coupons/admin',
{
code: 'WELCOME10',
description: 'Get 10% off on your first order',
type: 'PERCENTAGE',
value: 10,
minOrderAmount: 500,
maxUses: 1000,
validFrom: new Date(),
validUntil: new Date('2024-12-31'),
isActive: true,
},
{
headers: {
Authorization: `Bearer ${adminToken}`,
},
}
);
};
Example 2: Admin Creates Fixed Amount Coupon
{
code: 'FLAT500',
description: 'Flat ₹500 off on orders above ₹2000',
type: 'FIXED_AMOUNT',
value: 500,
minOrderAmount: 2000,
maxUses: 500,
validFrom: new Date(),
validUntil: new Date('2024-12-31'),
}
Example 3: Admin Creates Free Shipping Coupon
{
code: 'FREESHIP',
description: 'Free shipping on all orders',
type: 'FREE_SHIPPING',
value: 0,
minOrderAmount: null,
maxUses: null,
validFrom: new Date(),
validUntil: new Date('2024-12-31'),
}
Example 4: Customer Applies Coupon
// In your checkout component
const [discount, setDiscount] = useState(0);
<CouponApply
orderAmount={2599}
onCouponApplied={(data) => {
setDiscount(data.discountAmount);
console.log('Saved:', data.savings);
}}
onCouponRemoved={() => {
setDiscount(0);
}}
/>
🎨 Coupon Validation Rules
Automatic Validations
- Active Status - Must be active
- Date Range - Must be within validFrom and validUntil
- Usage Limit - Must have remaining uses
- Minimum Order - Order amount must meet minimum requirement
- Code Uniqueness - Code must be unique (enforced on creation)
Error Messages
❌ "This coupon is no longer active"
❌ "This coupon is not yet valid"
❌ "This coupon has expired"
❌ "This coupon has reached its usage limit"
❌ "Minimum order amount of ₹1000 required"
❌ "Invalid coupon code"
📊 Coupon Types Explained
1. PERCENTAGE Discount
Order Amount: ₹2,599
Coupon: 20% OFF
Discount: ₹519.80
Final Amount: ₹2,079.20
2. FIXED_AMOUNT Discount
Order Amount: ₹2,599
Coupon: ₹500 OFF
Discount: ₹500
Final Amount: ₹2,099
3. FREE_SHIPPING
Order Amount: ₹2,599
Shipping: ₹50
Coupon: FREE SHIPPING
Discount: ₹50
Final Amount: ₹2,549
🔧 Advanced Features
Feature 1: User-Specific Coupons
Add a userId field to restrict coupons:
model Coupon {
// ... existing fields
userId String? // Optional: restrict to specific user
user User? @relation(fields: [userId], references: [id])
}
Feature 2: Product-Specific Coupons
model Coupon {
// ... existing fields
applicableCategories String[] // Only for these categories
applicableProducts String[] // Only for these products
}
Feature 3: First-Time User Coupons
// In validation logic
const userOrders = await prisma.order.count({
where: { userId, status: 'DELIVERED' }
});
if (coupon.firstTimeOnly && userOrders > 0) {
return { isValid: false, message: 'Coupon only for first-time users' };
}
✅ Testing Checklist
Backend Testing
- Create percentage coupon
- Create fixed amount coupon
- Create free shipping coupon
- Validate active coupon
- Validate expired coupon
- Validate coupon below min order
- Validate coupon at usage limit
- Apply coupon to order
- Remove coupon from order
- Toggle coupon status
- Get coupon statistics
Frontend Testing
- Apply valid coupon
- Apply invalid coupon
- View available coupons
- Remove applied coupon
- See discount in order summary
- Responsive design
- Loading states
- Error handling
🎯 Common Use Cases
Use Case 1: Flash Sale
{
code: 'FLASH50',
type: 'PERCENTAGE',
value: 50,
validFrom: new Date('2024-12-25T00:00:00'),
validUntil: new Date('2024-12-25T23:59:59'),
maxUses: 100
}
Use Case 2: Minimum Purchase Promotion
{
code: 'BUY1000GET200',
type: 'FIXED_AMOUNT',
value: 200,
minOrderAmount: 1000,
}
Use Case 3: New Customer Welcome
{
code: 'WELCOME',
type: 'PERCENTAGE',
value: 15,
description: 'Welcome! Get 15% off on your first order',
maxUses: null, // Unlimited
}
📈 Analytics & Reporting
Track coupon performance:
// Get most successful coupons
const topCoupons = await prisma.coupon.findMany({
orderBy: { usedCount: 'desc' },
take: 10,
});
// Calculate total savings given
const totalSavings = await calculateTotalDiscounts();
// Conversion rate
const conversionRate = (usedCount / impressions) * 100;
Your coupon system is ready! 🎉
Full features: ✅ Admin CRUD operations ✅ Smart validation ✅ Multiple coupon types ✅ Usage tracking ✅ Statistics & analytics ✅ User-friendly UI ✅ Mobile responsive
- User applies coupon "WELCOME10" ↓
- CouponApply validates coupon ↓
- Coupon data stored in state { couponCode: "WELCOME10", discountAmount: 259.90, ... } ↓
- User clicks "Place Order" ↓
- Frontend sends: { items: [...], couponCode: "WELCOME10" ✅ } ↓
- Backend validates coupon again ↓
- Creates order in transaction:
- Creates order
- Increments coupon.usedCount ✅ ↓
- Admin panel shows: "usedCount": 1 ✅