Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.launchmystore.io/llms.txt

Use this file to discover all available pages before exploring further.

Discount Functions

Discount functions allow your app to apply custom discounts during checkout. Use them for tiered pricing, loyalty rewards, dynamic promotions, BOGO offers, and complex discount rules that go beyond standard discount codes.

How It Works

Function Manifest

{
  "handle": "my-discounts-app",
  "name": "Smart Discounts",
  "version": "1.0.0",
  "functions": {
    "discount": {
      "handle": "dynamic-discounts",
      "name": "Dynamic Discount Rules",
      "config": {
        "vipDiscount": 0.15,
        "bulkThreshold": 10,
        "bulkDiscount": 0.10
      }
    }
  }
}

Input Schema

interface DiscountInput {
  cart: {
    items: CartItem[];
    subtotal: number;
    currency: string;
    discountCodes: string[];   // Customer-entered codes
    attributes: Record<string, string>;
  };
  customer: {
    id: string;
    email: string;
    tags: string[];
    ordersCount: number;
    totalSpent: number;
  } | null;
  shop: {
    id: string;
    currency: string;
  };
}

interface CartItem {
  id: string;
  variantId: string;
  productId: string;
  title: string;
  quantity: number;
  price: number;         // Unit price in cents
  sku: string;
  productType: string;
  vendor: string;
  tags: string[];
  collections: string[]; // Collection handles
  properties: Record<string, string>;
}

Output Schema

interface DiscountOutput {
  discounts: Discount[];
}

type Discount = 
  | {
      type: 'percentage';
      value: number;          // 0.0 to 1.0 (e.g., 0.15 = 15%)
      target: DiscountTarget;
      message: string;        // Shown to customer
    }
  | {
      type: 'fixed_amount';
      value: number;          // Amount in cents
      target: DiscountTarget;
      message: string;
    }
  | {
      type: 'buy_x_get_y';
      buyQuantity: number;
      getQuantity: number;
      target: DiscountTarget;
      message: string;
    };

type DiscountTarget = 
  | { scope: 'all' }                           // All items
  | { scope: 'product'; productIds: string[] } // Specific products
  | { scope: 'variant'; variantIds: string[] } // Specific variants
  | { scope: 'collection'; handles: string[] } // Products in collections
  | { scope: 'shipping' };                     // Shipping cost

Discount Types

Percentage Discount

{
  discounts: [
    {
      type: 'percentage',
      value: 0.15,  // 15% off
      target: { scope: 'all' },
      message: 'VIP Member: 15% off entire order'
    }
  ]
}

Fixed Amount Discount

{
  discounts: [
    {
      type: 'fixed_amount',
      value: 1000,  // $10 off
      target: { scope: 'all' },
      message: 'Loyalty reward: $10 off'
    }
  ]
}

Buy X Get Y

{
  discounts: [
    {
      type: 'buy_x_get_y',
      buyQuantity: 2,
      getQuantity: 1,  // Buy 2 get 1 free
      target: { 
        scope: 'collection', 
        handles: ['sale-items'] 
      },
      message: 'Buy 2 get 1 free on sale items!'
    }
  ]
}

Example Implementations

VIP Customer Discounts

function calculateDiscounts(input, config) {
  const discounts = [];
  const { customer } = input;
  
  if (!customer) return { discounts };
  
  // VIP tier discounts
  const vipTiers = {
    'vip_platinum': { discount: 0.20, name: 'Platinum' },
    'vip_gold': { discount: 0.15, name: 'Gold' },
    'vip_silver': { discount: 0.10, name: 'Silver' }
  };
  
  for (const [tag, tier] of Object.entries(vipTiers)) {
    if (customer.tags.includes(tag)) {
      discounts.push({
        type: 'percentage',
        value: tier.discount,
        target: { scope: 'all' },
        message: `${tier.name} Member: ${tier.discount * 100}% off`
      });
      break;  // Only apply highest tier
    }
  }
  
  return { discounts };
}

Tiered Spending Discounts

