.do
Implementation Guides

Pricing and Billing Guide

Complete guide to implementing pricing models, usage tracking, billing systems, and payment integration

Learn how to implement comprehensive pricing and billing systems for your Services-as-Software, from simple per-use pricing to complex tiered subscriptions with usage tracking and automated invoicing.

Why Pricing and Billing Matters

Effective pricing and billing is critical for Services-as-Software success:

  1. Revenue Generation - Convert service value into sustainable revenue
  2. Customer Clarity - Transparent pricing builds trust and reduces friction
  3. Business Insights - Usage data drives product and business decisions
  4. Scalability - Automated billing enables growth without overhead
  5. Fair Value Exchange - Align pricing with customer value received
  6. Compliance - Meet tax, accounting, and regulatory requirements

Pricing Model Selection

Per-Use Pricing

Charge customers based on actual usage - ideal for unpredictable or variable workloads:

import $ from 'sdk.do'

const emailService = await $.Service.create({
  name: 'Email Delivery Service',

  pricing: {
    model: 'per-use',
    rate: 0.001, // $0.001 per email
    unit: 'email',
    currency: 'USD',

    // Minimum charge
    minimum: {
      charge: 1.0, // Minimum $1
      unit: 'transaction',
    },

    // Pricing tiers based on volume
    tiers: [
      { from: 0, to: 1000, rate: 0.001 }, // First 1000: $0.001 each
      { from: 1000, to: 10000, rate: 0.0008 }, // Next 9000: $0.0008 each
      { from: 10000, to: null, rate: 0.0005 }, // Above 10000: $0.0005 each
    ],
  },
})

Subscription Pricing

Charge recurring fees for ongoing access - ideal for predictable revenue:

const analyticsService = await $.Service.create({
  name: 'Business Analytics Platform',

  pricing: {
    model: 'subscription',
    currency: 'USD',

    plans: [
      {
        id: 'starter',
        name: 'Starter',
        price: 29.0,
        interval: 'month',
        limits: {
          reports: 10,
          dashboards: 3,
          users: 5,
          dataRetention: 30, // days
        },
        features: ['basic-analytics', 'email-reports', 'api-access'],
      },
      {
        id: 'professional',
        name: 'Professional',
        price: 99.0,
        interval: 'month',
        limits: {
          reports: 50,
          dashboards: 15,
          users: 25,
          dataRetention: 90,
        },
        features: ['advanced-analytics', 'custom-dashboards', 'email-reports', 'api-access', 'white-label'],
      },
      {
        id: 'enterprise',
        name: 'Enterprise',
        price: 499.0,
        interval: 'month',
        limits: {
          reports: null, // unlimited
          dashboards: null,
          users: null,
          dataRetention: 365,
        },
        features: [
          'advanced-analytics',
          'custom-dashboards',
          'email-reports',
          'api-access',
          'white-label',
          'priority-support',
          'dedicated-account-manager',
          'sla-guarantee',
        ],
      },
    ],

    // Annual discount
    annualDiscount: 0.2, // 20% off annual plans
  },
})

Tiered Pricing

Combine usage-based pricing with volume discounts:

const apiService = await $.Service.create({
  name: 'API Gateway Service',

  pricing: {
    model: 'tiered',
    unit: 'request',
    currency: 'USD',

    tiers: [
      {
        name: 'First 1 million',
        from: 0,
        to: 1000000,
        rate: 0.0001, // $0.0001 per request
        fixedFee: 0,
      },
      {
        name: 'Next 9 million',
        from: 1000000,
        to: 10000000,
        rate: 0.00008,
        fixedFee: 100, // $100 base + per-request
      },
      {
        name: 'Above 10 million',
        from: 10000000,
        to: null,
        rate: 0.00005,
        fixedFee: 800,
      },
    ],

    // Calculate total cost
    calculator: (usage) => {
      let cost = 0
      let remaining = usage

      for (const tier of pricing.tiers) {
        if (remaining <= 0) break

        const tierSize = tier.to ? tier.to - tier.from : remaining
        const unitsInTier = Math.min(remaining, tierSize)

        cost += tier.fixedFee + unitsInTier * tier.rate
        remaining -= unitsInTier
      }

      return cost
    },
  },
})

Hybrid Pricing

Combine subscription base with usage overages:

