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.

Usage-Based Billing

Usage-based billing charges merchants based on their actual usage of your app, such as API calls, orders processed, emails sent, or storage used. This model aligns your revenue with the value you provide.

How It Works

Stripe Setup

1. Create Metered Price

Create a metered price in Stripe:
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

async function createMeteredPrice() {
  // Create product
  const product = await stripe.products.create({
    name: 'Orders Processed'
  });
  
  // Create metered price
  const price = await stripe.prices.create({
    product: product.id,
    currency: 'usd',
    recurring: {
      interval: 'month',
      usage_type: 'metered'
    },
    billing_scheme: 'tiered',
    tiers_mode: 'graduated',  // Each tier applies to portion of usage
    tiers: [
      {
        up_to: 100,
        unit_amount: 0  // First 100 free
      },
      {
        up_to: 1000,
        unit_amount: 10  // $0.10 per order
      },
      {
        up_to: 10000,
        unit_amount: 5   // $0.05 per order
      },
      {
        up_to: 'inf',
        unit_amount: 2   // $0.02 per order
      }
    ]
  });
  
  return price;
}

2. Create Subscription with Metered Item

async function createMeteredSubscription(customerId, priceId) {
  const subscription = await stripe.subscriptions.create({
    customer: customerId,
    items: [
      {
        price: priceId
      }
    ],
    // Optionally add a base fee
    add_invoice_items: [
      {
        price: 'price_base_fee'  // $9.99 platform fee
      }
    ]
  });
  
  // Save the subscription item ID for usage reporting
  const subscriptionItemId = subscription.items.data[0].id;
  
  return { subscription, subscriptionItemId };
}

Recording Usage

Basic Usage Reporting

Report usage to Stripe:
async function reportUsage(subscriptionItemId, quantity, timestamp = null) {
  const usageRecord = await stripe.subscriptionItems.createUsageRecord(
    subscriptionItemId,
    {
      quantity,
      timestamp: timestamp || Math.floor(Date.now() / 1000),
      action: 'increment'  // Add to existing usage
    }
  );
  
  return usageRecord;
}

Usage Events Service

Create a service to handle usage recording:
// services/usageService.js
import Stripe from 'stripe';
import { Redis } from 'ioredis';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const redis = new Redis(process.env.REDIS_URL);

class UsageService {
  constructor() {
    this.batchSize = 100;
    this.flushInterval = 60000; // 1 minute
  }
  
  /**
   * Record a usage event
   */
  async recordUsage(shopId, metric, quantity = 1, metadata = {}) {
    const event = {
      shopId,
      metric,
      quantity,
      timestamp: Date.now(),
      metadata
    };
    
    // Buffer in Redis
    await redis.lpush(`usage:buffer:${shopId}:${metric}`, JSON.stringify(event));
    
    // Check if we should flush
    const bufferSize = await redis.llen(`usage:buffer:${shopId}:${metric}`);
    if (bufferSize >= this.batchSize) {
      await this.flushUsage(shopId, metric);
    }
  }
  
  /**
   * Flush buffered usage to Stripe
   */
  async flushUsage(shopId, metric) {
    const key = `usage:buffer:${shopId}:${metric}`;
    
    // Get and clear buffer atomically
    const events = await redis.lrange(key, 0, -1);
    await redis.del(key);
    
    if (events.length === 0) return;
    
    // Calculate total quantity
    const totalQuantity = events.reduce((sum, event) => {
      const parsed = JSON.parse(event);
      return sum + parsed.quantity;
    }, 0);
    
    // Get subscription item ID
    const subscriptionItemId = await this.getSubscriptionItemId(shopId, metric);
    
    if (subscriptionItemId) {
      await stripe.subscriptionItems.createUsageRecord(
        subscriptionItemId,
        {
          quantity: totalQuantity,
          timestamp: Math.floor(Date.now() / 1000),
          action: 'increment'
        }
      );
    }
    
    // Store for analytics
    await this.storeUsageAnalytics(shopId, metric, totalQuantity, events);
  }
  