function calculateDiscounts(input, config) {
  const discounts = [];
  const { cart } = input;
  
  // Spend more, save more
  const tiers = [
    { threshold: 20000, discount: 0.20, label: 'Spend $200+, save 20%' },
    { threshold: 15000, discount: 0.15, label: 'Spend $150+, save 15%' },
    { threshold: 10000, discount: 0.10, label: 'Spend $100+, save 10%' },
    { threshold: 5000, discount: 0.05, label: 'Spend $50+, save 5%' }
  ];
  
  // Find applicable tier (highest first)
  const applicableTier = tiers.find(
    tier => cart.subtotal >= tier.threshold
  );
  
  if (applicableTier) {
    discounts.push({
      type: 'percentage',
      value: applicableTier.discount,
      target: { scope: 'all' },
      message: applicableTier.label
    });
  }
  
  return { discounts };
}

Buy X Get Y Free

function calculateDiscounts(input, config) {
  const discounts = [];
  const { cart } = input;
  
  // Buy 2 get 1 free on specific collection
  const bogoCollections = ['tshirts', 'accessories'];
  
  const bogoItems = cart.items.filter(
    item => item.collections.some(c => bogoCollections.includes(c))
  );
  
  const totalBogoQuantity = bogoItems.reduce(
    (sum, item) => sum + item.quantity, 0
  );
  
  // Every 3rd item is free
  if (totalBogoQuantity >= 3) {
    discounts.push({
      type: 'buy_x_get_y',
      buyQuantity: 2,
      getQuantity: 1,
      target: { scope: 'collection', handles: bogoCollections },
      message: 'Buy 2 Get 1 Free on T-Shirts & Accessories!'
    });
  }
  
  return { discounts };
}

First Order Discount

function calculateDiscounts(input, config) {
  const discounts = [];
  const { customer, cart } = input;
  
  // New customer discount
  if (customer && customer.ordersCount === 0) {
    discounts.push({
      type: 'percentage',
      value: 0.10,
      target: { scope: 'all' },
      message: 'Welcome! 10% off your first order'
    });
  }
  
  // Winback discount for returning customers
  if (customer && customer.ordersCount > 0) {
    // Check if customer has been inactive (this would need date data)
    // For demo, check a tag
    if (customer.tags.includes('winback-eligible')) {
      discounts.push({
        type: 'fixed_amount',
        value: 1500,
        target: { scope: 'all' },
        message: 'We missed you! $15 off your return order'
      });
    }
  }
  
  return { discounts };
}

Bundle Discount

function calculateDiscounts(input, config) {
  const discounts = [];
  const { cart } = input;
  
  // Define bundles and their discounts
  const bundles = [
    {
      name: 'Complete Set',
      requiredProducts: ['prod_shirt', 'prod_pants', 'prod_belt'],
      discount: 0.25
    },
    {
      name: 'Starter Kit',
      requiredProducts: ['prod_cleanser', 'prod_moisturizer'],
      discount: 0.15
    }
  ];
  
  for (const bundle of bundles) {
    const hasAllProducts = bundle.requiredProducts.every(
      prodId => cart.items.some(item => item.productId === prodId)
    );
    
    if (hasAllProducts) {
      discounts.push({
        type: 'percentage',
        value: bundle.discount,
        target: { 
          scope: 'product', 
          productIds: bundle.requiredProducts 
        },
        message: `${bundle.name}: ${bundle.discount * 100}% off when bought together`
      });
    }
  }
  
  return { discounts };
}

Category-Specific Discounts

function calculateDiscounts(input, config) {
  const discounts = [];
  const { cart } = input;
  
  // Sale collection
  const saleItems = cart.items.filter(
    item => item.collections.includes('sale')
  );
  
  if (saleItems.length > 0) {
    discounts.push({
      type: 'percentage',
      value: 0.30,
      target: { scope: 'collection', handles: ['sale'] },
      message: 'Sale: 30% off clearance items'
    });
  }
  
  // Bulk discount on specific vendor
  const vendorItems = cart.items.filter(
    item => item.vendor === 'Premium Brand'
  );
  
  const vendorQuantity = vendorItems.reduce(
    (sum, item) => sum + item.quantity, 0
  );
  
  if (vendorQuantity >= 5) {
    discounts.push({
      type: 'percentage',
      value: 0.10,
      target: { 
        scope: 'product', 
        productIds: vendorItems.map(i => i.productId) 
      },
      message: 'Bulk discount: 10% off 5+ Premium Brand items'
    });
  }
  
  return { discounts };
}