const videoService = await $.Service.create({
  name: 'Video Processing Service',

  pricing: {
    model: 'hybrid',
    currency: 'USD',

    // Base subscription
    subscription: {
      plans: [
        {
          id: 'basic',
          name: 'Basic',
          price: 49.0,
          interval: 'month',
          included: {
            processingMinutes: 100,
            storage: 10, // GB
            bandwidth: 100, // GB
          },
        },
        {
          id: 'pro',
          name: 'Pro',
          price: 199.0,
          interval: 'month',
          included: {
            processingMinutes: 500,
            storage: 100,
            bandwidth: 1000,
          },
        },
      ],
    },

    // Overage charges
    overages: {
      processingMinutes: {
        rate: 0.1, // $0.10 per minute over limit
        unit: 'minute',
      },
      storage: {
        rate: 0.1, // $0.10 per GB per month over limit
        unit: 'gb-month',
      },
      bandwidth: {
        rate: 0.05, // $0.05 per GB over limit
        unit: 'gb',
      },
    },

    // Calculate bill
    calculator: (plan, usage) => {
      let cost = plan.price

      // Calculate overages
      for (const [resource, limit] of Object.entries(plan.included)) {
        const used = usage[resource]
        if (used > limit) {
          const overage = used - limit
          cost += overage * pricing.overages[resource].rate
        }
      }

      return cost
    },
  },
})

Value-Based Pricing

Price based on value delivered rather than resources consumed:

const leadGenService = await $.Service.create({
  name: 'B2B Lead Generation Service',

  pricing: {
    model: 'value-based',
    currency: 'USD',

    // Price per qualified lead
    perLead: {
      base: 50.0,

      // Adjust based on lead quality
      qualityMultipliers: {
        'decision-maker': 2.0, // 2x for decision makers
        'high-intent': 1.5, // 1.5x for high intent
        warm: 1.0, // Base rate
        cold: 0.5, // 0.5x for cold leads
      },

      // Adjust based on industry
      industryMultipliers: {
        'enterprise-software': 2.5,
        'financial-services': 2.0,
        healthcare: 1.8,
        retail: 1.0,
        other: 0.8,
      },
    },

    // Success-based pricing
    successBased: {
      enabled: true,
      baseRate: 25.0, // Lower base rate
      conversionBonus: 100.0, // Bonus when lead converts
      conversionWindow: 90, // days to track conversion
    },

    // Calculate lead value
    calculator: (lead) => {
      let price = pricing.perLead.base

      // Apply quality multiplier
      if (lead.quality) {
        price *= pricing.perLead.qualityMultipliers[lead.quality] || 1.0
      }

      // Apply industry multiplier
      if (lead.industry) {
        price *= pricing.perLead.industryMultipliers[lead.industry] || 1.0
      }

      return price
    },
  },
})

Usage Tracking Implementation

Basic Usage Tracking

Track service usage for billing:

import { db, on, send } from 'sdk.do'

// Track usage when service executes
on($.ServiceExecution.complete, async (execution) => {
  // Record usage
  await db.create($.UsageRecord, {
    customerId: execution.customerId,
    serviceId: execution.serviceId,
    timestamp: new Date(),
    quantity: execution.quantity || 1,
    unit: execution.service.pricing.unit,
    metadata: {
      requestId: execution.requestId,
      duration: execution.duration,
      inputSize: execution.inputSize,
      outputSize: execution.outputSize,
    },
  })

  // Update customer usage totals
  await db.update($.Customer, {
    where: { id: execution.customerId },
    data: {
      totalUsage: { increment: execution.quantity || 1 },
      lastUsage: new Date(),
    },
  })
})

Advanced Usage Tracking

Track multiple metrics and aggregate usage:

import { db } from 'sdk.do'

class UsageTracker {
  async trackUsage(params: { customerId: string; serviceId: string; metrics: Record<string, number>; metadata?: Record<string, any> }) {
    const { customerId, serviceId, metrics, metadata } = params

    // Create usage record
    const record = await db.create($.UsageRecord, {
      customerId,
      serviceId,
      timestamp: new Date(),
      metrics,
      metadata,
    })

    // Update aggregated usage
    await this.updateAggregates(customerId, serviceId, metrics)

    // Check for limit violations
    await this.checkLimits(customerId, serviceId, metrics)

    return record
  }

  async updateAggregates(customerId: string, serviceId: string, metrics: Record<string, number>) {
    const now = new Date()
    const periodStart = new Date(now.getFullYear(), now.getMonth(), 1)

    // Get or create period aggregate
    let aggregate = await db.findOne($.UsageAggregate, {
      where: {
        customerId,
        serviceId,
        period: 'month',
        periodStart,
      },
    })

    if (!aggregate) {
      aggregate = await db.create($.UsageAggregate, {
        customerId,
        serviceId,
        period: 'month',
        periodStart,
        periodEnd: new Date(now.getFullYear(), now.getMonth() + 1, 0),
        metrics: {},
      })
    }

    // Update metrics
    for (const [metric, value] of Object.entries(metrics)) {
      aggregate.metrics[metric] = (aggregate.metrics[metric] || 0) + value
    }

    await db.update($.UsageAggregate, {
      where: { id: aggregate.id },
      data: { metrics: aggregate.metrics },
    })
  }

