Skip to content

E-Commerce Cart & Coupon Optimizer (Indicator MIP) ​

Problem ClassSolverKey APIsLinks
Mixed-Integer Programming (MIP)HiGHS (default)m.binary(), m.constraint(), m.indicator(), c, minimize

Modern e-commerce checkouts often feature complex promotional rules: minimum spend thresholds, category-specific discounts, stackable vs. mutually exclusive coupons, and maximum coupon limits per order. Running an optimizer directly in the browser (or an edge worker) lets the checkout UI automatically apply the best coupon combination—and even recommend a small "filler" item when crossing a spend threshold lowers the customer's net bill.

This example demonstrates m.indicator(binVar, constraint, { activeValue, M }), which automatically linearizes conditional "if coupon is selected, minimum spend must hold" constraints using Big-M reformulation for MIP solvers like HiGHS.

INTERACTIVE EXAMPLE · HiGHS Indicator Constraints

E-Commerce Cart & Smart Coupon Optimizer

Select items in the cart and click '▶ Run Optimization' to find the best coupon combination and smart filler add-on.

Wireless ANC Headphones$160
Mechanical Keyboard$80
4K Streaming Webcam$95
Aluminum Laptop Stand$45
Monitor Light Bar$55
Felt Desk Mat$25
Candidates: USB-C Cable (+$10), Cleaning Kit (+$15), Keycap Puller (+$25)
WELCOME15$15 off $100+ (Stackable)
Skipped
MEMBER10$10 off $180+ (Stackable)
Skipped
SAVE30_200$30 off $200+ (Tier exclusive)
Skipped
MEGA50_250$50 off $250+ (Tier exclusive)
Skipped
Selected Items Subtotal$240
Total Coupon Discounts-$0
Final Cash Payable$240

Scenario & Promotion Rules ​

A customer's shopping cart currently contains $242 of merchandise ($210 in Tech items and $32 in Apparel):

  • Optional Filler Add-on: A $10 Braided USB-C Cable (Tech) can be added to bring the cart total from $242 to $252.
  • Available Coupons (maximum 2 coupons total per checkout; at most 1 Exclusive coupon):
Coupon CodeDiscountConditionStacking Rule
WELCOME15-$15Total cart spend ≥$100\ge \$100Stackable
TECH30-$30Tech category spend ≥$200\ge \$200Exclusive (cannot combine with MEGA45_250)
MEGA45_250-$45Total cart spend ≥$250\ge \$250Exclusive (cannot combine with TECH30)
MEMBER10-$10Total cart spend ≥$150\ge \$150Stackable

Without adding the $10 cable, the cart ($242) cannot use MEGA45_250 and pays $242 - $30 (TECH30) - $15 (WELCOME15) = $197.
By adding the $10 cable ($252 gross), the customer unlocks MEGA45_250 (-$45) + WELCOME15 (-$15) for a net total of $192—saving an extra $5 while getting an extra item!

Implementation in WaSHI ​

typescript
import { Model, sum, c, minimize } from "@ohtaman/washi";

async function optimizeCheckoutCart() {
  const m = new Model("CartCouponOptimizer");

  const baseCartSubtotal = 242;
  const baseTechSubtotal = 210;

  // Binary decision: add optional $10 USB-C cable to unlock $250 tier?
  const addUsbCable = m.binary("addon_USB_Cable");
  const usbCablePrice = 10;

  const coupons = [
    { id: "WELCOME15",  discount: 15, minCart: 100, minTech: 0,   stackable: true  },
    { id: "TECH30",     discount: 30, minCart: 0,   minTech: 200, stackable: false },
    { id: "MEGA45_250", discount: 45, minCart: 250, minTech: 0,   stackable: false },
    { id: "MEMBER10",   discount: 10, minCart: 150, minTech: 0,   stackable: true  },
  ];

  // Binary decision variables for each coupon
  const useCoupon = m.binary(coupons.map((c) => c.id));

  const merchSpend = addUsbCable.mul(usbCablePrice).add(baseCartSubtotal);
  const techSpend  = addUsbCable.mul(usbCablePrice).add(baseTechSubtotal);

  // 1. Indicator constraints: If useCoupon[id] == 1, minimum spend threshold must hold
  for (const cpn of coupons) {
    if (cpn.minCart > 0) {
      m.indicator(useCoupon[cpn.id], merchSpend.ge(cpn.minCart), {
        activeValue: 1,
        M: 500,
        name: `min_spend_${cpn.id}`,
      });
    }
    if (cpn.minTech > 0) {
      m.indicator(useCoupon[cpn.id], techSpend.ge(cpn.minTech), {
        activeValue: 1,
        M: 500,
        name: `min_tech_${cpn.id}`,
      });
    }
  }

  // 2. Mutual exclusion among non-stackable ("Exclusive") coupons
  const exclusiveCoupons = coupons.filter((c) => !c.stackable);
  m.constraint(
    c`${sum(exclusiveCoupons, (c) => useCoupon[c.id])} <= 1`,
    "at_most_one_exclusive"
  );

  // 3. Max 2 coupons per order
  m.constraint(
    c`${sum(coupons, (c) => useCoupon[c.id])} <= 2`,
    "max_two_coupons"
  );

  // Objective: Minimize final net payable amount (merchSpend - totalDiscounts)
  const totalDiscount = sum(coupons, (c) => useCoupon[c.id].mul(c.discount));
  m.objective(minimize(merchSpend.sub(totalDiscount)));

  const status = await m.solve();
  console.log(`Status: ${status}, Optimal Net Payable: $${m.objectiveValue}`);
  console.log(`Recommended Add-on (+$10 USB-C Cable): ${(addUsbCable.value ?? 0) > 0.5 ? "YES" : "NO"}`);
  for (const cpn of coupons) {
    const applied = (useCoupon[cpn.id].value ?? 0) > 0.5;
    console.log(`  ${applied ? "✅ [APPLIED]" : "   [skipped]"} ${cpn.id.padEnd(11)} (-$${cpn.discount})`);
  }
}

optimizeCheckoutCart();

Released under the MIT License.