Free Shipping Threshold

function calculateDiscounts(input, config) {
  const discounts = [];
  const { cart } = input;
  
  const freeShippingThreshold = config.freeShippingThreshold || 5000;
  
  if (cart.subtotal >= freeShippingThreshold) {
    discounts.push({
      type: 'percentage',
      value: 1.0,  // 100% off shipping
      target: { scope: 'shipping' },
      message: `Free shipping on orders over $${freeShippingThreshold / 100}`
    });
  }
  
  return { discounts };
}

Time-Limited Discounts

function calculateDiscounts(input, config) {
  const discounts = [];
  
  const now = new Date();
  const hour = now.getHours();
  const dayOfWeek = now.getDay();
  
  // Happy hour: 4 PM - 6 PM
  if (hour >= 16 && hour < 18) {
    discounts.push({
      type: 'percentage',
      value: 0.10,
      target: { scope: 'all' },
      message: 'Happy Hour: 10% off (4-6 PM only!)'
    });
  }
  
  // Weekend special
  if (dayOfWeek === 0 || dayOfWeek === 6) {
    discounts.push({
      type: 'percentage',
      value: 0.05,
      target: { scope: 'all' },
      message: 'Weekend Special: Extra 5% off'
    });
  }
  
  // Flash sale (check config for active sales)
  const activeSale = config.flashSales?.find(
    sale => new Date(sale.startTime) <= now && now <= new Date(sale.endTime)
  );
  
  if (activeSale) {
    discounts.push({
      type: 'percentage',
      value: activeSale.discount,
      target: activeSale.target,
      message: activeSale.message
    });
  }
  
  return { discounts };
}

Cart Display

Discounts appear in the cart and checkout:
Your Cart
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Premium T-Shirt x 2              $59.98
Accessories Pack                 $29.99
Belt                            $24.99

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Subtotal                        $114.96

Applied Discounts:
✓ Complete Set: 25% off         -$28.74
✓ Gold Member: 15% off          -$12.93

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total                            $73.29
You saved: $41.67

Discount Stacking

By default, your function’s discounts can stack with:
  • Discount codes entered by customers
  • Other app discount functions
Be careful with stacking. A 20% VIP discount + 30% sale + 10% code = 60% off total. Consider excluding already-discounted items.

Prevent Over-Discounting

function calculateDiscounts(input, config) {
  const discounts = [];
  const { cart } = input;
  
  // Don't stack with existing discount codes
  if (cart.discountCodes.length > 0) {
    return { discounts };  // No additional discounts
  }
  
  // Or exclude sale items from VIP discount
  const nonSaleItems = cart.items.filter(
    item => !item.collections.includes('sale')
  );
  
  if (nonSaleItems.length > 0) {
    discounts.push({
      type: 'percentage',
      value: 0.15,
      target: { 
        scope: 'product', 
        productIds: nonSaleItems.map(i => i.productId) 
      },
      message: 'VIP: 15% off (excludes sale items)'
    });
  }
  
  return { discounts };
}

Best Practices

Messages appear in cart UI. Make them clear and concise: “VIP: 15% off” not “Discount applied based on customer tag vip_gold”.
Test with empty carts, single items, guest checkout, and various customer tiers.
Think about how your discounts interact with store discount codes and other apps.
Discount functions run on cart updates. Keep calculations fast.
Track which discounts are applied for reporting and debugging.
Store discount rules in config so you can update without code changes.

Testing

Test with various scenarios:
{
  "cart": {
    "items": [
      { "productId": "prod_1", "title": "Widget", "price": 2999, "quantity": 2 }
    ],
    "subtotal": 5998,
    "discountCodes": []
  },
  "customer": {
    "id": "cust_123",
    "tags": ["vip_gold"],
    "ordersCount": 10
  }
}

See Also