  async checkLimits(customerId: string, serviceId: string, metrics: Record<string, number>) {
    // Get customer's plan
    const subscription = await db.findOne($.Subscription, {
      where: { customerId, status: 'active' },
      include: { plan: true },
    })

    if (!subscription?.plan?.limits) return

    // Check current period usage
    const aggregate = await db.findOne($.UsageAggregate, {
      where: {
        customerId,
        serviceId,
        period: 'month',
        periodStart: {
          gte: new Date(new Date().getFullYear(), new Date().getMonth(), 1),
        },
      },
    })

    // Check each limit
    for (const [metric, limit] of Object.entries(subscription.plan.limits)) {
      const usage = aggregate?.metrics[metric] || 0

      // Approaching limit (80%)
      if (usage >= limit * 0.8 && usage < limit) {
        await send($.Alert.create, {
          type: 'usage-warning',
          customerId,
          metric,
          usage,
          limit,
          threshold: 0.8,
        })
      }

      // Exceeded limit
      if (usage >= limit) {
        await send($.Alert.create, {
          type: 'usage-exceeded',
          customerId,
          metric,
          usage,
          limit,
        })

        // Handle based on plan settings
        if (subscription.plan.enforceLimits) {
          throw new Error(`Usage limit exceeded for ${metric}: ${usage}/${limit}`)
        }
      }
    }
  }

  async getUsage(customerId: string, period: { start: Date; end: Date }) {
    return await db.list($.UsageRecord, {
      where: {
        customerId,
        timestamp: {
          gte: period.start,
          lte: period.end,
        },
      },
      orderBy: { timestamp: 'desc' },
    })
  }

  async getAggregateUsage(customerId: string, serviceId: string, period: 'day' | 'month' | 'year') {
    return await db.findOne($.UsageAggregate, {
      where: {
        customerId,
        serviceId,
        period,
        periodStart: {
          gte: this.getPeriodStart(period),
        },
      },
    })
  }

  private getPeriodStart(period: 'day' | 'month' | 'year'): Date {
    const now = new Date()
    switch (period) {
      case 'day':
        return new Date(now.getFullYear(), now.getMonth(), now.getDate())
      case 'month':
        return new Date(now.getFullYear(), now.getMonth(), 1)
      case 'year':
        return new Date(now.getFullYear(), 0, 1)
    }
  }
}

// Usage
const tracker = new UsageTracker()

// Track usage
await tracker.trackUsage({
  customerId: 'cust-123',
  serviceId: 'svc-456',
  metrics: {
    requests: 1,
    tokensUsed: 1500,
    duration: 2500, // ms
    bandwidth: 150000, // bytes
  },
  metadata: {
    endpoint: '/api/generate',
    model: 'gpt-5',
  },
})

// Get usage
const usage = await tracker.getUsage('cust-123', {
  start: new Date('2025-10-01'),
  end: new Date('2025-10-31'),
})

Billing Calculation Logic

Simple Billing Calculator

Calculate bills from usage:

import { db } from 'sdk.do'

class BillingCalculator {
  async calculateBill(customerId: string, period: { start: Date; end: Date }) {
    // Get customer's subscription
    const subscription = await db.findOne($.Subscription, {
      where: { customerId, status: 'active' },
      include: { plan: true },
    })

    // Get usage for period
    const usage = await db.list($.UsageRecord, {
      where: {
        customerId,
        timestamp: { gte: period.start, lte: period.end },
      },
    })

    let total = 0

    // Base subscription fee
    if (subscription) {
      total += subscription.plan.price
    }

    // Calculate usage charges
    for (const record of usage) {
      const service = await db.findOne($.Service, {
        where: { id: record.serviceId },
      })

      // Apply pricing model
      const cost = this.calculateUsageCost(service, record)
      total += cost
    }

    return {
      customerId,
      period,
      subscription: subscription
        ? {
            planId: subscription.plan.id,
            planName: subscription.plan.name,
            amount: subscription.plan.price,
          }
        : null,
      usage: {
        records: usage.length,
        amount: total - (subscription?.plan.price || 0),
      },
      total,
      currency: subscription?.plan.currency || 'USD',
    }
  }

