Skip to content
arminrad edited this page Mar 16, 2026 · 2 revisions

Coupon System

Discount codes and promotional credits


Overview

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

Quick Start

Create Coupon (Admin)

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"
  }'

Redeem Coupon (User)

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"
}

API Endpoints

1. Create Coupon (Admin)

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

2. List Coupons (Admin)

GET /admin/coupons

Query params:

  • active_only: Boolean
  • limit, offset: Pagination

3. Get Coupon (Admin)

GET /admin/coupons/{code}

Includes usage statistics.

4. Update Coupon (Admin)

PUT /admin/coupons/{code}

Update expiration, max uses, etc.

5. Deactivate Coupon (Admin)

DELETE /admin/coupons/{code}

6. Validate Coupon (Public)

POST /coupons/validate

Check if code valid without redeeming.

7. Redeem Coupon (User)

POST /coupons/redeem

Apply coupon to account.


Discount Types

Percentage

{
  "discount_type": "percentage",
  "discount_value": 20  // 20% off
}

Applied to purchase:

  • $50 purchase → $10 discount → $40 final
  • Credits: 4000 instead of 5000

Fixed Amount

{
  "discount_type": "fixed",
  "discount_value": 15  // $15 off
}

Direct credit:

  • User gets $15 added to balance
  • Or $15 off next purchase

Usage Limits

Total Uses

{
  "max_uses": 1000,
  "uses": 234,
  "remaining": 766
}

Per User

{
  "max_uses_per_user": 1,
  "user_uses": 1,
  "can_use_again": false
}

Database Schema

coupons Table

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()
);

coupon_redemptions Table

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
);

Validation Rules

Code Format

  • Uppercase: Automatically converted
  • Alphanumeric: A-Z, 0-9 only
  • Length: 4-20 characters
  • Unique: No duplicates

Redemption Checks

  1. Code exists: Valid coupon
  2. Active: is_active = true
  3. Not expired: expires_at > NOW()
  4. Uses remaining: uses < max_uses
  5. User limit: user_uses < max_uses_per_user
  6. User not redeemed: Check redemptions table

Use Cases

1. Welcome Bonus

{
  "code": "WELCOME10",
  "discount_type": "fixed",
  "discount_value": 10,
  "max_uses_per_user": 1,
  "description": "New user welcome credit"
}

2. Seasonal Promotion

{
  "code": "BLACKFRIDAY50",
  "discount_type": "percentage",
  "discount_value": 50,
  "max_uses": 10000,
  "expires_at": "2024-11-30T23:59:59Z"
}

3. Referral Reward

{
  "code": "REF_USER123",
  "discount_type": "fixed",
  "discount_value": 5,
  "max_uses": 1,
  "max_uses_per_user": 1
}

4. Partnership Deal

{
  "code": "PARTNER2024",
  "discount_type": "percentage",
  "discount_value": 25,
  "max_uses": 500,
  "expires_at": "2024-12-31T23:59:59Z"
}

Bulk Generation

# 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)

Analytics

Usage Statistics

-- 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;

Best Practices

For Admins

  1. Expiration dates: Always set expiration
  2. Usage limits: Prevent abuse
  3. Track performance: Monitor redemptions
  4. Deactivate old: Remove expired coupons
  5. Unique codes: For tracking campaigns
  6. Test first: Validate before launch

For Users

  1. Check expiration: Use before expiry
  2. One-time use: Can't reuse most coupons
  3. Case insensitive: WELCOME10 = welcome10
  4. Share carefully: May have usage limits

Troubleshooting

Coupon not working

Causes:

  • Expired
  • Max uses reached
  • Already redeemed by user
  • Invalid code

Solution: Check status via /coupons/validate

Duplicate redemption

Cause: User trying to use twice

Solution: Database constraint prevents this


Implementation

Location:

  • DB: src/db/coupons.py
  • Routes: src/routes/coupons.py

Related Documentation


Last Updated: December 2024 Status: Production Ready


Related

Clone this wiki locally