Chapter 207 min read

Chapter 20: Coupon, Discount, and Promotion Engines

Why This Exists

E-commerce is ruthlessly competitive. To drive sales, clear out seasonal inventory, and reward loyal customers, marketing teams need levers to manipulate prices dynamically. However, modifying the core price of a product in the database every time there is a sale is dangerous and destroys historical financial data. Promotion engines exist to apply complex mathematical rules on top of a shopping cart, temporarily lowering the total cost for specific users under specific conditions without altering the underlying catalog.

Real World Problem

The marketing team issues two discount codes:

  • SUMMER50: $50 off your order.
  • EMPLOYEE20: 20% off your entire order. A smart customer adds a $60 pair of shoes to their cart. They apply EMPLOYEE20 (saving $12). The cart is now $48. They apply SUMMER50 (saving $50). The cart total is now -$2.00. The checkout system crashes, or worse, processes a negative charge and issues a refund to the customer, essentially paying them to take the shoes. The real-world problem is managing overlapping rules, priorities, and boundaries in pricing math.

Everyday Analogy

Think of a coupon clipper at a grocery checkout. The cashier doesn't just scan the coupon and hand over money. The cashier acts as a "Rules Engine."

  • Condition Check: "Does this coupon apply to the specific brand of cereal you bought?"
  • Validity Check: "Is this coupon expired?"
  • Stacking Check: "The sign says 'Cannot be combined with other offers.' I must reject your second coupon." A Promotion Engine is just a massive block of code simulating that cashier's logic at lightning speed.

Beginner Explanation

A discount code (like WELCOME10) is a rule. When you type it into the cart, the computer checks:

  1. Is the code real?
  2. Is it still valid (not expired)?
  3. Does the cart meet the rules (e.g., "Must spend over $100")? If everything is yes, it does some math (subtracts 10%) and shows you the new, cheaper total.

Intermediate Explanation

Discounts generally fall into three types:

  • Fixed Amount: Subtract a specific dollar amount (e.g., -$10).
  • Percentage: Subtract a percentage of the total (e.g., -15%).
  • Free Shipping: Zero out the shipping line item.

The engine must evaluate Conditions. A discount might only apply if:

  • The user belongs to the "VIP" customer group.
  • The cart contains at least 3 items from the "Shoes" category.
  • The current date is between Friday and Monday (Black Friday sale).

Because these rules are highly dynamic, hardcoding them (e.g., if (code == 'VIP')) doesn't work. The architecture relies on data-driven Rule Engines.

Advanced Explanation

At scale, architectures must handle Discount Stacking and Conflict Resolution. If a cart qualifies for a 10% Storewide Sale AND the user inputs a 20% Coupon Code, how do they combine?

  • Additive: 10% + 20% = 30% off. (Dangerous for margins).
  • Sequential: Take 10% off $100 = $90. Then take 20% off $90 = $72. (Safer).
  • Best Value (Exclusive): The engine calculates both, realizes 20% is better for the customer, applies it, and discards the 10% rule.

Furthermore, discounts must be applied proportionally to Line Items, not just the final total. If you have a $50 shirt and a $50 hat, and apply a $10 coupon, the system must record a $5 discount on the shirt and a $5 discount on the hat. This is a strict legal requirement for correctly calculating state sales tax and processing partial returns later.

Real World Example

Target's Cartwheel / Circle App: Target's promotion engine is legendary. A user can have a manufacturer coupon (from Kellogg's), a store-wide category sale (10% off Groceries), a personalized offer (Spend $50, get a $5 gift card), and a RedCard discount (5% off total). When the user scans their barcode at the register, the backend Promotion Engine evaluates hundreds of overlapping rules in milliseconds, figures out the legal stacking order, proportionally divides the discounts across the line items, and calculates the final tax.

Architecture Design

The Cart Service offloads the math to a dedicated Promotion Engine:

sequenceDiagram
    participant Client
    participant Cart_Service
    participant Promo_Engine
    participant Catalog_DB

    Client->>Cart_Service: Apply Code: "BOGO_FREE"
    Cart_Service->>Promo_Engine: Evaluate Cart payload + "BOGO_FREE"
    
    Note over Promo_Engine: Load active rules from DB
    Promo_Engine->>Promo_Engine: Check Expiration & Usage Limits
    Promo_Engine->>Promo_Engine: Evaluate Conditions (Are there 2 eligible items?)
    Promo_Engine->>Promo_Engine: Calculate Line Item Math
    
    Promo_Engine-->>Cart_Service: Returns updated Cart Total & Discount Lines
    Cart_Service-->>Client: Success: Cart updated!

Database Design

1. Promotions Table (The Header):

CREATE TABLE promotions (
    id UUID PRIMARY KEY,
    code VARCHAR(50) UNIQUE, -- 'SUMMER20'
    discount_type VARCHAR(20), -- 'PERCENTAGE', 'FIXED_CART', 'FIXED_ITEM'
    discount_value DECIMAL(10,2), -- 20.00
    starts_at TIMESTAMP,
    ends_at TIMESTAMP,
    usage_limit INT -- e.g., only 100 people can use this
);

2. Promotion Conditions (The Rules):

CREATE TABLE promotion_conditions (
    id UUID PRIMARY KEY,
    promotion_id UUID,
    condition_type VARCHAR(50), -- 'MIN_CART_VALUE', 'REQUIRED_CATEGORY'
    condition_value VARCHAR(255) -- '100.00' or 'category_id_5'
);

API Design

Apply Coupon to Cart: POST /api/cart/{cart_id}/coupons Payload: { "code": "SUMMER20" }

Response (Notice the Line Item breakdown):

{
  "subtotal": 100.00,
  "discount_total": 20.00,
  "tax_total": 6.40, // 8% tax applied to the discounted $80 amount
  "grand_total": 86.40,
  "items": [
    { "sku": "SHIRT", "price": 100.00, "discount_applied": 20.00 }
  ]
}

Production Considerations

  • Performance Constraints: Marketing teams love complex rules ("Buy 2 shirts, get 50% off a hat, but only if the hat is blue"). Evaluating these rules against a cart with 50 items requires looping over permutations. The rule engine must be highly optimized and heavily cached to prevent adding 2 seconds of latency to every checkout step.
  • Concurrency (Usage Limits): If a coupon is limited to "First 100 customers," and 5,000 people try to use it simultaneously during a flash sale, the system must use Redis distributed locks (or UPDATE ... RETURNING) to ensure exactly 100 people get the discount.

Security Considerations

  • Brute Force Attacks: Hackers will write scripts that hit POST /apply-coupon with PROMO1, PROMO2, TEST10, ADMIN100 thousands of times a second to find hidden developer or employee discount codes. The endpoint must have strict Rate Limiting based on IP address and User Session.

Common Mistakes

  • Applying Discounts After Tax: Calculating 10% tax on a $100 cart ($110), then applying a $20 discount ($90). This is illegal. You collected tax on $100 of revenue but only earned $80. Discounts must be applied to the subtotal before tax calculation.
  • Negative Carts: Failing to use a MAX(0, total - discount) constraint, allowing multiple fixed-amount discounts to push the cart total below zero.

Tradeoffs and Alternatives

  • Custom Built Engine vs SaaS: Building a basic percentage discount is easy. Building a complex rules engine that handles overlapping BOGO conditions, tiered pricing, and exclusive stacking rules takes a senior engineering team months. Many companies offload this to specialized SaaS API providers (like Talon.One or Voucherify) to handle the extreme complexity of promotional math.

Interview Questions

  1. A user applies a $10 coupon to a cart containing a $30 shirt and a $20 hat. Explain why you must split that $10 discount across the two line items rather than just subtracting $10 from the total.
  2. How do you prevent a user's cart total from dropping below $0 when multiple discounts are applied?
  3. How do you protect a coupon endpoint from a brute-force guessing attack?

Hands-On Exercise

  1. Assume a cart has Item A ($50) and Item B ($50).
  2. A $20 flat-rate coupon is applied to the entire cart.
  3. Write the pseudocode to proportionally distribute the $20 discount across Item A and Item B based on their weight (price). (Hint: Since they are both 50% of the cart value, they should each get 50% of the discount).

Key Takeaways

  • Promotion engines isolate the volatile logic of sales and marketing from the stable prices in the Product Catalog.
  • The math is complex: architectures must handle Rule Evaluation, Constraint Checking, and Conflict Resolution (Stacking).
  • Discounts must be applied proportionally to individual Line Items before calculating tax.
  • Defend the coupon API heavily against brute-force guessing and concurrency race conditions.

Further Reading

  • The concept of "Proportional Discount Allocation" in E-commerce
  • Shopify API: Price Rules and Discount Codes
    Chapter 20: Coupon, Discount, and Promotion Engines — Architecting Modern E-Commerce Systems: From First Principles to AI-Powered Marketplaces | Krishna Tiwari