  private calculateUsageCost(service: any, record: any): number {
    const pricing = service.pricing

    switch (pricing.model) {
      case 'per-use':
        return this.calculatePerUse(pricing, record)
      case 'tiered':
        return this.calculateTiered(pricing, record)
      default:
        return 0
    }
  }

  private calculatePerUse(pricing: any, record: any): number {
    return (record.quantity || 1) * pricing.rate
  }

  private calculateTiered(pricing: any, record: any): number {
    let cost = 0
    let remaining = record.quantity || 1

    for (const tier of pricing.tiers) {
      if (remaining <= 0) break

      const tierSize = tier.to ? tier.to - tier.from : remaining
      const unitsInTier = Math.min(remaining, tierSize)

      cost += unitsInTier * tier.rate
      remaining -= unitsInTier
    }

    return cost
  }
}

// Usage
const calculator = new BillingCalculator()

const bill = await calculator.calculateBill('cust-123', {
  start: new Date('2025-10-01'),
  end: new Date('2025-10-31'),
})

console.log(`Total: ${bill.currency} ${bill.total.toFixed(2)}`)

Advanced Billing with Discounts and Credits

Handle complex billing scenarios:

class AdvancedBillingCalculator extends BillingCalculator {
  async calculateBill(customerId: string, period: { start: Date; end: Date }) {
    // Get base bill
    const baseBill = await super.calculateBill(customerId, period)

    // Apply discounts
    const discounts = await this.calculateDiscounts(customerId, baseBill)

    // Apply credits
    const credits = await this.applyCredits(customerId, baseBill.total)

    // Calculate taxes
    const taxes = await this.calculateTaxes(customerId, baseBill.total - credits.applied)

    return {
      ...baseBill,
      discounts,
      credits,
      taxes,
      finalTotal: baseBill.total - discounts.total - credits.applied + taxes.total,
    }
  }

  private async calculateDiscounts(customerId: string, bill: any) {
    const discounts = []
    let total = 0

    // Volume discount
    if (bill.usage.records > 1000) {
      const discount = {
        type: 'volume',
        description: 'High volume discount',
        percentage: 0.1, // 10%
        amount: bill.usage.amount * 0.1,
      }
      discounts.push(discount)
      total += discount.amount
    }

    // Promotional discount
    const promo = await db.findOne($.Promotion, {
      where: {
        customerId,
        validFrom: { lte: new Date() },
        validTo: { gte: new Date() },
        active: true,
      },
    })

    if (promo) {
      const discount = {
        type: 'promotion',
        code: promo.code,
        description: promo.description,
        percentage: promo.percentage,
        amount: bill.total * promo.percentage,
      }
      discounts.push(discount)
      total += discount.amount
    }

    // Loyalty discount
    const customer = await db.findOne($.Customer, {
      where: { id: customerId },
    })

    if (customer.loyaltyTier) {
      const discount = {
        type: 'loyalty',
        tier: customer.loyaltyTier,
        description: `${customer.loyaltyTier} tier discount`,
        percentage: this.getLoyaltyDiscount(customer.loyaltyTier),
        amount: bill.total * this.getLoyaltyDiscount(customer.loyaltyTier),
      }
      discounts.push(discount)
      total += discount.amount
    }

    return { discounts, total }
  }

  private async applyCredits(customerId: string, amount: number) {
    const availableCredits = await db.findOne($.CustomerCredits, {
      where: { customerId },
    })

    if (!availableCredits || availableCredits.balance <= 0) {
      return { available: 0, applied: 0, remaining: 0 }
    }

    const applied = Math.min(availableCredits.balance, amount)

    // Update credit balance
    await db.update($.CustomerCredits, {
      where: { customerId },
      data: {
        balance: { decrement: applied },
      },
    })

    return {
      available: availableCredits.balance,
      applied,
      remaining: availableCredits.balance - applied,
    }
  }

  private async calculateTaxes(customerId: string, amount: number) {
    const customer = await db.findOne($.Customer, {
      where: { id: customerId },
      include: { billingAddress: true },
    })

    const taxRate = await this.getTaxRate(customer.billingAddress)

    return {
      rate: taxRate,
      total: amount * taxRate,
      region: customer.billingAddress.region,
    }
  }

  private async getTaxRate(address: any): Promise<number> {
    // Simplified tax calculation
    // In production, use a tax service like TaxJar or Avalara
    const taxRates: Record<string, number> = {
      CA: 0.0725, // California
      NY: 0.08, // New York
      TX: 0.0625, // Texas
      // ... other states
    }

    return taxRates[address.state] || 0
  }

