
Price Rules Engine
- 64 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Define stackable pricing rules with priority ordering, customer-segment targeting, product exclusions, and automatic discount-combination logic.
About
Builds a pricing rules engine with priority-ordered stackable rules, segment targeting, product exclusions, and discount-combination logic. A developer uses it to implement complex, conflict-free promotional pricing.
- Priority-ordered stackable rules with product exclusions
- Customer-segment targeting and discount-combination logic
Price Rules Engine by the numbers
- 64 all-time installs (skills.sh)
- Ranked #3,123 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 price-rules-engineAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 64 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Define stackable pricing rules with priority ordering, customer-segment targeting, product exclusions, and automatic discount-combination logic.
Files
Price Rules Engine
Overview
A price rules engine lets you define multiple concurrent promotions — site-wide sales, coupon codes, loyalty discounts — and apply them to a cart in a predictable, controlled order. The key concerns are: which rules apply to which products, which rules can stack with others, and what happens when multiple rules target the same item. Every major platform has some form of this built in; the gap is usually in advanced stacking control and customer-segment targeting.
When to Use This Skill
- When you have multiple concurrent promotions (site-wide sale + coupon + loyalty discount) and need deterministic stacking behavior
- When marketing needs to create complex promotions (e.g., "20% off all shoes except Nike, for Gold loyalty members") without engineering involvement
- When migrating from hardcoded promotional logic scattered across the codebase to a data-driven rule system
- When building a promotion scheduler that activates and deactivates rules at configured times
- When you need an audit log that shows exactly which rules were applied and why for customer service queries
Core Instructions
Step 1: Determine the merchant's platform and choose the right tool
| Platform | Built-in Capability | When to Extend |
|---|---|---|
| Shopify | Discounts admin handles basic stacking via "Combinations" settings | Use Bold Discounts or Shopify Scripts (Plus) for advanced rule stacking, customer-segment targeting, and conditional logic |
| WooCommerce | Coupons core + Dynamic Pricing plugin for conditional rules | For complex priority ordering and segment targeting: YITH WooCommerce Dynamic Pricing & Discounts provides the most control |
| BigCommerce | Promotions engine supports multiple concurrent promotions with priority ordering and stacking rules | BigCommerce's built-in system handles most scenarios natively |
| Custom / Headless | Must build | Required when none of the above fits your use case |
Step 2: Configure price rules with stacking on your platform
---
Shopify
Shopify manages discount stacking through the Combinations setting on each discount.
Setting up stacking rules: 1. Go to Discounts → create or edit a discount 2. Scroll to the Combinations section 3. Toggle which types this discount can combine with:
- Product discounts — can this stack with other product-level discounts?
- Order discounts — can this stack with order-level discounts?
- Shipping discounts — can this stack with free-shipping discounts?
4. For automatic discounts, the highest-value automatic discount takes precedence by default; use Combinations to allow stacking
Customer-segment targeting: 1. Under Customer eligibility, choose Specific customer segments 2. Select segments created in Customers → Segments (e.g., "VIP customers", "First-time buyers")
Priority and exclusions:
- Set Start and end dates to control which rules are active
- Under Products, set which products or collections the discount applies to
- Add exclusions: "Exclude sale items" or specify products that are excluded
Shopify Plus — Shopify Scripts: For rules that cannot be expressed through the Discounts UI (e.g., tiered stacking where the second discount only applies if the cart is above a threshold): 1. Go to Apps → Script Editor (Scripts is a separate Shopify Plus feature) 2. Create a Line Item Script or Shipping Script 3. Scripts run at checkout and can apply complex conditional discounts; they take precedence over other discounts
Bold Discounts (App Store, ~$20/month): A visual rule builder for Shopify that supports:
- Stack / don't stack per promotion
- Priority ordering between promotions
- Complex conditions (customer tags, collection membership, quantity thresholds)
---
WooCommerce
WooCommerce coupons support basic single-rule discounts. For a full price rules engine with priority ordering and stacking control, use the Dynamic Pricing plugin.
Installing and configuring YITH WooCommerce Dynamic Pricing & Discounts: 1. Install the plugin from YITH.com (~$70/year) 2. Go to YITH → Dynamic Pricing → Pricing Rules 3. Create a rule and configure:
- Type: cart, product, or category pricing
- Discount: percentage or fixed amount
- Conditions: cart subtotal, quantity, customer role, date range
- Products/categories: which items the rule applies to; set exclusions
- Priority: lower number = higher priority (applied first)
- Stacking: "Stop other rules" to prevent lower-priority rules from stacking
Customer segment targeting in WooCommerce: 1. Use WooCommerce Customer Roles (available via plugins like User Role Editor or WooCommerce B2B):
- Assign customers to roles like "wholesale", "vip", "trade"
2. In Dynamic Pricing rules, restrict each rule to specific customer roles 3. Customers in that role see the discounted price; others see the regular price
Example: VIP-only 20% off apparel, excludes clearance: 1. Create a rule: Type = Category pricing, Category = Apparel 2. Discount = 20% off 3. Customer Role = VIP 4. Excluded products: [list of clearance product IDs] 5. Priority = 10
---
BigCommerce
BigCommerce's Promotions engine natively supports priority ordering and stacking control.
1. Go to Marketing → Promotions → Create Promotion 2. Under Conditions:
- Set cart value, quantity, or product conditions
- Under Customer groups: restrict to specific groups (wholesale, VIP, etc.)
3. Under Actions: set the discount type and amount 4. Set Shipping conditions if applicable 5. Under Rules:
- Can be combined with other promotions: yes/no
- Priority: lower number runs first
6. Set Active date range
BigCommerce evaluates promotions in priority order and respects the "can be combined" setting. Multiple non-combinable promotions will apply only the best-value one for the customer.
---
Custom / Headless
For custom storefronts, implement a rule evaluator that processes rules in priority order, enforces stacking constraints, and applies rules to eligible cart lines:
interface PriceRule {
id: string;
name: string;
type: 'percentage_off' | 'fixed_off' | 'free_shipping' | 'buy_x_get_y';
value: number; // percentage or cents
priority: number; // higher = applied first
isStackable: boolean;
couponCode?: string; // null = automatic (no code required)
minCartCents?: number;
customerSegments?: string[];
applicableProducts?: string[];
applicableCategories?: string[];
excludedProducts?: string[];
startsAt: Date;
endsAt?: Date;
}
interface CartContext {
lines: { lineId: string; productId: string; categoryIds: string[]; quantity: number; currentPriceCents: number }[];
subtotalCents: number;
customerSegments: string[];
appliedCouponCode?: string;
}
function evaluateRules(cart: CartContext, rules: PriceRule[]): { ruleId: string; discountCents: number }[] {
const now = new Date();
const active = rules.filter(r =>
r.startsAt <= now && (!r.endsAt || r.endsAt > now)
).sort((a, b) => b.priority - a.priority); // highest priority first
const applications: { ruleId: string; discountCents: number }[] = [];
let nonStackableApplied = false;
for (const rule of active) {
if (!rule.isStackable && nonStackableApplied) continue;
// Coupon-linked rules require the code to be applied
if (rule.couponCode && rule.couponCode !== cart.appliedCouponCode) continue;
// Cart minimum check
if (rule.minCartCents && cart.subtotalCents < rule.minCartCents) continue;
// Customer segment check
if (rule.customerSegments?.length && !rule.customerSegments.some(s => cart.customerSegments.includes(s))) continue;
// Find eligible lines
const eligibleLines = cart.lines.filter(line => {
if (rule.excludedProducts?.includes(line.productId)) return false;
if (rule.applicableProducts?.length) return rule.applicableProducts.includes(line.productId);
if (rule.applicableCategories?.length) return rule.applicableCategories.some(c => line.categoryIds.includes(c));
return true; // no scope restriction = all products
});
if (eligibleLines.length === 0) continue;
let discountCents = 0;
if (rule.type === 'percentage_off') {
discountCents = Math.round(
eligibleLines.reduce((s, l) => s + l.currentPriceCents * l.quantity, 0) * rule.value / 100
);
} else if (rule.type === 'fixed_off') {
discountCents = Math.min(rule.value, cart.subtotalCents);
}
if (discountCents > 0) {
applications.push({ ruleId: rule.id, discountCents });
if (!rule.isStackable) nonStackableApplied = true;
}
}
return applications;
}Persist rule applications with every order so you can answer customer service questions ("which discount applied?") and track promotion ROI.
Best Practices
- Higher priority = evaluated first — use an explicit
priorityinteger so marketing can control evaluation order without code changes - Separate stackable from exclusive rules — once a non-stackable rule applies, skip all subsequent non-stackable rules; stackable rules always apply on top
- Test rules in "dry run" mode before activating — review the discount calculation on a sample cart before the promotion goes live
- Use exclusion lists generously — always allow marketing to specify excluded products/categories; unexpected application to premium or already-reduced items creates margin problems
- Version rules rather than editing live rules — deactivate old rules and create new versions; this preserves historical calculation for past orders
- Log which rules were applied to each order — store rule IDs and discount amounts on the order for customer service and ROI analysis
Common Pitfalls
| Problem | Solution |
|---|---|
| Two non-stackable rules both apply | Sort by priority, apply the highest-priority non-stackable first, then skip all other non-stackable rules |
| A rule applies to an excluded product | Always check exclusions before inclusions; exclusion takes precedence in all cases |
| Total discount causes order to go negative | Cap total discount at cart subtotal; no order total should go below zero |
| Marketing edits a live rule mid-campaign | Treat active rules as immutable — create a new rule and deactivate the old one; never edit live rules |
| Rule activates/deactivates a few seconds off schedule | Set start/end times conservatively (a few minutes before/after intended time) and verify in staging; for critical timing, use Launchpad (Shopify Plus) or a scheduled job |
Related Skills
- @coupon-management
- @discount-engine
- @dynamic-pricing
- @ab-testing-pricing
- @volume-pricing
{
"context": "Tests whether the agent correctly implements order-placement persistence (inserting rule applications and incrementing usage_count within the same transaction), the buy_x_get_y discount applied to cheapest items first, fixed_off applied at cart level rather than per line, and the rule versioning pattern (deactivate old, create new) when modifying a live promotion.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Applications inserted with order_id",
"max_score": 8,
"description": "The persistence function inserts a record into the rule-applications table that includes the order_id linking it to the specific order"
},
{
"name": "Applications inserted with rule_id",
"max_score": 8,
"description": "Each inserted application record includes the rule_id identifying which rule generated the discount"
},
{
"name": "discount_amount in cents",
"max_score": 8,
"description": "The discount_amount stored in the applications table is an integer representing cents (not a decimal dollar amount)"
},
{
"name": "usage_count incremented atomically",
"max_score": 10,
"description": "The usage_count increment on price_rules is performed in the same database transaction as the application insert (not in a separate query outside the transaction)"
},
{
"name": "B2G1 cheapest items first",
"max_score": 12,
"description": "For buy_x_get_y, the free-item discount is applied to the cheapest eligible units (prices sorted ascending, free units taken from the front of the sorted list)"
},
{
"name": "Free unit count formula",
"max_score": 10,
"description": "The number of free units in buy_x_get_y is calculated as floor(totalUnits / (BUY_X + GET_Y)) * GET_Y"
},
{
"name": "fixed_off at cart level",
"max_score": 10,
"description": "A fixed_off rule calculates its discount once against the cart subtotal, not by summing a per-line calculation"
},
{
"name": "fixed_off capped at subtotal",
"max_score": 8,
"description": "The fixed_off discount is capped at the cart subtotal so it cannot produce a negative total"
},
{
"name": "Old rule deactivated on update",
"max_score": 10,
"description": "When a live rule is changed, the existing rule record is set to inactive (is_active = false) rather than mutated in place"
},
{
"name": "New rule version created",
"max_score": 10,
"description": "A new price_rules record is created with the updated values rather than overwriting the old rule"
},
{
"name": "Historical order references preserved",
"max_score": 6,
"description": "The old (deactivated) rule record is retained so that historical order applications still reference a valid rule_id"
}
]
}
Promotion Tracking and Safe Rule Updates
Problem/Feature Description
GearUp, a sporting goods retailer, has been running a pricing rule engine for two months and has surfaced two critical problems. First, customer service agents cannot look up exactly which promotions were applied to a specific past order — they only see the final discounted total, making refund calculations and dispute resolution very difficult. The team needs a function that, when an order is placed, records every applied discount against the order for future auditing.
Second, there are correctness questions about the "Accessories Buy 2 Get 1 Free" promotion: customers have been complaining the wrong item is being made free, and the engineering team needs to revisit that calculation. On top of that, the CMO wants to change the minimum cart value on their site-wide "Summer Flat $15 Off" promotion from $75 to $100 because margins are being squeezed — the team needs a workflow that changes the rule without corrupting the audit trail for the 3,400 orders already placed under the old terms.
Your task is to address all three gaps: 1. Implement the order-placement logic that persists discount records 2. Implement correct discount calculations for the flat-off and buy-X-get-Y rule types 3. Demonstrate a safe workflow for updating the live Summer sale rule
Output Specification
Produce a solution/ directory containing:
persist.ts(or.js) — the function that records rule applications when an order is placedapply-rules.ts(or.js) — implementations of thefixed_offandbuy_x_get_ydiscount calculationsrule-update.ts(or.js) — a script or function demonstrating how to safely update the live Summer sale ruletests/— test files providing good coverage of the persistence, discount calculation, and rule update logic- A
README.mdexplaining the design decisions
Input Files
The following rules and orders are provided as starting context. Extract them before beginning.
=============== FILE: inputs/existing-rules.json =============== { "rules": [ { "id": "rule-b2g1-accessories", "name": "Accessories Buy 2 Get 1 Free", "type": "buy_x_get_y", "value": null, "priority": 20, "is_stackable": true, "applicable_categories": ["cat-accessories"], "is_active": true, "usage_limit": null, "usage_count": 847 }, { "id": "rule-summer-flat15", "name": "Summer Flat $15 Off", "type": "fixed_off", "value": 1500, "priority": 10, "is_stackable": false, "min_cart_value": 7500, "is_active": true, "usage_limit": 5000, "usage_count": 3400 } ] }
{
"context": "Tests whether the agent implements the rule evaluation loop correctly: sorting by priority descending, handling non-stackable exclusion via a flag, requiring coupon codes for coupon-linked rules, applying discounts to the running (already-discounted) price, capping discounts at cart subtotal, respecting usage limits, filtering by schedule, and supporting a dry-run mode.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Priority descending sort",
"max_score": 12,
"description": "Rules are fetched or sorted by priority in descending order (higher priority rules are processed first)"
},
{
"name": "Non-stackable flag introduced",
"max_score": 10,
"description": "A boolean flag (or equivalent) is initialised to false before the evaluation loop and set to true after the first non-stackable rule is applied"
},
{
"name": "Non-stackable rule skipped",
"max_score": 10,
"description": "A non-stackable rule is skipped (not applied) when another non-stackable rule has already been applied in the same evaluation pass"
},
{
"name": "Stackable rules unaffected",
"max_score": 8,
"description": "Stackable rules continue to be applied even after a non-stackable rule has been applied"
},
{
"name": "Coupon code check",
"max_score": 10,
"description": "A rule with a coupon_code is skipped when the cart does NOT include that coupon code; it is applied when the cart DOES include the matching code"
},
{
"name": "Discount from currentPrice",
"max_score": 10,
"description": "Percentage or line-level discounts are calculated from the line's currentPrice (not basePrice / original price)"
},
{
"name": "Discount cap at subtotal",
"max_score": 8,
"description": "Total discount is capped so it cannot exceed the cart subtotal (order total cannot go negative)"
},
{
"name": "Usage limit respected",
"max_score": 8,
"description": "A rule whose usage_count has reached usage_limit is skipped during evaluation"
},
{
"name": "Schedule filter applied",
"max_score": 8,
"description": "Only rules where is_active is true, starts_at <= now, and ends_at is null or > now are considered"
},
{
"name": "Monetary values in cents",
"max_score": 8,
"description": "All monetary amounts (cartSubtotal, discountAmount, prices) are represented as integers (cents), not floating-point dollars"
},
{
"name": "Dry-run / evaluate-only mode",
"max_score": 8,
"description": "The implementation includes an evaluate-only or dry-run flag/option that returns the evaluation result without persisting any applications"
}
]
}
Checkout Promotions Evaluator
Problem/Feature Description
Trendy Threads is a mid-size online fashion retailer that runs several promotions at any given time: a site-wide seasonal sale, a loyalty members-only discount, and periodic flash coupon codes distributed via email. Until now each of these was a separate if-statement scattered across the checkout service, which made it impossible to predict or debug which discounts would fire for a given cart.
The engineering team wants a single TypeScript function, evaluateRules, that accepts a cart context and a list of active promotion rules, and returns an array of applied promotion results. The function must handle the full complexity of concurrent promotions: conflicting exclusive deals, coupon-code-gated offers, rules that have run their course, time-limited campaigns, and the need to layer additional discounts on top of an already-reduced price. Marketing has also asked for a way to preview what discounts would apply to a cart without actually recording anything, so they can test new rules before launching.
Output Specification
Implement the evaluateRules function in TypeScript (or JavaScript). Produce a solution/ directory containing:
evaluator.ts(or.js) — the core evaluation function with full inline comments explaining key decisionsevaluator.test.ts(or.test.js) — test cases that achieve good coverage of the evaluation logic across a variety of cart and rule configurations- A
README.mdbriefly describing the approach and any notable design choices
All monetary values in the tests should be expressed as integers (e.g. a $29.99 item should appear as 2999).
{
"context": "Tests whether the agent defines the correct SQL schema for price rules (correct column types, valid rule type constraints, audit table) and implements eligible line filtering that respects exclusions over inclusions, NULL-means-all semantics, customer segment gating, and correct monetary comparisons in cents.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Rule type constraint",
"max_score": 10,
"description": "The price_rules table includes a CHECK constraint (or equivalent validation) limiting type to: 'percentage_off', 'fixed_off', 'free_shipping', 'buy_x_get_y', 'fixed_price'"
},
{
"name": "Priority column present",
"max_score": 6,
"description": "The schema includes a priority column (INTEGER type) on the price_rules table"
},
{
"name": "is_stackable column present",
"max_score": 6,
"description": "The schema includes an is_stackable BOOLEAN column on the price_rules table"
},
{
"name": "Monetary amounts as integers",
"max_score": 8,
"description": "min_cart_value and discount_amount are defined as INTEGER (not DECIMAL/FLOAT/NUMERIC with scale), representing cents"
},
{
"name": "Audit table created",
"max_score": 10,
"description": "A separate price_rule_applications (or equivalent) table is created with at minimum order_id, rule_id, and discount_amount columns"
},
{
"name": "coupon_code nullable",
"max_score": 6,
"description": "coupon_code column is nullable (NULL = automatic, no code required)"
},
{
"name": "Exclusions checked first",
"max_score": 10,
"description": "In the eligibility filter, excluded_products and excluded_categories are evaluated BEFORE applicable_products and applicable_categories"
},
{
"name": "Exclusion wins over inclusion",
"max_score": 10,
"description": "A product that matches both an exclusion list and an applicable list is treated as ineligible (excluded)"
},
{
"name": "NULL scope means all products",
"max_score": 8,
"description": "When applicable_products and applicable_categories are both null/empty, all products are treated as eligible (no inclusion restriction)"
},
{
"name": "Customer segment gating",
"max_score": 8,
"description": "A rule with a non-empty customer_segments list only applies when the cart's customer segments include at least one matching segment"
},
{
"name": "Min cart value in cents",
"max_score": 8,
"description": "The min_cart_value condition compares against the cart subtotal in the same integer cents unit (not converted to dollars)"
},
{
"name": "NULL customer_segments means all",
"max_score": 10,
"description": "When customer_segments is null or empty on a rule, the rule applies to all customers regardless of their segment"
}
]
}
Promotions Infrastructure for a Fashion Retailer
Problem/Feature Description
StyleHub is a fashion e-commerce platform preparing for their biggest promotional calendar of the year: a summer sale that applies to all apparel except their new premium collection, a loyalty-tier discount exclusively for "Gold" and "Platinum" members, a site-wide free-shipping threshold for any order over a certain value, and category-specific deals on accessories with no customer restrictions.
The backend team has been asked to create the database schema and the eligibility filtering logic that determines which cart lines a given rule applies to. The marketing team is particularly anxious about two past incidents: a premium capsule collection accidentally got discounted during last year's sale, and a loyalty reward once applied to a guest checkout. The schema must make it straightforward to prevent both issues. Marketing also wants the flexibility to create rules that apply automatically (no code required) as well as rules gated behind a coupon code.
Output Specification
Produce a solution/ directory containing:
schema.sql— DDL statements creating the price rules table (and any supporting tables needed for auditing applied discounts)eligibility.ts(or.js) — agetEligibleLinesfunction (and any helpers) that filters a list of cart lines based on a rule's inclusion/exclusion configurationeligibility.test.ts(or.test.js) — test cases covering a variety of rule configurations and cart compositions drawn from the sample data below, including targeting edge cases- A brief
README.mddescribing design decisions
Input Files
The following sample data can be used as fixtures in your tests. Extract them before beginning.
=============== FILE: inputs/sample-categories.json =============== { "categories": [ { "id": "cat-apparel", "name": "Apparel" }, { "id": "cat-accessories", "name": "Accessories" }, { "id": "cat-premium", "name": "Premium Collection" } ], "products": [ { "id": "prod-shirt-001", "name": "Classic T-Shirt", "categoryIds": ["cat-apparel"] }, { "id": "prod-premium-jacket", "name": "Designer Jacket", "categoryIds": ["cat-apparel", "cat-premium"] }, { "id": "prod-belt-001", "name": "Leather Belt", "categoryIds": ["cat-accessories"] } ] }
{
"name": "finsi/price-rules-engine",
"version": "0.1.0",
"summary": "Stackable pricing rules with priority, exclusions, and customer segment targeting",
"skills": {
"price-rules-engine": {
"path": "SKILL.md"
}
}
}