
Discount Engine
- 72 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Configure a discount system for percentage-off, fixed-amount, BOGO, tiered thresholds, and conditional rules on your platform or custom cart.
About
Shows how to set up native discount capabilities and apps per platform before building custom rule evaluation. A developer uses it when adding promo codes, tiered pricing, or BOGO/bundle promotions to a checkout.
- Per-platform native discount capabilities and when to add apps
- Guidance to configure the platform engine before writing custom code
Discount Engine by the numbers
- 72 all-time installs (skills.sh)
- Ranked #3,076 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill discount-engineAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 72 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Configure a discount system for percentage-off, fixed-amount, BOGO, tiered thresholds, and conditional rules on your platform or custom cart.
Files
Discount Engine
Overview
A discount engine evaluates promotions against a cart and applies the right discounts in the right order. Every major e-commerce platform has one built in — you should configure the platform's native system before considering custom code. This skill covers how to set up complex discount logic (percentage off, fixed amount, BOGO, tiered thresholds, conditional rules) on each platform, and when to use apps or plugins to extend native capabilities.
When to Use This Skill
- When building a promotions system for a new e-commerce store
- When adding coupon code support to an existing checkout flow
- When implementing tiered pricing (e.g., buy 3+ get 10% off, buy 10+ get 20% off)
- When creating automatic discounts that apply based on cart conditions
- When you need BOGO, bundle, or gift-with-purchase promotions
Core Instructions
Step 1: Determine the merchant's platform and choose the right tool
| Platform | Built-in Discount Capabilities | When to Add Apps/Plugins |
|---|---|---|
| Shopify | Automatic discounts, code-based discounts, BOGO, tiered Buy X Get Y — all in Discounts admin | Shopify Plus Scripts for advanced stacking and custom logic; Bold Discounts for visual rule building |
| WooCommerce | Coupons (core) cover basic cases | For BOGO, tiered quantity pricing, or automatic rule-based discounts: YITH WooCommerce Dynamic Pricing & Discounts (~$70/yr) or Dynamic Pricing extension by WooCommerce (~$99/yr) |
| BigCommerce | Promotions engine supports cart-level and item-level discounts, free products, BOGO | BigCommerce's built-in Promotions are powerful — use custom scripts for edge cases |
| Custom / Headless | Must build — see below | N/A |
Step 2: Set up automatic discounts and BOGO on your platform
---
Shopify
Automatic discounts (no code required): 1. Go to Discounts → Create discount → Amount off products (or Amount off order) 2. Toggle No discount code (makes it automatic — applies without a code) 3. Set the discount value and conditions 4. Under Minimum purchase requirements: set a minimum quantity or subtotal to trigger the discount 5. Set the Active dates — automatic discounts apply to all eligible carts during this window
BOGO (Buy X Get Y): 1. Go to Discounts → Create discount → Buy X get Y 2. Set the Customer buys section: quantity and which products/collections qualify 3. Set the Customer gets section: quantity of free/discounted items and which products 4. Set the discount percentage (100% = free) 5. Set a Maximum uses limit if needed
Tiered quantity discounts (Shopify): Native Shopify does not support tiered quantity breaks (e.g., 1–4 units = full price, 5–9 = 10% off, 10+ = 20% off) without an app. Options:
- Bold Discounts (App Store, ~$20/month): visual interface for tiered pricing rules
- Wholesale Club (App Store): adds a wholesale/tiered pricing layer for B2B
- Shopify Plus + Shopify Functions: write a Discount Function in JavaScript that applies the correct tier based on cart line quantities
Stacking rules: In Shopify, each discount can be configured to combine with others: 1. Edit a discount → scroll to Combinations 2. Toggle which discount types it can be combined with: product discounts, order discounts, shipping discounts
---
WooCommerce
WooCommerce core coupons cover single-code, percentage, and fixed-amount discounts. For automatic, rule-based, and BOGO discounts, use the Dynamic Pricing & Discounts plugin.
Installing Dynamic Pricing: 1. Purchase and install YITH WooCommerce Dynamic Pricing & Discounts or the WooCommerce-branded Dynamic Pricing extension 2. Go to WooCommerce → Dynamic Pricing → Add Rule
Creating a tiered quantity rule: 1. Set rule type to Product Pricing or Category Pricing 2. Add pricing tiers:
- From 1 to 4 quantity: 0% off (full price)
- From 5 to 9: 10% off
- From 10 to 24: 20% off
- From 25 and above: 30% off
3. Set which products or categories this applies to 4. Save and publish
BOGO in WooCommerce: 1. In Dynamic Pricing, create a new rule of type Cart Pricing 2. Set the condition: "When cart contains X of [product]" 3. Set the action: "Add Y of [product] at [0% of regular price]" 4. Or use WooCommerce Smart Coupons for gift-with-purchase
Preventing unintended stacking: In WooCommerce core coupons, tick Individual use only to prevent a coupon from combining with other coupons. For automatic rules in Dynamic Pricing, each rule has a "Stops further rules" option that prevents lower-priority rules from applying.
---
BigCommerce
BigCommerce has a native Promotions system that covers most discount scenarios.
1. Go to Marketing → Promotions → Create promotion 2. Set the Promotion type: cart-level discount, item-level discount, free shipping, free item, BOGO 3. Configure Conditions:
- Minimum cart subtotal
- Specific products or categories included/excluded
- Customer groups (for B2B or segment-specific pricing)
4. Set Actions: the discount amount and which items it applies to 5. Set Coupon (optional): attach a code to require manual application, or leave blank for automatic 6. Set Rules for stacking: BigCommerce allows defining whether a promotion can stack with other promotions
Tiered quantity pricing on BigCommerce: Use the Price Lists feature (Plus plan and above): 1. Go to Products → Price Lists 2. Create a price list with explicit per-unit prices at different quantity thresholds 3. Assign the price list to a customer group 4. Customers in that group automatically see the tiered prices
---
Custom / Headless
For custom storefronts, the discount engine evaluates applicable discounts and allocates them to cart lines. The key principles are: evaluate server-side, apply in priority order, and enforce stacking rules.
interface Discount {
id: string;
type: 'percentage' | 'fixed_amount' | 'bogo' | 'free_shipping';
value: number; // percentage (0-100) or cents
target: 'order' | 'line_item' | 'shipping';
isStackable: boolean;
minCartCents?: number;
minQuantity?: number;
entitledProductIds?: string[];
excludedProductIds?: string[];
startsAt: Date;
endsAt?: Date;
}
interface CartLine {
id: string;
productId: string;
quantity: number;
unitPriceCents: number;
}
function evaluateDiscounts(
cart: { lines: CartLine[]; subtotalCents: number },
discounts: Discount[]
): { discountId: string; amountCents: number; affectedLineIds: string[] }[] {
const now = new Date();
const eligible = discounts.filter(d =>
d.startsAt <= now && (!d.endsAt || d.endsAt > now)
);
// Sort: non-stackable first, then higher-value
const sorted = [...eligible].sort((a, b) => (a.isStackable ? 1 : -1) - (b.isStackable ? 1 : -1));
const applications: { discountId: string; amountCents: number; affectedLineIds: string[] }[] = [];
let nonStackableApplied = false;
for (const discount of sorted) {
if (!discount.isStackable && nonStackableApplied) continue;
// Check minimum cart value
if (discount.minCartCents && cart.subtotalCents < discount.minCartCents) continue;
const eligibleLines = cart.lines.filter(line =>
(!discount.entitledProductIds || discount.entitledProductIds.includes(line.productId)) &&
(!discount.excludedProductIds || !discount.excludedProductIds.includes(line.productId))
);
if (eligibleLines.length === 0) continue;
let amountCents = 0;
if (discount.type === 'percentage') {
amountCents = Math.round(
eligibleLines.reduce((s, l) => s + l.unitPriceCents * l.quantity, 0) * discount.value / 100
);
} else if (discount.type === 'fixed_amount') {
amountCents = Math.min(discount.value, cart.subtotalCents);
} else if (discount.type === 'bogo') {
const buyQty = discount.minQuantity ?? 1;
for (const line of eligibleLines) {
const freeItems = Math.floor(line.quantity / (buyQty + discount.value)) * discount.value;
amountCents += freeItems * line.unitPriceCents;
}
}
if (amountCents > 0) {
applications.push({ discountId: discount.id, amountCents, affectedLineIds: eligibleLines.map(l => l.id) });
if (!discount.isStackable) nonStackableApplied = true;
}
}
return applications;
}Always re-run this evaluation at order creation, not just at cart-add time, to catch race conditions and expired discounts.
Best Practices
- Always calculate discounts server-side — never trust client-submitted discount amounts; recalculate at checkout
- Define stacking rules explicitly — decide upfront whether multiple discounts can combine and document the policy for your merchandising team
- Cap maximum discount amounts — for percentage discounts, set a maximum dollar cap to prevent runaway promotions
- Exclude sale items from additional promotions — prevent compounding discounts from destroying margins; Shopify and WooCommerce both support "exclude sale items" rules
- Test rules on staging before activating — particularly important for automatic discounts that apply to all eligible customers without requiring a code
- Log every discount application — record discount ID, customer, order, and amount for audit trails and promotion ROI reporting
- Set clear end dates — automatic discounts without an end date continue applying indefinitely; always set an expiry
Common Pitfalls
| Problem | Solution |
|---|---|
| Discount applied after order is placed (stale cart) | Re-validate all discounts at order creation; remove expired or invalid ones before charging |
| BOGO applied to a single-unit cart | Enforce a minimum cart quantity equal to the buy quantity before the BOGO triggers |
| Discount causes an order total to go negative | Cap the total discount at the cart subtotal; no order total should go below zero |
| Automatic and code-based discounts stack unexpectedly | Review the "Combinations" settings on each discount in Shopify; use "Individual use only" in WooCommerce coupons |
| Tiered discount shows wrong tier when quantity is updated | Recalculate the applicable tier on every cart quantity change, not just at checkout |
Related Skills
- @stripe-integration
- @checkout-flow-optimization
- @coupon-management
- @price-rules-engine
- @volume-pricing
{
"context": "Tests whether the agent implements the coupon redemption endpoint with the correct validation sequence, distinct HTTP status codes for each failure mode, server-side discount calculation, atomic usage tracking to prevent race conditions, case-insensitive code lookup, and re-validation of existing cart discounts.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Validation order",
"max_score": 10,
"description": "The handler checks validations in this order: (1) code existence, (2) expiry, (3) total usage limit, (4) per-customer usage limit, (5) cart condition check — later checks are NOT reached if an earlier one fails"
},
{
"name": "404 for missing code",
"max_score": 8,
"description": "Returns HTTP 404 when the discount code does not exist or is not active; test (a) asserts a 404 response"
},
{
"name": "410 for expired",
"max_score": 8,
"description": "Returns HTTP 410 when the discount's endsAt is in the past; test (b) asserts a 410 response for an expired code"
},
{
"name": "410 for usage limit",
"max_score": 8,
"description": "Returns HTTP 410 (not 403 or 400) when currentUses >= maxUses; test (c) asserts a 410 response"
},
{
"name": "403 for per-customer limit",
"max_score": 8,
"description": "Returns HTTP 403 when the customer has already used the discount maxUsesPerCustomer times; test (d) asserts a 403 response"
},
{
"name": "422 for unmet conditions",
"max_score": 8,
"description": "Returns HTTP 422 when the discount exists and is valid but the cart does not meet conditions (e.g., below minimum purchase); test (e) asserts a 422 response"
},
{
"name": "Case-insensitive lookup",
"max_score": 6,
"description": "The code is normalized (toUpperCase and trim, or equivalent) before querying the database; the handler or api-spec.md notes that codes are case-insensitive"
},
{
"name": "Server-side recalculation",
"max_score": 8,
"description": "The handler calls the discount evaluation function to compute the discount amount rather than accepting a client-provided amount; the discount total is derived from cart data"
},
{
"name": "Atomic usage increment",
"max_score": 10,
"description": "Usage count is incremented using a conditional atomic update (e.g., UPDATE ... SET current_uses = current_uses + 1 WHERE current_uses < max_uses or equivalent with a check) rather than a read-then-write pattern; code comments or api-spec.md mention race condition prevention"
},
{
"name": "Discount audit log",
"max_score": 8,
"description": "On successful application, the handler records a discount usage entry (or equivalent audit log entry) containing at minimum: discount ID, customer ID, and discount amount"
},
{
"name": "Re-validate existing discounts",
"max_score": 8,
"description": "When processing the new code, the handler re-evaluates or re-validates any discounts already on the cart (not only the newly submitted code), removing or flagging any that are now expired or invalid"
},
{
"name": "api-spec.md documents codes",
"max_score": 6,
"description": "api-spec.md lists all five possible error HTTP status codes (404, 410 ×2, 403, 422) with their distinct meanings, and documents the validation sequence"
},
{
"name": "Successful response shape",
"max_score": 4,
"description": "On success, the response includes the computed discount allocation details (at minimum: discountId/code, title, amount) and updated cart information"
}
]
}
Coupon Code Redemption API Endpoint
Problem/Feature Description
A B2C subscription platform is adding coupon code support to their checkout flow. Customer support has complained that the current checkout silently fails when an invalid code is entered, leaving customers confused about why their discount wasn't applied. Additionally, the growth team recently discovered that a promotional code intended for new customers was used repeatedly by existing customers, and that during a high-traffic sale, the same limited-use code was redeemed more times than allowed because of a race condition.
The team needs a well-structured REST API endpoint (POST /api/cart/discount) that validates and applies a coupon code entered by a customer. The endpoint must give customers clear, differentiated feedback for every failure mode, enforce usage limits safely, and be implemented so that discounts are always calculated server-side. It must also re-validate any previously applied discounts whenever a new code is submitted.
Output Specification
Produce the following files in your working directory:
1. apply-discount.ts — The Express (or framework-agnostic) request handler implementing the coupon redemption endpoint. Include any supporting functions (condition checking, usage counting, etc.) in the same file or clearly imported modules. 2. apply-discount.test.ts — Tests (using any framework or plain assertions) covering: (a) code not found, (b) expired code, (c) usage limit reached, (d) per-customer limit reached, (e) cart does not meet conditions, and (f) a successful application. 3. api-spec.md — Documents the endpoint contract: request shape, all possible response codes with their meanings, and the order in which validation steps are performed.
{
"context": "Tests whether the agent follows the skill's data model specification: correct TypeScript interface fields, integer monetary values, the automatic-vs-code-based distinction, case normalization, and the required PostgreSQL schema including indexes and a usage-tracking table.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Discount type union",
"max_score": 8,
"description": "The Discount type field uses exactly the union 'percentage' | 'fixed_amount' | 'bogo' | 'free_shipping' (all four, no others or extras)"
},
{
"name": "Target union",
"max_score": 6,
"description": "The Discount target field uses exactly the union 'order' | 'line_item' | 'shipping'"
},
{
"name": "allocationMethod field",
"max_score": 6,
"description": "The Discount interface includes an allocationMethod field with values 'across' | 'each'"
},
{
"name": "Monetary values in cents",
"max_score": 10,
"description": "Monetary fields (value for fixed amounts, minPurchaseAmount, maxDiscountAmount, unitPrice, etc.) are typed as integers/numbers in cents, NOT as floats or decimal strings; design-notes.md explicitly mentions storing money as integers/cents"
},
{
"name": "Nullable code for automatic",
"max_score": 8,
"description": "The code field on Discount is optional (code?) to represent automatic discounts; design-notes.md or comments distinguish automatic (no code) from coupon-code discounts"
},
{
"name": "Stacking flags",
"max_score": 8,
"description": "The Discount interface includes all three stacking boolean fields: combinesWithProductDiscounts, combinesWithOrderDiscounts, and combinesWithShippingDiscounts"
},
{
"name": "Usage limit fields",
"max_score": 6,
"description": "The Discount interface includes maxUses, currentUses, and maxUsesPerCustomer fields"
},
{
"name": "Conditions array",
"max_score": 8,
"description": "The Discount interface includes a conditions field typed as an array, with a DiscountCondition type that includes at minimum 'min_purchase', 'min_quantity', 'customer_tag', and 'first_order' condition types"
},
{
"name": "discounts table schema",
"max_score": 8,
"description": "schema.sql creates a discounts table with a UUID primary key, a nullable code column, monetary columns stored as INTEGER (not DECIMAL/FLOAT), and a conditions column using JSONB"
},
{
"name": "Case-insensitive code index",
"max_score": 8,
"description": "schema.sql includes a functional index on UPPER(code) for the discounts table (e.g., CREATE INDEX ... ON discounts(UPPER(code)))"
},
{
"name": "Active discounts index",
"max_score": 6,
"description": "schema.sql includes a composite index covering is_active, starts_at, and ends_at columns"
},
{
"name": "discount_usages table",
"max_score": 8,
"description": "schema.sql creates a discount_usages table with at minimum: a foreign key to discounts, a customer_id column, an order_id column, and an amount column in cents"
},
{
"name": "Scope fields present",
"max_score": 6,
"description": "The Discount interface includes appliesTo ('all' | 'specific_products' | 'specific_collections' | ...) plus entitledProductIds, entitledCollectionIds, and excludedProductIds fields"
},
{
"name": "Schedule fields",
"max_score": 4,
"description": "The Discount interface includes startsAt (required Date) and endsAt (optional Date) fields"
}
]
}
Promotions System: Data Model Design
Problem/Feature Description
A mid-size fashion retailer is expanding their e-commerce platform and needs a robust promotions module. Their merchandising team wants to run a variety of campaigns: percentage-off sales, flat-dollar coupons, buy-one-get-one offers on selected products, and free shipping thresholds. The engineering team has been asked to design the foundational data layer before any UI or business logic is built.
The data model must support both automatically applied promotions (such as a sitewide sale that activates during a flash sale window) and merchant-issued coupon codes that customers enter at checkout. It also needs to track how many times each discount has been used, and support per-customer limits to prevent abuse. The team plans to use PostgreSQL for persistence.
Output Specification
Produce the following files in your working directory:
1. discount-model.ts — TypeScript type definitions for the discount system, including the core discount entity and any supporting interfaces (e.g., for cart context, line items, and condition types). 2. schema.sql — PostgreSQL DDL to create the tables and indexes needed to persist discounts and track their usage. 3. design-notes.md — A brief document (bullet points are fine) explaining key decisions made in the data model, especially around monetary representation, code handling, and how automatic vs. coupon discounts are distinguished.
{
"context": "Tests whether the agent implements the discount evaluation pipeline correctly: the four-step order (filter → condition check → priority sort → stacking), the priority formula (automatic before code-based, specific scope before general), stacking flag enforcement, correct allocation math for each discount type, the maxDiscountAmount cap, and tiered pricing exclusivity.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Pipeline step order",
"max_score": 8,
"description": "The evaluateDiscounts function (or equivalent) filters to active/valid/non-expired/non-exhausted discounts BEFORE checking conditions, and sorts by priority BEFORE applying stacking logic"
},
{
"name": "Automatic-first priority",
"max_score": 8,
"description": "The priority function assigns higher priority to discounts without a code (automatic) over discounts with a code; tests confirm an automatic discount is applied before a coupon-code discount of equal value"
},
{
"name": "Specific-scope priority",
"max_score": 6,
"description": "The priority function gives higher priority to specific_products scope over specific_collections scope, and both over 'all'; this is reflected in the code or test assertions"
},
{
"name": "Stacking flag enforcement",
"max_score": 10,
"description": "A second line_item-targeted discount is skipped when combinesWithProductDiscounts is false; the test for competing line-item discounts verifies only one is applied"
},
{
"name": "Percentage allocation",
"max_score": 8,
"description": "Percentage discounts compute Math.round(lineTotal * value / 100) per eligible item"
},
{
"name": "Fixed-amount order spread",
"max_score": 8,
"description": "For fixed_amount with target='order', the discount is spread proportionally across eligible items by their lineTotal ratio, capped at eligibleTotal"
},
{
"name": "Fixed-amount per-item cap",
"max_score": 6,
"description": "For fixed_amount applied per item (not order-level), the per-unit discount is capped at unitPrice (Math.min(discount.value, item.unitPrice) * quantity)"
},
{
"name": "BOGO formula",
"max_score": 10,
"description": "BOGO discount computes free items as Math.floor(quantity / (buyQty + getQty)) * getQty, and the discount amount is freeItems * unitPrice"
},
{
"name": "maxDiscountAmount cap",
"max_score": 8,
"description": "After computing totalDiscount for any discount type, the code applies Math.min(totalDiscount, maxDiscountAmount) when maxDiscountAmount is set; a test or the README confirms this cap is active"
},
{
"name": "Tiered no-stacking",
"max_score": 10,
"description": "In the tiered bulk discount test, when a cart qualifies for multiple tiers (e.g., 10% at qty 3 and 15% at qty 5), only the highest qualifying tier is applied (not both); this is achieved via combinesWithProductDiscounts: false on the tier discounts"
},
{
"name": "Eligibility date filter",
"max_score": 8,
"description": "The pipeline filters out discounts where isActive is false, startsAt is in the future, or endsAt is in the past"
},
{
"name": "Usage-limit filter",
"max_score": 6,
"description": "The pipeline filters out discounts where currentUses >= maxUses (when maxUses is set)"
},
{
"name": "Free-shipping type",
"max_score": 4,
"description": "The free_shipping discount type sets the discount amount equal to the cart's shippingCost"
}
]
}
Cart Discount Engine Implementation
Problem/Feature Description
A growing online marketplace needs a discount engine that can apply multiple types of promotions to a shopping cart. The operations team regularly runs several promotions simultaneously — for example, a site-wide free shipping offer may be active at the same time as a product-specific bulk discount. The business rules around which promotions combine and which ones override each other are complex, and getting the math wrong has directly caused losses in the past (including one incident where a percentage-off coupon was applied to an already heavily discounted order with no cap).
The engineering team needs a TypeScript implementation of the core evaluation logic: given a list of discount rules and a cart snapshot, compute how much each discount should reduce the order. The implementation must handle the full range of discount types the business uses (percentage off, fixed dollar off, buy-N-get-M-free, and free shipping), and respect the configured combination rules so that promotions don't stack in unintended ways.
Output Specification
Produce the following files in your working directory:
1. discount-engine.ts — The TypeScript implementation containing the evaluation pipeline, condition checking, allocation calculation for all discount types, and the stacking/priority logic. Include type definitions for cart context, line items, and discount allocations in the same file or as imports. 2. discount-engine.test.ts — A test file (using any test framework of your choice, or plain assertions with console output) that exercises at minimum: (a) a tiered bulk discount scenario, (b) a BOGO scenario, (c) a fixed-amount order discount, and (d) a case where two competing line-item discounts are present and stacking must be resolved. 3. README.md — Brief notes describing how the priority and stacking system works.
{
"name": "finsi/discount-engine",
"version": "0.1.0",
"summary": "Rule-based discount system — percentage, fixed, BOGO, tiered, conditional",
"skills": {
"discount-engine": {
"path": "SKILL.md"
}
}
}