  private getLoyaltyDiscount(tier: string): number {
    const discounts: Record<string, number> = {
      bronze: 0.05,
      silver: 0.1,
      gold: 0.15,
      platinum: 0.2,
    }

    return discounts[tier.toLowerCase()] || 0
  }
}

Invoice Generation

Basic Invoice Generator

Create professional invoices:

import { db, send } from 'sdk.do'

class InvoiceGenerator {
  async generateInvoice(bill: any) {
    // Create invoice record
    const invoice = await db.create($.Invoice, {
      customerId: bill.customerId,
      number: await this.getNextInvoiceNumber(),
      date: new Date(),
      dueDate: this.calculateDueDate(),
      period: {
        start: bill.period.start,
        end: bill.period.end,
      },
      items: this.buildLineItems(bill),
      subtotal: bill.total,
      discounts: bill.discounts?.total || 0,
      credits: bill.credits?.applied || 0,
      taxes: bill.taxes?.total || 0,
      total: bill.finalTotal,
      currency: bill.currency,
      status: 'pending',
    })

    // Send invoice to customer
    await send($.Email.send, {
      to: bill.customer.email,
      subject: `Invoice ${invoice.number} - ${bill.customer.name}`,
      template: 'invoice',
      data: {
        invoice,
        customer: bill.customer,
      },
    })

    return invoice
  }

  private async getNextInvoiceNumber(): Promise<string> {
    const lastInvoice = await db.findOne($.Invoice, {
      orderBy: { number: 'desc' },
    })

    if (!lastInvoice) {
      return 'INV-0001'
    }

    const lastNumber = parseInt(lastInvoice.number.split('-')[1])
    return `INV-${String(lastNumber + 1).padStart(4, '0')}`
  }

  private calculateDueDate(): Date {
    const dueDate = new Date()
    dueDate.setDate(dueDate.getDate() + 30) // 30 days
    return dueDate
  }

  private buildLineItems(bill: any): any[] {
    const items = []

    // Subscription item
    if (bill.subscription) {
      items.push({
        description: `${bill.subscription.planName} Subscription`,
        quantity: 1,
        rate: bill.subscription.amount,
        amount: bill.subscription.amount,
      })
    }

    // Usage items
    if (bill.usage.amount > 0) {
      items.push({
        description: 'Usage Charges',
        quantity: bill.usage.records,
        rate: bill.usage.amount / bill.usage.records,
        amount: bill.usage.amount,
      })
    }

    // Discount items
    if (bill.discounts) {
      for (const discount of bill.discounts.discounts) {
        items.push({
          description: discount.description,
          quantity: 1,
          rate: -discount.amount,
          amount: -discount.amount,
        })
      }
    }

    // Tax items
    if (bill.taxes) {
      items.push({
        description: `Tax (${bill.taxes.region})`,
        quantity: 1,
        rate: bill.taxes.total,
        amount: bill.taxes.total,
      })
    }

    return items
  }
}

PDF Invoice Generation

Generate PDF invoices:

class PDFInvoiceGenerator extends InvoiceGenerator {
  async generateInvoice(bill: any) {
    // Create base invoice
    const invoice = await super.generateInvoice(bill)

    // Generate PDF
    const pdf = await this.createPDF(invoice, bill)

    // Store PDF
    const pdfUrl = await this.storePDF(invoice.number, pdf)

    // Update invoice with PDF URL
    await db.update($.Invoice, {
      where: { id: invoice.id },
      data: { pdfUrl },
    })

    return { ...invoice, pdfUrl }
  }

  private async createPDF(invoice: any, bill: any): Promise<Buffer> {
    // Using a PDF generation library (e.g., puppeteer, pdfmake)
    const html = this.generateInvoiceHTML(invoice, bill)

    // Convert HTML to PDF (pseudo-code)
    const pdf = await htmlToPDF(html)

    return pdf
  }