  /**
   * Get current period usage
   */
  async getCurrentUsage(shopId, metric) {
    const subscriptionItemId = await this.getSubscriptionItemId(shopId, metric);
    
    if (!subscriptionItemId) return 0;
    
    const usageSummary = await stripe.subscriptionItems.listUsageRecordSummaries(
      subscriptionItemId,
      { limit: 1 }
    );
    
    return usageSummary.data[0]?.total_usage || 0;
  }
  
  async getSubscriptionItemId(shopId, metric) {
    return redis.get(`subscription_item:${shopId}:${metric}`);
  }
}

export const usageService = new UsageService();

Usage Recording Middleware

Automatically record usage for API calls:
// middleware/usageTracking.js
import { usageService } from '../services/usageService';

export function trackUsage(metric) {
  return async (req, res, next) => {
    // Store original end function
    const originalEnd = res.end;
    
    res.end = function(...args) {
      // Only track successful requests
      if (res.statusCode >= 200 && res.statusCode < 300) {
        usageService.recordUsage(req.shopId, metric, 1, {
          endpoint: req.path,
          method: req.method
        }).catch(console.error);
      }
      
      return originalEnd.apply(this, args);
    };
    
    next();
  };
}

// Usage in routes
app.post('/api/orders/process', trackUsage('orders'), processOrder);
app.post('/api/emails/send', trackUsage('emails'), sendEmail);

Usage Dashboard

Show merchants their current usage:
import { useState, useEffect } from 'react';

function UsageDashboard({ shopId }) {
  const [usage, setUsage] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchUsage();
  }, []);
  
  const fetchUsage = async () => {
    const response = await fetch(`/api/billing/usage?shopId=${shopId}`);
    const data = await response.json();
    setUsage(data);
    setLoading(false);
  };
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <div className="usage-dashboard">
      <h2>Current Billing Period</h2>
      
      <div className="usage-metrics">
        {usage.metrics.map(metric => (
          <div key={metric.name} className="metric-card">
            <h3>{metric.name}</h3>
            <div className="metric-value">
              {metric.current.toLocaleString()} / {metric.included.toLocaleString()}
            </div>
            <div className="progress-bar">
              <div 
                className="progress" 
                style={{ width: `${Math.min(100, (metric.current / metric.included) * 100)}%` }}
              />
            </div>
            <div className="metric-details">
              <span>Included: {metric.included.toLocaleString()}</span>
              {metric.current > metric.included && (
                <span className="overage">
                  Overage: {(metric.current - metric.included).toLocaleString()} 
                  ({formatCurrency(metric.overageCost)})
                </span>
              )}
            </div>
          </div>
        ))}
      </div>
      
      <div className="estimated-bill">
        <h3>Estimated Bill</h3>
        <div className="bill-breakdown">
          <div className="line-item">
            <span>Base Fee</span>
            <span>{formatCurrency(usage.baseFee)}</span>
          </div>
          {usage.metrics.map(metric => (
            metric.overageCost > 0 && (
              <div key={metric.name} className="line-item">
                <span>{metric.name} Overage</span>
                <span>{formatCurrency(metric.overageCost)}</span>
              </div>
            )
          ))}
          <div className="line-item total">
            <span>Estimated Total</span>
            <span>{formatCurrency(usage.estimatedTotal)}</span>
          </div>
        </div>
      </div>
    </div>
  );
}

Usage Alerts

