Skip to content

ersinkoc/CouponEngine

Repository files navigation

Coupon Engine - Shopify App

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.

Table of Contents

Features

  • 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

Quick Start

Prerequisites

  • Node.js >= 20.0.0
  • Docker and Docker Compose
  • Shopify Partner Account
  • ngrok (for development)

Installation

  1. Clone and install dependencies
git clone <repository-url>
cd coupon-engine
npm install
  1. Set up environment variables
cp .env.example .env

Edit .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
  1. Start Docker services
npm run docker:up

This starts PostgreSQL (port 5432), Redis (port 6379), and Adminer (port 8080).

  1. Initialize database
npm run setup        # Generate Prisma client and push schema
npm run db:seed      # Seed with sample data (optional)
  1. Start development server
npm run dev

The app will be available at your ngrok URL.

Architecture

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

Condition Types

Cart Conditions

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

Customer Conditions

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

Product Conditions

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 -

Time Conditions

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

Reward Types

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

API Reference

Validate Coupon

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"
}

Track Usage

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..."
}

Generate Codes

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", ...]
}

Billing Plans

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

Commands

# 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

Adding New Condition Types

  1. Add type to app/types/condition.ts:
export type ConditionType =
  | "cart_total"
  | "your_new_condition"  // Add here
  | ...
  1. 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;
  1. Add UI in app/components/rules/ConditionRow.tsx:
const conditionOptions = [
  { label: "Your New Condition", value: "your_new_condition" },
  ...
];
  1. 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;

Adding New Reward Types

  1. Add type to app/types/reward.ts:
export type RewardType =
  | "percentage_off"
  | "your_new_reward"  // Add here
  | ...
  1. Update app/components/rules/RewardBuilder.tsx with UI

  2. Update discount function extension to handle the new type

Testing

Tests are written using Vitest with 100% mock coverage for external services.

Run Tests

# Run all tests
npm run test

# Run in watch mode
npm run test:watch

# Run with coverage
npm run test:coverage

Test Structure

test/
├── 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)

Test Coverage Areas

  • Rule Engine: Condition evaluation, logical operators (AND/OR), nested groups, edge cases
  • Billing: Plan configuration, feature checking, limits, upgrade prompts

Deployment

Production Checklist

  • 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

Environment Variables

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

Deploy to Shopify

# Build the app
npm run build

# Deploy extensions
npx shopify app deploy

Troubleshooting

Common Issues

Database connection failed

# Check Docker containers are running
npm run docker:ps

# Reset Docker containers
npm run docker:reset

Redis connection failed

# Verify Redis is running
docker exec -it coupon-redis redis-cli ping

TypeScript errors

# Run type check
npm run typecheck

# Regenerate Prisma client
npx prisma generate

Build fails

# Clear build cache
rm -rf build node_modules/.cache
npm run build

Extension not loading

# Check extension build
cd extensions/checkout-ui
npm install
npm run build

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Write tests for new functionality
  4. Ensure all tests pass (npm run test)
  5. Ensure TypeScript passes (npm run typecheck)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

License

Private - All rights reserved

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors