Skip to content

feat: Promo Codes and Coupon System#375

Merged
Aamod-Dev merged 5 commits into
niharika-mente:mainfrom
swetalin-10:feat/promo-codes
Jun 22, 2026
Merged

feat: Promo Codes and Coupon System#375
Aamod-Dev merged 5 commits into
niharika-mente:mainfrom
swetalin-10:feat/promo-codes

Conversation

@swetalin-10

Copy link
Copy Markdown
Contributor

Closes #138

What's changed

Backend

BACKEND/models/coupon.model.js (new)

Field Type Notes
code String Unique, uppercase, 3–20 chars
type 'percentage' | 'flat' Discount type
value Number % (1–100) or flat $ amount
minOrderAmount Number Minimum cart total to apply
maxUses Number | null null = unlimited
usedCount Number Auto-incremented on fulfillment
expiresAt Date | null null = never expires
isActive Boolean Soft-disable without deleting

BACKEND/controllers/coupon.controller.js (new)

  • POST /api/coupons/validate — public; checks code validity, expiry, usage limit, and minimum order; returns discount and finalTotal
  • POST /api/coupons — admin: create coupon
  • GET /api/coupons — admin: list all coupons
  • PATCH /api/coupons/:code/deactivate — admin: soft-deactivate

BACKEND/controllers/checkout.controller.js

  • Accepts optional couponCode alongside items
  • Re-validates the coupon server-side (not trusting frontend discount amount)
  • Applies discount by scaling each line item's unit_amount proportionally so Stripe sees the correct discounted total
  • Stores couponCode and discountAmount in Stripe session metadata
  • Webhook increments usedCount after successful payment fulfillment

Frontend — Cart Drawer (Navbar.jsx)

Promo code input — visible only when cart has items:

  • Text input auto-uppercases as you type; press Enter or click Apply
  • Calls POST /api/coupons/validate with current cart total
  • On success: input replaced by a dismissable green tag showing code + savings
  • On error: toast with the reason (expired, limit reached, minimum not met, etc.)

Updated totals section:

  • Strikethrough original price shown when a coupon is applied
  • Discounted total displayed in cyan
  • couponCode forwarded to POST /api/checkout on proceed

Creating coupons (admin)

# 20% off all orders
POST /api/coupons
{ "code": "SUMMER20", "type": "percentage", "value": 20 }

# $10 flat off orders over $50
POST /api/coupons
{ "code": "SAVE10", "type": "flat", "value": 10, "minOrderAmount": 50 }

# 15% off, max 100 uses, expires end of year
POST /api/coupons
{ "code": "HOLIDAY15", "type": "percentage", "value": 15, "maxUses": 100, "expiresAt": "2026-12-31" }

Screenshots

Entering a promo code

Promo code input

After applying — discount reflected in total

Coupon applied

Backend:
- Coupon model: code (unique, uppercase), type (percentage|flat),
  value, minOrderAmount, maxUses, usedCount, expiresAt, isActive
- POST /api/coupons/validate — public, validates code + cart total,
  returns discount and final total
- POST /api/coupons — admin: create coupon
- GET  /api/coupons — admin: list all coupons
- PATCH /api/coupons/:code/deactivate — admin: soft-deactivate
- checkout.controller: accepts optional couponCode, validates it,
  applies discount by scaling Stripe line item unit_amounts
  proportionally; stores couponCode in session metadata; webhook
  increments usedCount after successful payment

Frontend (Navbar cart drawer):
- Promo code input with Apply button; validates against API
- Applied coupon shown as dismissable tag with savings amount
- Total section shows strikethrough original + discounted price
- couponCode forwarded to /api/checkout on Proceed to Checkout
@vercel

vercel Bot commented Jun 20, 2026

Copy link
Copy Markdown

@swetalin-10 is attempting to deploy a commit to the niharika-mente's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Aamod-Dev

Copy link
Copy Markdown
Collaborator

@swetalin-10 can u resolve conflicts

Resolved conflict in BACKEND/app.js — kept couponRoutes (this PR),
newsletterRoutes and analyticsRoutes (upstream)
@Aamod-Dev Aamod-Dev added the Hard label Jun 21, 2026
Aamod-Dev
Aamod-Dev previously approved these changes Jun 21, 2026

@Aamod-Dev Aamod-Dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, approved!

Resolved conflict in BACKEND/controllers/checkout.controller.js:
- kept coupon usedCount increment (this PR)
- kept processReferralOnPurchase call (upstream)
Both run after Order.create in the Stripe webhook handler.
Aamod-Dev
Aamod-Dev previously approved these changes Jun 21, 2026

@Aamod-Dev Aamod-Dev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, approved!

Resolved conflict in Navbar.jsx handleCheckout:
- kept setAppliedCoupon(null) from this PR (clear coupon after checkout)
- dropped emptyCart() — SuccessPage already handles it on mount
- kept window.location.href = data.url from upstream (Stripe redirect fix)
@Aamod-Dev Aamod-Dev merged commit 61da9db into niharika-mente:main Jun 22, 2026
1 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Promo Codes and Coupon System

2 participants