  private generateInvoiceHTML(invoice: any, bill: any): string {
    return `
      <!DOCTYPE html>
      <html>
        <head>
          <style>
            body { font-family: Arial, sans-serif; }
            .header { text-align: center; margin-bottom: 30px; }
            .invoice-details { margin-bottom: 20px; }
            table { width: 100%; border-collapse: collapse; }
            th, td { padding: 10px; text-align: left; border-bottom: 1px solid #ddd; }
            .total { font-weight: bold; font-size: 1.2em; }
          </style>
        </head>
        <body>
          <div class="header">
            <h1>INVOICE</h1>
            <p>Invoice Number: ${invoice.number}</p>
            <p>Date: ${invoice.date.toLocaleDateString()}</p>
          </div>

          <div class="invoice-details">
            <p><strong>Bill To:</strong></p>
            <p>${bill.customer.name}</p>
            <p>${bill.customer.email}</p>
          </div>

          <table>
            <thead>
              <tr>
                <th>Description</th>
                <th>Quantity</th>
                <th>Rate</th>
                <th>Amount</th>
              </tr>
            </thead>
            <tbody>
              ${invoice.items
                .map(
                  (item) => `
                <tr>
                  <td>${item.description}</td>
                  <td>${item.quantity}</td>
                  <td>${invoice.currency} ${item.rate.toFixed(2)}</td>
                  <td>${invoice.currency} ${item.amount.toFixed(2)}</td>
                </tr>
              `
                )
                .join('')}
            </tbody>
            <tfoot>
              <tr class="total">
                <td colspan="3">Total</td>
                <td>${invoice.currency} ${invoice.total.toFixed(2)}</td>
              </tr>
            </tfoot>
          </table>

          <p style="margin-top: 30px;">
            <strong>Due Date:</strong> ${invoice.dueDate.toLocaleDateString()}
          </p>
        </body>
      </html>
    `
  }

  private async storePDF(invoiceNumber: string, pdf: Buffer): Promise<string> {
    // Upload to storage (e.g., R2, S3)
    const filename = `invoices/${invoiceNumber}.pdf`
    const url = await uploadToStorage(filename, pdf)
    return url
  }
}

Payment Integration

Stripe Integration

Integrate with Stripe for payment processing:

import Stripe from 'stripe'
import { db, on, send } from 'sdk.do'

class StripePaymentProcessor {
  private stripe: Stripe

  constructor(apiKey: string) {
    this.stripe = new Stripe(apiKey, {
      apiVersion: '2023-10-16',
    })
  }

  async createCustomer(customer: any) {
    const stripeCustomer = await this.stripe.customers.create({
      email: customer.email,
      name: customer.name,
      metadata: {
        customerId: customer.id,
      },
    })

    // Store Stripe customer ID
    await db.update($.Customer, {
      where: { id: customer.id },
      data: {
        stripeCustomerId: stripeCustomer.id,
      },
    })

    return stripeCustomer
  }

  async createSubscription(customerId: string, planId: string) {
    // Get customer
    const customer = await db.findOne($.Customer, {
      where: { id: customerId },
    })

    // Get plan
    const plan = await db.findOne($.Plan, {
      where: { id: planId },
    })

    // Create Stripe subscription
    const subscription = await this.stripe.subscriptions.create({
      customer: customer.stripeCustomerId,
      items: [{ price: plan.stripePriceId }],
      metadata: {
        customerId,
        planId,
      },
    })

    // Store subscription
    await db.create($.Subscription, {
      customerId,
      planId,
      stripeSubscriptionId: subscription.id,
      status: subscription.status,
      currentPeriodStart: new Date(subscription.current_period_start * 1000),
      currentPeriodEnd: new Date(subscription.current_period_end * 1000),
    })

    return subscription
  }

  async chargeInvoice(invoiceId: string) {
    // Get invoice
    const invoice = await db.findOne($.Invoice, {
      where: { id: invoiceId },
      include: { customer: true },
    })

    // Create payment intent
    const paymentIntent = await this.stripe.paymentIntents.create({
      amount: Math.round(invoice.total * 100), // Convert to cents
      currency: invoice.currency.toLowerCase(),
      customer: invoice.customer.stripeCustomerId,
      description: `Invoice ${invoice.number}`,
      metadata: {
        invoiceId: invoice.id,
      },
    })

    // Update invoice
    await db.update($.Invoice, {
      where: { id: invoiceId },
      data: {
        stripePaymentIntentId: paymentIntent.id,
        status: 'processing',
      },
    })

    return paymentIntent
  }

  async handleWebhook(event: Stripe.Event) {
    switch (event.type) {
      case 'payment_intent.succeeded':
        await this.handlePaymentSuccess(event.data.object as Stripe.PaymentIntent)
        break

      case 'payment_intent.payment_failed':
        await this.handlePaymentFailed(event.data.object as Stripe.PaymentIntent)
        break

      case 'invoice.payment_succeeded':
        await this.handleInvoicePaymentSuccess(event.data.object as Stripe.Invoice)
        break

      case 'customer.subscription.updated':
        await this.handleSubscriptionUpdate(event.data.object as Stripe.Subscription)
        break

      case 'customer.subscription.deleted':
        await this.handleSubscriptionCanceled(event.data.object as Stripe.Subscription)
        break
    }
  }

