-
Notifications
You must be signed in to change notification settings - Fork 1
Coupon System
arminrad edited this page Mar 16, 2026
·
2 revisions
Discount codes and promotional credits
Create and manage promotional codes:
- Percentage discounts (10%, 20%, etc.)
- Fixed amount credits ($10, $50, etc.)
- Usage limits per coupon
- Expiration dates
- One-time use per user
- Bulk generation
curl -X POST http://localhost:8000/admin/coupons \
-H "Authorization: Bearer ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "WELCOME10",
"discount_type": "percentage",
"discount_value": 10,
"max_uses": 1000,
"expires_at": "2024-12-31T23:59:59Z"
}'curl -X POST http://localhost:8000/coupons/redeem \
-H "Authorization: Bearer USER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "WELCOME10"
}'Response:
{
"success": true,
"credits_added": 10.0,
"new_balance": 20.0,
"message": "Coupon WELCOME10 applied successfully"
}POST /admin/coupons
Request:
{
"code": "SUMMER2024",
"discount_type": "fixed",
"discount_value": 25,
"max_uses": 500,
"max_uses_per_user": 1,
"expires_at": "2024-09-01T00:00:00Z",
"description": "Summer promotion"
}Fields:
-
code: Coupon code (uppercase, unique) -
discount_type:"percentage"or"fixed" -
discount_value: % or $ amount -
max_uses: Total redemptions allowed -
max_uses_per_user: Per-user limit -
expires_at: Expiration datetime -
description: Optional description
GET /admin/coupons
Query params:
-
active_only: Boolean -
limit,offset: Pagination
GET /admin/coupons/{code}
Includes usage statistics.
PUT /admin/coupons/{code}
Update expiration, max uses, etc.
DELETE /admin/coupons/{code}
POST /coupons/validate
Check if code valid without redeeming.
POST /coupons/redeem
Apply coupon to account.
{
"discount_type": "percentage",
"discount_value": 20 // 20% off
}Applied to purchase:
- $50 purchase → $10 discount → $40 final
- Credits: 4000 instead of 5000
{
"discount_type": "fixed",
"discount_value": 15 // $15 off
}Direct credit:
- User gets $15 added to balance
- Or $15 off next purchase
{
"max_uses": 1000,
"uses": 234,
"remaining": 766
}{
"max_uses_per_user": 1,
"user_uses": 1,
"can_use_again": false
}CREATE TABLE coupons (
id INTEGER PRIMARY KEY,
code TEXT UNIQUE NOT NULL,
discount_type TEXT NOT NULL, -- 'percentage', 'fixed'
discount_value NUMERIC NOT NULL,
max_uses INTEGER,
uses INTEGER DEFAULT 0,
max_uses_per_user INTEGER DEFAULT 1,
expires_at TIMESTAMP,
is_active BOOLEAN DEFAULT true,
description TEXT,
created_at TIMESTAMP DEFAULT NOW()
);CREATE TABLE coupon_redemptions (
id INTEGER PRIMARY KEY,
coupon_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
credits_added NUMERIC,
redeemed_at TIMESTAMP DEFAULT NOW(),
FOREIGN KEY (coupon_id) REFERENCES coupons(id),
UNIQUE(coupon_id, user_id) -- One use per user
);- Uppercase: Automatically converted
- Alphanumeric: A-Z, 0-9 only
- Length: 4-20 characters
- Unique: No duplicates
- Code exists: Valid coupon
-
Active:
is_active = true -
Not expired:
expires_at > NOW() -
Uses remaining:
uses < max_uses -
User limit:
user_uses < max_uses_per_user - User not redeemed: Check redemptions table
{
"code": "WELCOME10",
"discount_type": "fixed",
"discount_value": 10,
"max_uses_per_user": 1,
"description": "New user welcome credit"
}{
"code": "BLACKFRIDAY50",
"discount_type": "percentage",
"discount_value": 50,
"max_uses": 10000,
"expires_at": "2024-11-30T23:59:59Z"
}{
"code": "REF_USER123",
"discount_type": "fixed",
"discount_value": 5,
"max_uses": 1,
"max_uses_per_user": 1
}{
"code": "PARTNER2024",
"discount_type": "percentage",
"discount_value": 25,
"max_uses": 500,
"expires_at": "2024-12-31T23:59:59Z"
}# Generate 100 unique codes
import secrets
import string
def generate_coupon_code(prefix="PROMO"):
random = ''.join(
secrets.choice(string.ascii_uppercase + string.digits)
for _ in range(8)
)
return f"{prefix}_{random}"
for _ in range(100):
code = generate_coupon_code("BATCH")
create_coupon(code, discount_type="fixed", discount_value=5)-- Top performing coupons
SELECT code, uses, max_uses,
(uses::float / max_uses * 100) as usage_pct
FROM coupons
WHERE max_uses > 0
ORDER BY usage_pct DESC;
-- Total credits distributed
SELECT SUM(credits_added) as total_credits
FROM coupon_redemptions;
-- Most popular
SELECT c.code, COUNT(*) as redemptions
FROM coupons c
JOIN coupon_redemptions cr ON c.id = cr.coupon_id
GROUP BY c.code
ORDER BY redemptions DESC;- Expiration dates: Always set expiration
- Usage limits: Prevent abuse
- Track performance: Monitor redemptions
- Deactivate old: Remove expired coupons
- Unique codes: For tracking campaigns
- Test first: Validate before launch
- Check expiration: Use before expiry
- One-time use: Can't reuse most coupons
- Case insensitive: WELCOME10 = welcome10
- Share carefully: May have usage limits
Causes:
- Expired
- Max uses reached
- Already redeemed by user
- Invalid code
Solution: Check status via /coupons/validate
Cause: User trying to use twice
Solution: Database constraint prevents this
Location:
-
DB:
src/db/coupons.py -
Routes:
src/routes/coupons.py
- Referral System - User referrals
- Stripe Integration - Apply to payments
Last Updated: December 2024 Status: Production Ready
- Stripe-Integration — Payment processing
- Free-Trial-System — Coupons can supplement trial credits
- Referral-System — Another way users earn credits
Reading Path (start here, in order)
- Conceptual Model
- Stability Definition
- Conceptual Model Features
- Features
- Delta Report
- Features-Acceptance-Criteria
Testing
Security & Access
Billing
Monitoring
Features
Providers
Operations
Data References