ScaledByDesign/Insights
ServicesPricingAboutContact
Book a Call
Scaled By Design

Fractional CTO + execution partner for revenue-critical systems.

Company

  • About
  • Services
  • Contact

Resources

  • Insights
  • Pricing
  • FAQ

Legal

  • Privacy Policy
  • Terms of Service

© 2026 ScaledByDesign. All rights reserved.

contact@scaledbydesign.com

On This Page

Subscriptions Are HardThe Core ArchitectureThe Subscription ModelThe Renewal PipelineFailed Payment Recovery (Dunning)Churn PreventionKey Metrics
  1. Insights
  2. Growth Ops
  3. Subscription Commerce Technical Architecture — Beyond Recurring Billing

Subscription Commerce Technical Architecture — Beyond Recurring Billing

June 12, 2026·ScaledByDesign·
subscriptionecommercebillingarchitecturesaas

Subscriptions Are Hard

A DTC brand launched a subscription box. They integrated Stripe Billing and called it done. Within 6 months they discovered: customers wanted to skip months, swap products, change frequency, pause during vacations, gift subscriptions to friends, and get discounts for annual commitments. Stripe Billing handled the payment part. Everything else required custom engineering.

The Core Architecture

┌─────────────────────────────────────────────────┐
│                  Subscription Engine              │
├─────────────┬──────────────┬────────────────────┤
│  Plan       │  Customer    │  Subscription       │
│  Management │  Management  │  Lifecycle          │
├─────────────┼──────────────┼────────────────────┤
│  Billing    │  Inventory   │  Communication      │
│  Engine     │  Forecasting │  Engine             │
├─────────────┴──────────────┴────────────────────┤
│         Analytics & Churn Prediction             │
└─────────────────────────────────────────────────┘

The Subscription Model

interface Subscription {
  id: string;
  customerId: string;
  planId: string;
  status: "active" | "paused" | "canceled" | "past_due" | "trialing";
  
  // Billing
  billingCycle: "weekly" | "biweekly" | "monthly" | "quarterly" | "annual";
  nextBillingDate: Date;
  currentPeriodStart: Date;
  currentPeriodEnd: Date;
  
  // Product selection
  items: SubscriptionItem[];
  
  // Flexibility
  skipNextRenewal: boolean;
  pausedUntil?: Date;
  
  // Discounts
  discountId?: string;
  trialEndDate?: Date;
  
  // Metadata
  cancelReason?: string;
  canceledAt?: Date;
  createdAt: Date;
}
 
interface SubscriptionItem {
  productId: string;
  variantId: string;
  quantity: number;
  priceOverride?: number; // Customer-specific pricing
  swappable: boolean;     // Can customer swap this item?
}

The Renewal Pipeline

// Daily job: process upcoming renewals
async function processRenewals() {
  const dueSubscriptions = await db.subscriptions.findMany({
    where: {
      status: "active",
      skipNextRenewal: false,
      nextBillingDate: { lte: addDays(new Date(), 3) }, // 3-day lookahead
    },
  });
 
  for (const sub of dueSubscriptions) {
    try {
      // 1. Validate inventory
      const inventoryCheck = await checkInventory(sub.items);
      if (!inventoryCheck.allAvailable) {
        await handleOutOfStock(sub, inventoryCheck.unavailable);
        continue;
      }
 
      // 2. Calculate price (discounts, loyalty tiers, etc.)
      const price = await calculateSubscriptionPrice(sub);
 
      // 3. Attempt payment
      const payment = await chargeCustomer(sub.customerId, price);
      
      if (payment.success) {
        // 4. Create order
        await createSubscriptionOrder(sub, payment);
        // 5. Update next billing date
        await advanceBillingCycle(sub);
        // 6. Send confirmation
        await sendRenewalConfirmation(sub);
      } else {
        await handleFailedPayment(sub, payment);
      }
    } catch (error) {
      await handleRenewalError(sub, error);
    }
  }
}

Failed Payment Recovery (Dunning)