  private async handlePaymentSuccess(paymentIntent: Stripe.PaymentIntent) {
    const invoiceId = paymentIntent.metadata.invoiceId

    await db.update($.Invoice, {
      where: { id: invoiceId },
      data: {
        status: 'paid',
        paidAt: new Date(),
      },
    })

    await send($.Email.send, {
      to: paymentIntent.receipt_email,
      subject: 'Payment Confirmed',
      template: 'payment-confirmation',
      data: { invoiceId },
    })
  }

  private async handlePaymentFailed(paymentIntent: Stripe.PaymentIntent) {
    const invoiceId = paymentIntent.metadata.invoiceId

    await db.update($.Invoice, {
      where: { id: invoiceId },
      data: {
        status: 'failed',
      },
    })

    await send($.Email.send, {
      to: paymentIntent.receipt_email,
      subject: 'Payment Failed',
      template: 'payment-failed',
      data: { invoiceId },
    })
  }

  private async handleInvoicePaymentSuccess(invoice: Stripe.Invoice) {
    // Update subscription status
    if (invoice.subscription) {
      await db.update($.Subscription, {
        where: { stripeSubscriptionId: invoice.subscription as string },
        data: {
          status: 'active',
        },
      })
    }
  }

  private async handleSubscriptionUpdate(subscription: Stripe.Subscription) {
    await db.update($.Subscription, {
      where: { stripeSubscriptionId: subscription.id },
      data: {
        status: subscription.status,
        currentPeriodStart: new Date(subscription.current_period_start * 1000),
        currentPeriodEnd: new Date(subscription.current_period_end * 1000),
      },
    })
  }

  private async handleSubscriptionCanceled(subscription: Stripe.Subscription) {
    await db.update($.Subscription, {
      where: { stripeSubscriptionId: subscription.id },
      data: {
        status: 'canceled',
        canceledAt: new Date(),
      },
    })
  }
}

// Setup webhook endpoint
on($.Webhook.received, async (webhook) => {
  if (webhook.source !== 'stripe') return

  const processor = new StripePaymentProcessor(process.env.STRIPE_SECRET_KEY!)
  await processor.handleWebhook(webhook.data)
})

Subscription Management

Subscription Lifecycle

Manage subscription lifecycle:

import { db, on, send } from 'sdk.do'

class SubscriptionManager {
  async createSubscription(customerId: string, planId: string) {
    // Create subscription
    const subscription = await db.create($.Subscription, {
      customerId,
      planId,
      status: 'active',
      startDate: new Date(),
      currentPeriodStart: new Date(),
      currentPeriodEnd: this.calculatePeriodEnd('month'),
    })

    // Send confirmation
    await send($.Email.send, {
      customerId,
      template: 'subscription-created',
      data: { subscription },
    })

    return subscription
  }

  async upgradeSubscription(subscriptionId: string, newPlanId: string) {
    const subscription = await db.findOne($.Subscription, {
      where: { id: subscriptionId },
      include: { plan: true, customer: true },
    })

    const newPlan = await db.findOne($.Plan, {
      where: { id: newPlanId },
    })

    // Calculate prorated credit
    const credit = this.calculateProratedCredit(subscription, newPlan)

    // Apply credit
    if (credit > 0) {
      await db.update($.CustomerCredits, {
        where: { customerId: subscription.customerId },
        data: {
          balance: { increment: credit },
        },
      })
    }

    // Update subscription
    await db.update($.Subscription, {
      where: { id: subscriptionId },
      data: {
        planId: newPlanId,
        upgradedAt: new Date(),
      },
    })

    return subscription
  }

  async downgradeSubscription(subscriptionId: string, newPlanId: string) {
    // Schedule downgrade for end of period
    await db.update($.Subscription, {
      where: { id: subscriptionId },
      data: {
        scheduledPlanId: newPlanId,
        scheduledChangeDate: this.calculatePeriodEnd('month'),
      },
    })
  }

  async cancelSubscription(subscriptionId: string, immediate = false) {
    if (immediate) {
      await db.update($.Subscription, {
        where: { id: subscriptionId },
        data: {
          status: 'canceled',
          canceledAt: new Date(),
        },
      })
    } else {
      // Cancel at end of period
      await db.update($.Subscription, {
        where: { id: subscriptionId },
        data: {
          cancelAtPeriodEnd: true,
        },
      })
    }
  }

