Enterprise-grade coupon and discount management system for Shopify stores. Build powerful coupons with 20+ condition types, 8 reward types, bulk code generation, and detailed analytics.
- Features
- Quick Start
- Architecture
- Condition Types
- Reward Types
- API Reference
- Billing Plans
- Development
- Testing
- Deployment
- Troubleshooting
- Visual Rule Builder: Drag-and-drop interface for creating complex coupon conditions with AND/OR logic
- 20+ Condition Types: Cart, customer, product, collection, and time-based conditions
- 8 Reward Types: Percentage, fixed amount, free shipping, BOGO, tiered discounts
- Bulk Code Generation: Generate thousands of unique codes instantly
- Analytics Dashboard: Track usage, revenue impact, and conversion rates
- 4-Tier Billing: Free, Starter ($9/mo), Pro ($29/mo), Enterprise ($99/mo)
- Redis Caching: High-performance coupon validation
- Reusable Templates: Save and reuse common discount configurations
- Shopify Extensions: Native checkout UI and discount functions
- Node.js >= 20.0.0
- Docker and Docker Compose
- Shopify Partner Account
- ngrok (for development)
- Clone and install dependencies
git clone <repository-url>
cd coupon-engine
npm install- Set up environment variables
cp .env.example .envEdit .env with your Shopify app credentials:
SHOPIFY_API_KEY=your_api_key
SHOPIFY_API_SECRET=your_api_secret
SHOPIFY_APP_URL=https://your-ngrok-url.ngrok-free.app
SCOPES=read_products,read_orders,read_customers,read_discounts,write_discounts
DATABASE_URL=postgresql://coupon_user:coupon_pass@localhost:5432/coupon_engine
REDIS_URL=redis://localhost:6379- Start Docker services
npm run docker:upThis starts PostgreSQL (port 5432), Redis (port 6379), and Adminer (port 8080).
- Initialize database
npm run setup # Generate Prisma client and push schema
npm run db:seed # Seed with sample data (optional)- Start development server
npm run devThe app will be available at your ngrok URL.
coupon-engine/
├── app/
│ ├── components/ # React components
│ │ └── rules/ # Rule Builder components
│ │ ├── RuleBuilder.tsx
│ │ ├── RuleGroup.tsx
│ │ ├── ConditionRow.tsx
│ │ └── RewardBuilder.tsx
│ ├── routes/ # Remix routes
│ │ ├── app._index.tsx # Dashboard
│ │ ├── app.coupons.* # Coupon management
│ │ ├── app.billing.* # Billing pages
│ │ ├── app.analytics.* # Analytics pages
│ │ ├── app.templates.* # Template management
│ │ ├── app.settings.* # Settings
│ │ └── api.* # API endpoints
│ ├── services/ # Business logic
│ │ ├── coupon.server.ts # Coupon CRUD
│ │ ├── billing.server.ts # Billing operations
│ │ ├── rule-engine.server.ts # Condition evaluation
│ │ ├── code-generator.server.ts # Code generation
│ │ ├── analytics.server.ts # Analytics queries
│ │ └── redis.server.ts # Caching layer
│ └── types/ # TypeScript types
│ ├── condition.ts # Condition types
│ ├── reward.ts # Reward types
│ └── billing.ts # Billing types
├── extensions/
│ ├── checkout-ui/ # Checkout UI extension
│ └── discount-function/ # Discount function extension
├── prisma/
│ ├── schema.prisma # Database schema
│ └── seed.ts # Seed data
└── test/ # Test files
├── setup.ts # Test configuration
└── services/ # Service tests
| Type | Description | Operators |
|---|---|---|
cart_total |
Total cart value | gt, gte, lt, lte, eq |
cart_item_count |
Number of unique items | gt, gte, lt, lte, eq |
cart_total_quantity |
Total quantity of items | gt, gte, lt, lte, eq |
cart_contains_products |
Specific products in cart | contains, not_contains |
cart_contains_collections |
Products from collections | contains, not_contains |
cart_contains_tags |
Products with tags | contains, not_contains |
cart_weight |
Total cart weight | gt, gte, lt, lte |
| Type | Description | Operators |
|---|---|---|
customer_order_count |
Number of previous orders | gt, gte, lt, lte, eq |
customer_total_spent |
Lifetime spend amount | gt, gte, lt, lte |
customer_registered_days |
Days since registration | gt, gte, lt, lte |
customer_registered_months |
Months since registration | gt, gte, lt, lte |
customer_tag |
Customer has specific tags | contains, not_contains |
customer_first_order |
Is first order | is_true, is_false |
customer_email_domain |
Email domain check | eq, contains |
customer_accepts_marketing |
Marketing consent | is_true, is_false |
customer_country |
Shipping country | eq, in, not_in |
customer_state |
Shipping state/province | eq, in, not_in |
customer_verified_email |
Email verified | is_true, is_false |
| Type | Description | Operators |
|---|---|---|
product_quantity_min |
Minimum product quantity | gte |
product_quantity_exact |
Exact product quantity | eq |
requires_product |
Must contain products | contains |
requires_collection |
Must contain from collection | contains |
exclude_products |
Exclude specific products | - |
exclude_collections |
Exclude from collections | - |
exclude_sale_items |
Exclude on-sale items | - |
| Type | Description | Operators |
|---|---|---|
day_of_week |
Specific days of week | in, not_in |
hour_of_day |
Time of day | gt, gte, lt, lte |
date_range |
Date range validity | Built-in via startsAt/endsAt |
| Type | Description | Configuration |
|---|---|---|
percentage_off |
Percentage discount | value: 10-100% |
fixed_amount_off |
Fixed amount discount | value: Amount in currency |
free_shipping |
Free shipping | No additional config |
buy_x_get_y |
BOGO deals | buyQuantity, getQuantity, getProductId |
tiered_discount |
Volume discounts | tiers: Array of quantity/discount pairs |
free_gift |
Free product | giftProductId, giftVariantId |
percentage_off_products |
Discount on specific products | value, productIds |
percentage_off_collections |
Discount on collections | value, collectionIds |
Validates a coupon code against cart and customer data.
Endpoint: POST /api/validate-coupon
Request:
{
"code": "SUMMER20",
"cartTotal": 150.00,
"itemCount": 3,
"totalQuantity": 5,
"customerEmail": "customer@example.com",
"customerOrderCount": 2,
"customerTotalSpent": 500.00,
"productIds": ["gid://shopify/Product/123"],
"collectionIds": ["gid://shopify/Collection/456"]
}Response:
{
"valid": true,
"couponId": "clx...",
"rewards": [
{
"type": "percentage_off",
"value": 20,
"appliesTo": "cart"
}
],
"message": "Coupon applied successfully"
}Error Response:
{
"valid": false,
"error": "INVALID_CODE",
"message": "Coupon code not found"
}Records coupon usage after checkout completion.
Endpoint: POST /api/track-usage
Request:
{
"couponId": "clx...",
"code": "SUMMER20",
"orderId": "gid://shopify/Order/123",
"customerId": "gid://shopify/Customer/456",
"orderTotal": 120.00,
"discountAmount": 30.00
}Response:
{
"success": true,
"usageId": "clx..."
}Generates bulk coupon codes asynchronously.
Endpoint: POST /api/generate-codes
Request:
{
"couponId": "clx...",
"count": 100,
"prefix": "SUMMER",
"length": 8
}Response:
{
"success": true,
"batchId": "clx...",
"codesGenerated": 100,
"codes": ["SUMMER-ABCD1234", "SUMMER-EFGH5678", ...]
}| Feature | Free | Starter | Pro | Enterprise |
|---|---|---|---|---|
| Price | $0 | $9/mo | $29/mo | $99/mo |
| Active Coupons | 3 | 25 | Unlimited | Unlimited |
| Monthly Uses | 100 | 1,000 | 10,000+ | Unlimited |
| Cart Conditions | Yes | Yes | Yes | Yes |
| Customer Conditions | - | Yes | Yes | Yes |
| Advanced Conditions | - | Yes | Yes | Yes |
| Auto-Generated Codes | - | Yes | Yes | Yes |
| Bulk Code Gen | - | - | Yes | Yes |
| Basic Analytics | Yes | Yes | Yes | Yes |
| Advanced Analytics | - | - | Yes | Yes |
| API Access | - | - | - | Yes |
| Priority Support | - | - | Yes | Yes |
| Trial Period | - | 7 days | 14 days | 14 days |
# Development
npm run dev # Start Shopify app dev server
npm run dev:remix # Start Remix dev only
# Database
npm run db:push # Push schema changes
npm run db:studio # Open Prisma Studio
npm run db:seed # Seed database
npm run db:reset # Reset database
npm run db:migrate # Run migrations
# Docker
npm run docker:up # Start containers
npm run docker:down # Stop containers
npm run docker:logs # View logs
npm run docker:reset # Reset with fresh data
npm run docker:ps # Container status
# Testing
npm run test # Run tests
npm run test:watch # Run tests in watch mode
npm run test:coverage # Run with coverage
# Build & Quality
npm run build # Build for production
npm run typecheck # TypeScript check
npm run lint # ESLint check- Add type to
app/types/condition.ts:
export type ConditionType =
| "cart_total"
| "your_new_condition" // Add here
| ...- Add evaluation logic in
app/services/rule-engine.server.ts:
case "your_new_condition":
actualValue = context.your.field;
passed = compareNumeric(actualValue, operator, value as number);
break;- Add UI in
app/components/rules/ConditionRow.tsx:
const conditionOptions = [
{ label: "Your New Condition", value: "your_new_condition" },
...
];- Add validation in
rule-engine.server.ts:
case "your_new_condition":
if (typeof condition.value !== "number") {
errors.push("your_new_condition requires a numeric value");
}
break;- Add type to
app/types/reward.ts:
export type RewardType =
| "percentage_off"
| "your_new_reward" // Add here
| ...-
Update
app/components/rules/RewardBuilder.tsxwith UI -
Update discount function extension to handle the new type
Tests are written using Vitest with 100% mock coverage for external services.
# Run all tests
npm run test
# Run in watch mode
npm run test:watch
# Run with coverage
npm run test:coveragetest/
├── setup.ts # Global test setup with mocks
└── services/
├── rule-engine.test.ts # Rule engine tests (39 tests)
└── billing.test.ts # Billing helper tests (26 tests)
- Rule Engine: Condition evaluation, logical operators (AND/OR), nested groups, edge cases
- Billing: Plan configuration, feature checking, limits, upgrade prompts
- Set
NODE_ENV=production - Configure production database URL
- Configure production Redis URL
- Run
npm run build - Run
npx prisma migrate deploy - Set up monitoring (Sentry, DataDog, etc.)
- Configure webhook endpoints
- Test billing flow with live payments
- Deploy Shopify extensions
| Variable | Description | Required |
|---|---|---|
SHOPIFY_API_KEY |
Shopify app API key | Yes |
SHOPIFY_API_SECRET |
Shopify app API secret | Yes |
SHOPIFY_APP_URL |
Public app URL | Yes |
SCOPES |
Shopify API scopes | Yes |
DATABASE_URL |
PostgreSQL connection string | Yes |
REDIS_URL |
Redis connection string | Yes |
NODE_ENV |
Environment (development/production) | Yes |
# Build the app
npm run build
# Deploy extensions
npx shopify app deployDatabase connection failed
# Check Docker containers are running
npm run docker:ps
# Reset Docker containers
npm run docker:resetRedis connection failed
# Verify Redis is running
docker exec -it coupon-redis redis-cli pingTypeScript errors
# Run type check
npm run typecheck
# Regenerate Prisma client
npx prisma generateBuild fails
# Clear build cache
rm -rf build node_modules/.cache
npm run buildExtension not loading
# Check extension build
cd extensions/checkout-ui
npm install
npm run build- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Write tests for new functionality
- Ensure all tests pass (
npm run test) - Ensure TypeScript passes (
npm run typecheck) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Private - All rights reserved