
Coupon Management
- 93 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Set up a coupon system with percentage and fixed discounts, usage limits per customer, expiration dates, and bulk unique-code generation.
About
A skill for configuring ecommerce coupons with percentage/fixed discounts, usage limits, expirations, and bulk codes using built-in platform systems. A developer uses it to run promo codes without building a coupon engine from scratch.
- Built-in coupon setup per platform, minimal custom code
- Apps for advanced needs like bulk unique codes and campaign tracking
Coupon Management by the numbers
- 93 all-time installs (skills.sh)
- Ranked #1,156 of 1,879 Marketing & SEO 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 coupon-managementAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 93 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Set up a coupon system with percentage and fixed discounts, usage limits per customer, expiration dates, and bulk unique-code generation.
Files
Coupon Management
Overview
Coupon systems let merchants create promotional codes with configurable rules: percentage or fixed-amount discounts, minimum order requirements, usage limits per coupon or per customer, and expiration dates. Every major e-commerce platform includes a built-in coupon system — you almost never need to build one from scratch. This skill walks you through setting up coupon management on each platform and explains when to reach for an app or plugin for advanced requirements like bulk unique codes or campaign tracking.
When to Use This Skill
- When adding promotional codes to a checkout flow for the first time
- When migrating from a simple discount field to a rule-based coupon engine
- When running marketing campaigns that require unique, single-use codes for each recipient
- When building an admin interface to create and monitor coupon performance
- When enforcing complex coupon rules such as product/category exclusions or customer segment restrictions
Core Instructions
Step 1: Determine the merchant's platform and the right tool
| Platform | Built-in Capability | When to Add an App/Plugin |
|---|---|---|
| Shopify | Shopify Discounts — supports percentage, fixed amount, free shipping, BOGO; usage limits, expiry, minimum purchase | When you need bulk unique codes (Shopify supports import), customer-group-specific coupons, or loyalty integration (Smile.io, LoyaltyLion) |
| WooCommerce | WooCommerce Coupons — built into core; percentage, fixed cart, fixed product, free shipping types | When you need bulk unique code generation: WooCommerce Smart Coupons plugin; advanced rules: YITH WooCommerce Dynamic Pricing & Discounts |
| BigCommerce | Coupon codes built in — percentage, fixed amount, free shipping, free product types | When you need B2B-specific codes or advanced restrictions; BigCommerce app marketplace has options like Coupon Manager Pro |
| Custom / Headless | Must build — see Custom section below | N/A — you are building the system |
Step 2: Set up standard coupons on your platform
---
Shopify
1. Go to Discounts in the Shopify admin sidebar 2. Click Create discount and choose the type:
- Amount off products — percentage or fixed amount off specific products/collections
- Amount off order — percentage or fixed amount off the entire cart
- Buy X get Y — BOGO and bundle offers
- Free shipping — removes shipping cost when code is applied
3. Configure the code:
- Enter a code (e.g.,
SUMMER20) or click Generate code for a random one - Set Minimum purchase requirements (minimum subtotal or minimum quantity)
- Set Customer eligibility — all customers, specific customer segments, or specific customers
- Set Maximum discount uses — total uses and/or one use per customer
- Set Active dates — start and optional end date
4. Click Save discount
Bulk unique codes on Shopify: 1. In the same Discounts screen, choose Generate codes instead of entering a single code 2. Set the quantity (up to 100 at a time from the UI; use the Shopify Admin API for larger volumes) 3. All generated codes share the same rules (discount value, expiry, usage limits) 4. Export the codes to CSV for use in your email marketing platform
Shopify Plus — Shopify Scripts for advanced stacking:
- Use Shopify Scripts (Shopify Plus only) for custom coupon logic: e.g., different discount percentages by customer tag, auto-apply coupons without a code
- Access via Apps → Script Editor in your Shopify admin
---
WooCommerce
1. Go to WooCommerce → Coupons → Add coupon 2. Set the Coupon code (unique identifier customers type at checkout) 3. Under General tab:
- Discount type: Percentage discount, Fixed cart discount, Fixed product discount
- Coupon amount: The discount value
- Free shipping: Toggle to make this code grant free shipping
- Coupon expiry date: Date after which the code stops working
4. Under Usage restriction tab:
- Minimum spend: Cart subtotal must exceed this amount
- Maximum spend: Cart subtotal cannot exceed this amount
- Individual use only: Cannot be combined with other coupons
- Exclude sale items: Don't apply to already-reduced items
- Products and Exclude products: Restrict or exclude specific products
- Product categories and Exclude categories: Restrict or exclude by category
- Email restrictions: Limit to specific customer emails
5. Under Usage limits tab:
- Usage limit per coupon: Total number of times this code can be used
- Usage limit per user: How many times a single customer can use it
6. Click Publish
Bulk unique codes on WooCommerce:
- Install WooCommerce Smart Coupons (premium plugin, ~$99/year from StoreApps)
- Go to WooCommerce → Smart Coupons → Generate Coupons
- Set quantity, discount amount, expiry, and prefix — generates a CSV of unique codes
- Import or distribute via your email platform
---
BigCommerce
1. Go to Marketing → Coupon Codes → Create Coupon Code 2. Fill in:
- Code: The code customers enter (or use the auto-generate button)
- Type: Percentage off order, Dollar amount off order, Percentage off product, Dollar amount off product, Free shipping, Free product
- Discount amount: The value
- Applies to: All items, items from specific categories, or specific products
3. Under Restrictions:
- Minimum order: Subtotal must exceed this amount
- Max uses: Total redemptions allowed
- Max uses per customer: Per-account limit
- Expiration date: When the code stops working
4. Click Save
Bulk codes on BigCommerce: Use the BigCommerce Promotions API (POST /v2/coupons) to generate codes programmatically, then export for distribution.
---
Custom / Headless
For headless stores, you need to build the validation and redemption logic. The key requirements are atomic redemption (prevent race conditions when two customers use the last available redemption simultaneously) and idempotent order processing.
// Coupon validation at checkout
async function validateCoupon(
code: string,
customerId: string,
orderSubtotalCents: number
): Promise<{ valid: boolean; discountCents: number; error?: string }> {
const coupon = await db.coupons.findOne({ code: code.toUpperCase().trim(), is_active: true });
if (!coupon) return { valid: false, discountCents: 0, error: 'Code not found' };
const now = new Date();
if (coupon.expires_at && coupon.expires_at < now) return { valid: false, discountCents: 0, error: 'Code expired' };
if (coupon.usage_limit && coupon.usage_count >= coupon.usage_limit) return { valid: false, discountCents: 0, error: 'Code fully used' };
if (coupon.min_order_cents && orderSubtotalCents < coupon.min_order_cents) return { valid: false, discountCents: 0, error: `Minimum order $${coupon.min_order_cents / 100}` };
// Per-customer limit check
if (coupon.per_customer_limit) {
const uses = await db.couponRedemptions.count({ coupon_id: coupon.id, customer_id: customerId });
if (uses >= coupon.per_customer_limit) return { valid: false, discountCents: 0, error: 'Already used' };
}
const discountCents = coupon.type === 'percentage'
? Math.round(orderSubtotalCents * (coupon.value / 100))
: Math.min(coupon.value_cents, orderSubtotalCents);
return { valid: true, discountCents };
}
// Atomic redemption — use inside the order creation transaction
async function redeemCoupon(tx: Tx, couponId: string, customerId: string, orderId: string, discountCents: number) {
// Atomic increment with guard — prevents over-redemption under concurrency
const result = await tx.raw(
`UPDATE coupons SET usage_count = usage_count + 1
WHERE id = ? AND (usage_limit IS NULL OR usage_count < usage_limit)
RETURNING id`,
[couponId]
);
if (result.rowCount === 0) throw new Error('COUPON_EXHAUSTED');
await tx.couponRedemptions.insert({ coupon_id: couponId, customer_id: customerId, order_id: orderId, discount_cents: discountCents });
}Bulk code generation for email campaigns:
import crypto from 'crypto';
function generateCode(prefix = 'PROMO', length = 8): string {
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // no ambiguous chars
return prefix + '-' + Array.from(crypto.randomBytes(length))
.map(b => chars[b % chars.length]).join('');
}
async function bulkGenerate(template: CouponTemplate, quantity: number): Promise<string[]> {
const codes: string[] = [];
while (codes.length < quantity) {
const batch = Array.from({ length: Math.min(500, quantity - codes.length) }, () => generateCode(template.prefix));
const inserted = await db.coupons.insertMany(
batch.map(code => ({ ...template, code, usage_limit: 1, per_customer_limit: 1 })),
{ onConflict: 'ignore' }
);
codes.push(...inserted.map(r => r.code));
}
return codes;
}Best Practices
- Normalize codes to uppercase — store and compare codes in uppercase and trim whitespace to prevent "code not found" errors from minor formatting differences
- Use single-use codes for targeted campaigns — set usage limit to 1 per code when distributing unique codes via email to prevent sharing
- Validate at order creation, not just at cart — re-check coupon validity (expiry, usage limits) when the order is actually placed to handle race conditions
- Soft-delete coupons — deactivate rather than delete to preserve redemption history for accounting
- Track discount abuse — if a customer has abandoned and recovered with a discount code three or more times, consider excluding them from discount campaigns
- Cap maximum discount amounts — for percentage coupons, set a maximum dollar discount to prevent runaway promotions (e.g., 20% off capped at $50)
Common Pitfalls
| Problem | Solution |
|---|---|
| Two customers redeem the last use simultaneously | Use atomic UPDATE ... WHERE usage_count < usage_limit and verify rowCount === 1 (custom builds); platforms handle this natively |
| Coupon still valid after order cancellation | Decrement the usage count when an order is cancelled or refunded; Shopify does this automatically |
| Per-customer limit bypassed with multiple accounts | Supplement customer-ID checks with email checks; for high-value campaigns, require verified phone numbers |
| Bulk-generated codes collide with existing ones | Use INSERT ... ON CONFLICT DO NOTHING and regenerate collisions until target quantity is met |
| Free-shipping coupon stacks with a percentage discount unexpectedly | Define your stacking policy explicitly; on Shopify, use the "Can be combined with" settings on each discount |
Related Skills
- @discount-engine
- @price-rules-engine
- @ab-testing-pricing
- @loyalty-points-system
- @checkout-flow-optimization
{
"context": "Tests whether the agent uses crypto.randomBytes with the unambiguous character set for code generation, handles bulk collisions via ON CONFLICT, sets correct single-use limits, and implements atomic optimistic-locking redemption as specified in the coupon-management skill.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Uses crypto.randomBytes",
"max_score": 10,
"description": "generateCouponCode uses Node's built-in crypto.randomBytes (not Math.random or uuid) to generate the random portion"
},
{
"name": "Unambiguous character set",
"max_score": 10,
"description": "The character set used for code generation omits ambiguous characters: does NOT include '0', 'O', '1', or 'I'"
},
{
"name": "Code format: prefix-random",
"max_score": 8,
"description": "Generated codes follow the format '${prefix}-${randomChars}' (a prefix, a hyphen, then the random segment)"
},
{
"name": "Batch size 500",
"max_score": 8,
"description": "bulkGenerateCoupons processes codes in batches — batch size is 500 (or a constant named to that effect)"
},
{
"name": "Conflict-safe insert",
"max_score": 10,
"description": "bulkGenerateCoupons uses an insert strategy equivalent to ON CONFLICT DO NOTHING / onConflict: 'ignore' to silently skip collisions"
},
{
"name": "Generates until target quantity",
"max_score": 8,
"description": "bulkGenerateCoupons continues looping until the actual number of successfully inserted codes reaches the requested quantity"
},
{
"name": "usage_limit: 1 on bulk codes",
"max_score": 8,
"description": "Each bulk-generated code is inserted with usage_limit set to 1"
},
{
"name": "per_customer_limit: 1 on bulk codes",
"max_score": 8,
"description": "Each bulk-generated code is inserted with per_customer_limit set to 1"
},
{
"name": "Atomic UPDATE with WHERE guard",
"max_score": 10,
"description": "redeemCoupon uses an UPDATE ... SET usage_count = usage_count + 1 WHERE ... AND (usage_limit IS NULL OR usage_count < usage_limit) pattern"
},
{
"name": "rowCount race-condition check",
"max_score": 10,
"description": "redeemCoupon checks the UPDATE row count and throws an error (e.g. COUPON_EXHAUSTED) when zero rows were updated"
},
{
"name": "Transaction parameter",
"max_score": 10,
"description": "redeemCoupon accepts a transaction object/parameter rather than opening its own connection, enabling it to run inside the order creation transaction"
}
]
}
Email Campaign Code Generator
Problem/Feature Description
The FinSi marketing team is running a win-back campaign targeting 5,000 lapsed customers. Each recipient needs a unique, one-time-use discount code worth $10 off their next order — codes will be distributed through an email marketing platform and must not be reusable or shareable between customers. The codes should look professional and be easy to read aloud or type manually, since some customers interact with support to redeem them.
The engineering team also needs the order-processing service to safely redeem a code when an order is placed, even under high concurrency during flash sales where many customers may attempt to use the last available code simultaneously.
Output Specification
Produce a TypeScript file campaign-generator.ts that exports:
- A
generateCouponCodefunction for creating a single code - A
bulkGenerateCouponsfunction for generating a large batch
Produce a second TypeScript file redeem-coupon.ts that exports:
- A
redeemCouponfunction that safely marks a code as used within an order transaction
Both files should include inline comments. You may stub out the database layer. Also produce a short README.md explaining the collision-handling strategy and how concurrent redemption is made safe.
{
"context": "Tests whether the agent designs the coupon schema with the exact column names, data types, constraints, and indexes specified in the coupon-management skill — including both the coupons and coupon_redemptions tables.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Coupons table exists",
"max_score": 5,
"description": "A CREATE TABLE statement for a table named exactly 'coupons' is present"
},
{
"name": "Redemptions table exists",
"max_score": 5,
"description": "A CREATE TABLE statement for a table named exactly 'coupon_redemptions' is present"
},
{
"name": "UUID primary key with gen_random_uuid",
"max_score": 8,
"description": "The coupons table id column is UUID PRIMARY KEY with DEFAULT gen_random_uuid()"
},
{
"name": "Code column type and constraint",
"max_score": 8,
"description": "The code column is VARCHAR(64) NOT NULL UNIQUE"
},
{
"name": "Type CHECK constraint",
"max_score": 10,
"description": "The type column has a CHECK constraint that restricts values to exactly ('percentage', 'fixed_amount', 'free_shipping')"
},
{
"name": "Numeric precision for money columns",
"max_score": 8,
"description": "value, min_order_amount, max_discount_amount, and discount_amount columns use NUMERIC(10,2)"
},
{
"name": "Nullable usage_limit",
"max_score": 8,
"description": "usage_limit is defined as INTEGER with no NOT NULL constraint (nullable to represent unlimited)"
},
{
"name": "per_customer_limit default",
"max_score": 8,
"description": "per_customer_limit has DEFAULT 1"
},
{
"name": "TIMESTAMPTZ for date columns",
"max_score": 8,
"description": "starts_at, expires_at, redeemed_at, and created_at columns use TIMESTAMPTZ (not TIMESTAMP or DATE)"
},
{
"name": "Product/category arrays",
"max_score": 5,
"description": "product_ids and category_ids columns are defined as UUID[] (PostgreSQL arrays)"
},
{
"name": "Code lookup index",
"max_score": 10,
"description": "An index on the coupons code column is defined (e.g. idx_coupons_code ON coupons(code))"
},
{
"name": "Redemptions lookup index",
"max_score": 10,
"description": "A composite index on coupon_redemptions covering both customer_id and coupon_id is defined"
},
{
"name": "Redemptions foreign key",
"max_score": 7,
"description": "coupon_redemptions.coupon_id references coupons(id) via a FOREIGN KEY constraint"
}
]
}
Promotional Code System — Database Design
Problem/Feature Description
FinSi is launching its first promotional campaign ahead of a product rebrand and needs a database layer to power coupon codes across its checkout flow. The engineering team has been asked to design the persistence layer from scratch: there is currently no coupon infrastructure and marketing wants to support percentage-off deals, flat dollar discounts, and free-shipping offers — each with configurable minimum basket sizes, discount caps, and expiry windows.
The system must also track which customers have already used a given code so that per-customer limits can be enforced, and it must retain a permanent history of every redemption even if a coupon is later retired. The CTO has flagged that coupon lookup will happen on every checkout page load so the schema must be optimized accordingly.
Output Specification
Produce a single file schema.sql containing:
- All
CREATE TABLEstatements for the coupon system - All
CREATE INDEXstatements required for production performance - Inline SQL comments where the purpose of a column or constraint is not self-evident
Do not include any application code, migration tooling, or seed data — just the schema DDL.
{
"context": "Tests whether the agent implements coupon validation with the correct TypeScript interface, all five error codes, code normalization, discount-cap logic, and validation logging as specified in the coupon-management skill.",
"type": "weighted_checklist",
"checklist": [
{
"name": "CouponValidationResult interface",
"max_score": 8,
"description": "A TypeScript interface named CouponValidationResult is exported with fields: valid (boolean), discountAmount (number), and an optional errorCode"
},
{
"name": "All five error codes",
"max_score": 10,
"description": "errorCode type includes all five values: 'EXPIRED', 'USAGE_LIMIT_REACHED', 'MIN_ORDER_NOT_MET', 'NOT_FOUND', 'CUSTOMER_LIMIT_REACHED' (no others substituted)"
},
{
"name": "Code normalization on lookup",
"max_score": 8,
"description": "The code is passed through toUpperCase() and trim() before the database lookup (not just in the response)"
},
{
"name": "Unreleased code returns NOT_FOUND",
"max_score": 10,
"description": "When starts_at is in the future, the function returns errorCode 'NOT_FOUND' (not a custom 'NOT_STARTED' or similar code)"
},
{
"name": "Usage limit check",
"max_score": 8,
"description": "Checks usage_count >= usage_limit (only when usage_limit is not null) and returns USAGE_LIMIT_REACHED"
},
{
"name": "Per-customer limit check",
"max_score": 8,
"description": "Queries a redemptions table/store to count how many times the customer has used this coupon, returning CUSTOMER_LIMIT_REACHED when exceeded"
},
{
"name": "Percentage cap respected",
"max_score": 10,
"description": "calculateDiscount applies Math.min(raw, max_discount_amount) when max_discount_amount is set for percentage coupons"
},
{
"name": "Free-shipping returns zero",
"max_score": 8,
"description": "calculateDiscount returns 0 for 'free_shipping' type coupons (not an error — zero subtotal discount)"
},
{
"name": "Fixed-amount capped at subtotal",
"max_score": 8,
"description": "calculateDiscount for fixed_amount returns Math.min(coupon.value, subtotal) — does NOT allow discount to exceed the order total"
},
{
"name": "Validation attempts logged",
"max_score": 8,
"description": "The code records or logs each validation attempt including failures (e.g. inserts a record, logs with reason code, or persists to a store)"
},
{
"name": "validateCoupon function signature",
"max_score": 6,
"description": "validateCoupon accepts at minimum: code (string), customerId (string), orderSubtotal (number), and itemIds (array)"
},
{
"name": "validateCoupon return type",
"max_score": 8,
"description": "validateCoupon is async and returns Promise<CouponValidationResult>"
}
]
}
Checkout Coupon Validation Service
Problem/Feature Description
The FinSi checkout team needs a TypeScript module that validates promotional codes when a customer applies one at checkout. The validator must reject codes that are past their end date, have reached their global redemption cap, don't meet the basket minimum, or have already been used by the same customer — and it must return a machine-readable reason in each case so the frontend can display a friendly message without any further API calls.
The discount calculation must correctly handle the three coupon types the marketing team uses: flat dollar-off, percentage-off (where a maximum discount cap is sometimes set to protect margin), and free-shipping codes. Free-shipping codes are processed in a separate shipping-cost step and should produce a zero discount in the subtotal calculation.
Validation attempts — including failures and their reasons — need to be persisted so the fraud and analytics teams can monitor misuse patterns over time.
Output Specification
Produce a TypeScript file coupon-validator.ts that exports:
- A
CouponValidationResultinterface - A
validateCouponfunction - A
calculateDiscounthelper function
The file should contain inline comments explaining key decisions. You may define a minimal stub/mock for any database layer — the grader cares about the structure and logic of the exported functions, not the DB implementation details.
{
"name": "finsi/coupon-management",
"version": "0.1.0",
"summary": "Coupon CRUD, validation rules, usage limits, single-use codes, bulk generation",
"skills": {
"coupon-management": {
"path": "SKILL.md"
}
}
}