  async renewSubscription(subscriptionId: string) {
    const subscription = await db.findOne($.Subscription, {
      where: { id: subscriptionId },
      include: { plan: true, customer: true },
    })

    // Calculate bill
    const calculator = new AdvancedBillingCalculator()
    const bill = await calculator.calculateBill(subscription.customerId, {
      start: subscription.currentPeriodStart,
      end: subscription.currentPeriodEnd,
    })

    // Generate invoice
    const invoiceGenerator = new InvoiceGenerator()
    const invoice = await invoiceGenerator.generateInvoice(bill)

    // Charge payment method
    const paymentProcessor = new StripePaymentProcessor(process.env.STRIPE_SECRET_KEY!)
    await paymentProcessor.chargeInvoice(invoice.id)

    // Update subscription period
    await db.update($.Subscription, {
      where: { id: subscriptionId },
      data: {
        currentPeriodStart: subscription.currentPeriodEnd,
        currentPeriodEnd: this.calculatePeriodEnd(subscription.plan.interval),
      },
    })
  }

  private calculateProratedCredit(subscription: any, newPlan: any): number {
    const now = new Date()
    const periodTotal = subscription.currentPeriodEnd.getTime() - subscription.currentPeriodStart.getTime()
    const periodRemaining = subscription.currentPeriodEnd.getTime() - now.getTime()
    const percentRemaining = periodRemaining / periodTotal

    const oldPlanCredit = subscription.plan.price * percentRemaining
    const newPlanCharge = newPlan.price * percentRemaining

    return Math.max(0, oldPlanCredit - newPlanCharge)
  }

  private calculatePeriodEnd(interval: 'month' | 'year'): Date {
    const date = new Date()
    if (interval === 'month') {
      date.setMonth(date.getMonth() + 1)
    } else {
      date.setFullYear(date.getFullYear() + 1)
    }
    return date
  }
}

// Automatic renewal
on($.Subscription.periodEnding, async (subscription) => {
  if (subscription.cancelAtPeriodEnd) {
    await new SubscriptionManager().cancelSubscription(subscription.id, true)
  } else {
    await new SubscriptionManager().renewSubscription(subscription.id)
  }
})

Complete Billing System Example

Putting it all together:

import { db, on, send } from 'sdk.do'

// Comprehensive billing orchestrator
class BillingOrchestrator {
  private usageTracker = new UsageTracker()
  private calculator = new AdvancedBillingCalculator()
  private invoiceGenerator = new PDFInvoiceGenerator()
  private paymentProcessor = new StripePaymentProcessor(process.env.STRIPE_SECRET_KEY!)
  private subscriptionManager = new SubscriptionManager()

  async processMonthlyBilling() {
    // Get all active customers
    const customers = await db.list($.Customer, {
      where: { status: 'active' },
    })

    for (const customer of customers) {
      await this.procesCustomerBilling(customer.id)
    }
  }

  async processCustomerBilling(customerId: string) {
    try {
      // Calculate bill
      const period = this.getCurrentBillingPeriod()
      const bill = await this.calculator.calculateBill(customerId, period)

      // Generate invoice
      const invoice = await this.invoiceGenerator.generateInvoice(bill)

      // Charge customer
      await this.paymentProcessor.chargeInvoice(invoice.id)

      // Send notification
      await send($.Email.send, {
        customerId,
        template: 'monthly-invoice',
        data: { invoice },
      })
    } catch (error) {
      // Handle billing error
      await send($.Alert.create, {
        type: 'billing-error',
        customerId,
        error: error.message,
      })
    }
  }

  private getCurrentBillingPeriod() {
    const now = new Date()
    return {
      start: new Date(now.getFullYear(), now.getMonth(), 1),
      end: new Date(now.getFullYear(), now.getMonth() + 1, 0),
    }
  }
}

// Schedule monthly billing
on($.Schedule.monthly, async () => {
  const orchestrator = new BillingOrchestrator()
  await orchestrator.processMonthlyBilling()
})

Best Practices

1. Transparent Pricing

Always be clear about pricing:

// ✅ Good: Clear pricing
pricing: {
  model: 'per-use',
  rate: 0.001,
  unit: 'request',
  minimum: 1.0,
  description: '$0.001 per request, minimum $1.00 per transaction',
}

// ❌ Bad: Hidden fees
pricing: {
  rate: 0.001,
  // Missing: minimum charge, unit, description
}

2. Accurate Usage Tracking

Track usage reliably:

// Always track usage atomically
// Use transactions to ensure consistency
// Include metadata for debugging

3. Timely Invoicing

Send invoices promptly:

// Generate invoices immediately after billing period
// Send reminders before due date
// Handle failed payments gracefully

4. Clear Communication

Keep customers informed:

// Send confirmation emails
// Provide detailed invoices
// Alert on usage thresholds
// Notify about billing changes

5. Compliance

Follow regulations:

// Collect required tax information
// Calculate taxes correctly
// Retain records per regulations
// Provide clear terms of service

Next Steps