Notify merchants when approaching limits:
// services/usageAlerts.js
async function checkUsageAlerts(shopId) {
  const subscription = await getShopSubscription(shopId);
  const usage = await getCurrentUsage(shopId);
  
  for (const metric of usage.metrics) {
    const percentage = (metric.current / metric.included) * 100;
    
    // 80% warning
    if (percentage >= 80 && percentage < 100) {
      const alreadyNotified = await redis.get(
        `usage_alert:${shopId}:${metric.name}:80`
      );
      
      if (!alreadyNotified) {
        await sendUsageAlert(shopId, metric, 80);
        await redis.set(
          `usage_alert:${shopId}:${metric.name}:80`,
          '1',
          'EX',
          86400 * 7  // Reset after 7 days
        );
      }
    }
    
    // 100% notification
    if (percentage >= 100) {
      const alreadyNotified = await redis.get(
        `usage_alert:${shopId}:${metric.name}:100`
      );
      
      if (!alreadyNotified) {
        await sendUsageAlert(shopId, metric, 100);
        await redis.set(
          `usage_alert:${shopId}:${metric.name}:100`,
          '1',
          'EX',
          86400 * 7
        );
      }
    }
  }
}

async function sendUsageAlert(shopId, metric, threshold) {
  const shop = await getShop(shopId);
  
  await sendEmail(shop.email, 'usage_alert', {
    shopName: shop.name,
    metricName: metric.name,
    currentUsage: metric.current,
    includedUsage: metric.included,
    threshold,
    overageRate: formatCurrency(metric.overageRate)
  });
}

Rate Limiting

Optionally limit usage to prevent unexpected bills:
// middleware/usageLimiter.js
import { usageService } from '../services/usageService';

export function limitUsage(metric, hardLimit) {
  return async (req, res, next) => {
    const shopId = req.shopId;
    const currentUsage = await usageService.getCurrentUsage(shopId, metric);
    
    if (currentUsage >= hardLimit) {
      return res.status(429).json({
        error: 'Usage limit exceeded',
        message: `You've reached your ${metric} limit. Please upgrade your plan.`,
        currentUsage,
        limit: hardLimit
      });
    }
    
    next();
  };
}

// Usage
app.post(
  '/api/orders/process',
  limitUsage('orders', 10000),
  trackUsage('orders'),
  processOrder
);

Billing Period End

Handle end-of-period usage finalization:
// Cron job: Run at end of each billing period
async function finalizeBillingPeriod(shopId) {
  // Flush all remaining usage
  await usageService.flushAllUsage(shopId);
  
  // Get final usage summary
  const usage = await getUsageSummary(shopId);
  
  // Store for historical records
  await storeUsageHistory(shopId, usage);
  
  // Reset alert flags
  await resetUsageAlerts(shopId);
}

Pricing Tiers

Implement graduated pricing:
function calculateUsageCost(usage, tiers) {
  let cost = 0;
  let remaining = usage;
  
  for (let i = 0; i < tiers.length; i++) {
    const tier = tiers[i];
    const prevLimit = i > 0 ? tiers[i - 1].upTo : 0;
    const tierLimit = tier.upTo === 'inf' ? Infinity : tier.upTo;
    const tierQuantity = Math.min(remaining, tierLimit - prevLimit);
    
    if (tierQuantity > 0) {
      cost += tierQuantity * tier.unitAmount;
      remaining -= tierQuantity;
    }
    
    if (remaining <= 0) break;
  }
  
  return cost;
}

// Example
const tiers = [
  { upTo: 100, unitAmount: 0 },
  { upTo: 1000, unitAmount: 10 },
  { upTo: 10000, unitAmount: 5 },
  { upTo: 'inf', unitAmount: 2 }
];

calculateUsageCost(5000, tiers);
// = (100 * 0) + (900 * 10) + (4000 * 5) = $290.00 (29000 cents)

Best Practices

Don’t report every event individually. Buffer and batch to reduce API calls.
Merchants want to see their current usage. Provide a dashboard or API endpoint.
Notify merchants at 80% and 100% of included usage to avoid bill shock.
Let merchants set spending limits or hard usage caps for peace of mind.
A free tier reduces friction and lets merchants see value before paying.
Use graduated tiers with decreasing per-unit costs as usage grows.

Common Metrics

MetricUse Case
ordersOrder management apps
emailsEmail marketing apps
api_callsAPI-heavy integrations
storage_gbStorage apps
products_syncedMarketplace integrations
sms_sentSMS notification apps
shipmentsShipping apps

See Also