async function handleFailedPayment(sub: Subscription, payment: PaymentResult) {
  const retrySchedule = [
    { daysAfterFailure: 1, action: "retry_payment" },
    { daysAfterFailure: 3, action: "retry_payment_notify" },
    { daysAfterFailure: 5, action: "retry_payment_urgent" },
    { daysAfterFailure: 7, action: "last_attempt" },
    { daysAfterFailure: 10, action: "cancel_subscription" },
  ];
 
  await db.subscriptions.update({
    where: { id: sub.id },
    data: {
      status: "past_due",
      failedPaymentCount: { increment: 1 },
    },
  });
 
  // Send "update your payment method" email with deep link
  await sendDunningEmail(sub, {
    updatePaymentUrl: `${BASE_URL}/account/payment?sub=${sub.id}`,
    retryDate: addDays(new Date(), 1),
  });
}

Churn Prevention

// Identify at-risk subscribers using behavioral signals
async function calculateChurnRisk(sub: Subscription): Promise<number> {
  const signals = {
    // Engagement signals
    daysSinceLastLogin: await getLastLoginDays(sub.customerId),
    supportTicketsLast30Days: await getTicketCount(sub.customerId, 30),
    productSwapsLast3Months: await getSwapCount(sub.id, 90),
    skippedRenewals: await getSkipCount(sub.id),
    
    // Payment signals
    failedPaymentsLast6Months: await getFailedPayments(sub.id, 180),
    
    // Satisfaction signals
    lastNpsScore: await getLatestNps(sub.customerId),
    reviewsLeft: await getReviewCount(sub.customerId),
  };
 
  // Simple scoring (replace with ML model when you have enough data)
  let riskScore = 0;
  if (signals.daysSinceLastLogin > 30) riskScore += 20;
  if (signals.supportTicketsLast30Days > 2) riskScore += 15;
  if (signals.skippedRenewals > 2) riskScore += 25;
  if (signals.failedPaymentsLast6Months > 1) riskScore += 20;
  if (signals.lastNpsScore !== null && signals.lastNpsScore < 7) riskScore += 20;
 
  return Math.min(riskScore, 100);
}
 
// Automated retention offers based on risk
async function triggerRetention(sub: Subscription, riskScore: number) {
  if (riskScore > 70) {
    await sendRetentionOffer(sub, { type: "discount", percent: 20, duration: 3 });
  } else if (riskScore > 50) {
    await sendRetentionOffer(sub, { type: "free_gift", nextRenewal: true });
  } else if (riskScore > 30) {
    await sendEngagementEmail(sub, { type: "product_education" });
  }
}

Key Metrics

Subscription health dashboard:
  → MRR (Monthly Recurring Revenue)
  → Churn rate (monthly and annual)
  → LTV:CAC ratio (target: > 3:1)
  → Average subscription duration
  → Recovery rate (dunning success %)
  → Skip rate (how often customers skip)
  → Swap rate (how often customers change products)
  → NPS score for subscribers vs. one-time buyers

Subscription commerce is a retention game disguised as an acquisition game. The technical architecture should optimize for flexibility (let customers modify their subscription easily), reliability (never miss a renewal or double-charge), and intelligence (predict churn before it happens). Build the billing integration first, but plan for the lifecycle management that makes subscriptions sustainable.

Previous
The CTO's First 100 Days at a Growth-Stage Startup
Insights
Subscription Commerce Technical Architecture — Beyond Recurring BillingCheckout Funnel Optimization — The Technical Fixes That Recover RevenueLoyalty Program Technical Architecture — Points, Tiers, and the Math Behind RetentionThe Shopify Plus Migration That Saved $400K/YearProduct Page Conversion Engineering — The Technical Optimizations That Move the NeedleThe Headless Commerce Migration Playbook — From Monolith to ComposableThe Subscription Box Tech Stack That Scales Past $10M ARRThe Post-Purchase Email Sequence That Drives 40% Repeat RevenueYour Post-Purchase Experience Is Leaving $2M on the TableYour Attribution Is Lying to You — Here's How to Fix ItThe DTC Tech Stack That Actually Scales Past $10MSubscription Churn Is a Systems Problem, Not a Marketing ProblemLifecycle Automation That CompoundsThe Checkout Optimization Playbook That Added $2M in RevenueWhy Your Shopify Store Breaks During Every SaleWhy Your Loyalty Program Isn't Working (And What to Build Instead)COGS Reporting Shouldn't Take 5 DaysHeadless Commerce: When It's Worth It and When It's a TrapThe Inventory Forecasting System That Stopped Our Client From OversellingPayment Processing Architecture for High-Volume Merchants

Ready to Ship?

Let's talk about your engineering challenges and how we can help.

Book a Call