Skip to main content

Guide

Stripe Billing vs Chargebee vs Recurly 2026

Stripe Billing vs Chargebee vs Recurly compared for SaaS subscription management. Trial periods, proration, dunning, metered billing, revenue recovery, and.

·PkgPulse Team·
0

Stripe Billing vs Chargebee vs Recurly: SaaS Subscriptions 2026

TL;DR

Recurring subscription billing is more complex than one-time payments — you need trials, proration when plans change, dunning for failed payments, tax compliance, and revenue reporting. Stripe Billing is the developer-first default — deep API control, excellent TypeScript SDK, Customer Portal for self-serve plan management, and tight integration with the rest of the Stripe ecosystem (Radar, Checkout, Connect). Chargebee is the subscription lifecycle specialist — handles dunning, revenue recovery, and complex pricing models with pre-built workflows that save engineering time; better for non-technical teams managing billing. Recurly is the enterprise subscription platform — advanced metered billing, hierarchical accounts, contract-based billing, and deep reporting for high-volume B2B. For developer-built SaaS products: Stripe Billing. For products where non-engineers manage billing logic: Chargebee. For enterprise B2B with complex metering and contracts: Recurly.

Key Takeaways

  • Stripe Billing has the best developer API — TypeScript SDK, webhook events, test mode with 100% feature parity
  • Chargebee has built-in dunning flows — automated retry sequences, email campaigns, and payment update flows without code
  • Recurly supports hierarchical accounts — parent/child account structures for resellers and enterprise
  • All three integrate with Stripe's payment methods — cards, ACH, SEPA, and 50+ payment methods
  • Stripe Customer Portal — self-serve plan upgrades/downgrades/cancellations with zero code
  • Chargebee's revenue recovery — typically 4-8% lift in recovered revenue from failed payments
  • Metered billing is supported by all three — usage-based pricing for API products

The Subscription Billing Stack

One-time payments:  Stripe Checkout / PaymentIntent
Subscriptions:      Stripe Billing / Chargebee / Recurly
  - Trial management
  - Proration (mid-cycle plan changes)
  - Dunning (failed payment recovery)
  - Invoicing + tax calculation
  - Customer Portal (self-serve plan management)
  - Revenue reporting (MRR, ARR, churn)

Stripe Billing: Developer-First Subscriptions

Stripe Billing extends Stripe with subscription primitives: Products, Prices, Subscriptions, and Invoices. If you're already on Stripe for payments, Billing integrates directly.

Installation

npm install stripe

Create Products and Prices

import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

// Create a product (represents your SaaS plan)
const product = await stripe.products.create({
  name: "Pro Plan",
  description: "Unlimited projects, 10 team members, priority support",
  metadata: {
    features: "unlimited_projects,team_collaboration,priority_support",
  },
});

// Create a monthly price for the product
const monthlyPrice = await stripe.prices.create({
  product: product.id,
  unit_amount: 2900,          // $29.00/month
  currency: "usd",
  recurring: {
    interval: "month",
    interval_count: 1,
  },
  lookup_key: "pro_monthly",  // Stable reference across price updates
});

// Annual price with discount
const annualPrice = await stripe.prices.create({
  product: product.id,
  unit_amount: 29000,         // $290/year (~17% discount)
  currency: "usd",
  recurring: {
    interval: "year",
  },
  lookup_key: "pro_annual",
});

Create a Subscription with Trial

// Create a customer first
const customer = await stripe.customers.create({
  email: user.email,
  name: user.name,
  metadata: { userId: user.id },
});

// Attach payment method
await stripe.paymentMethods.attach(paymentMethodId, {
  customer: customer.id,
});

await stripe.customers.update(customer.id, {
  invoice_settings: { default_payment_method: paymentMethodId },
});

// Create subscription with 14-day trial
const subscription = await stripe.subscriptions.create({
  customer: customer.id,
  items: [{ price: monthlyPrice.id }],
  trial_period_days: 14,
  payment_settings: {
    payment_method_types: ["card"],
    save_default_payment_method: "on_subscription",
  },
  expand: ["latest_invoice.payment_intent"],
});

// Store subscription ID in your database
await db.users.update({
  where: { id: user.id },
  data: {
    stripeCustomerId: customer.id,
    stripeSubscriptionId: subscription.id,
    subscriptionStatus: subscription.status,
  },
});

Plan Upgrade with Proration

// Upgrade from monthly to annual (mid-cycle proration)
async function upgradeSubscription(userId: string, newPriceId: string) {
  const user = await db.users.findUnique({ where: { id: userId } });
  const subscription = await stripe.subscriptions.retrieve(user.stripeSubscriptionId);

  const updatedSubscription = await stripe.subscriptions.update(
    subscription.id,
    {
      items: [{
        id: subscription.items.data[0].id,
        price: newPriceId,
      }],
      proration_behavior: "create_prorations",  // Charge immediately for upgrade
      // proration_behavior: "none" — no proration, start fresh next cycle
    }
  );

  return updatedSubscription;
}

Customer Portal (Self-Serve)

// Create a billing portal session — zero UI code needed
async function createPortalSession(userId: string, returnUrl: string) {
  const user = await db.users.findUnique({ where: { id: userId } });

  const session = await stripe.billingPortal.sessions.create({
    customer: user.stripeCustomerId,
    return_url: returnUrl,
  });

  return session.url;
  // Customer can: view invoices, update payment method, upgrade/downgrade, cancel
}

Webhook Handling

import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";

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

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get("stripe-signature")!;

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
  }

  switch (event.type) {
    case "customer.subscription.created":
    case "customer.subscription.updated": {
      const subscription = event.data.object as Stripe.Subscription;
      await db.users.updateMany({
        where: { stripeCustomerId: subscription.customer as string },
        data: {
          subscriptionStatus: subscription.status,
          currentPeriodEnd: new Date(subscription.current_period_end * 1000),
          planId: subscription.items.data[0].price.id,
        },
      });
      break;
    }

    case "customer.subscription.deleted": {
      const subscription = event.data.object as Stripe.Subscription;
      await db.users.updateMany({
        where: { stripeCustomerId: subscription.customer as string },
        data: { subscriptionStatus: "canceled", planId: null },
      });
      break;
    }

    case "invoice.payment_failed": {
      const invoice = event.data.object as Stripe.Invoice;
      // Notify user of failed payment
      await sendPaymentFailedEmail(invoice.customer_email ?? "");
      break;
    }

    case "invoice.payment_succeeded": {
      // Extend subscription access
      break;
    }
  }

  return NextResponse.json({ received: true });
}

Metered Billing (Usage-Based)

// Report usage for a metered subscription
await stripe.subscriptionItems.createUsageRecord(
  subscriptionItemId,
  {
    quantity: apiCallsThisHour,
    timestamp: "now",
    action: "increment",  // "set" for absolute, "increment" for additive
  }
);

Chargebee: Subscription Lifecycle Management

Chargebee wraps subscription billing with pre-built workflows — dunning, revenue recovery, and complex pricing logic without writing business logic yourself.

Installation

npm install chargebee

Creating a Subscription

import chargebee from "chargebee";

chargebee.configure({
  site: process.env.CHARGEBEE_SITE!,
  api_key: process.env.CHARGEBEE_API_KEY!,
});

// Create customer + subscription in one call
const result = await chargebee.subscription.create_with_items("pro-monthly-usd", {
  customer: {
    email: user.email,
    first_name: user.name.split(" ")[0],
    last_name: user.name.split(" ")[1] ?? "",
  },
  subscription_items: [{
    item_price_id: "pro-monthly-usd",
    unit_amount: 2900,
  }],
  trial_end: Math.floor((Date.now() + 14 * 24 * 60 * 60 * 1000) / 1000),
  payment_method: {
    type: "card",
    gateway_account_id: "your-gateway-id",
    tmp_token: cardToken,
  },
}).request();

const subscription = result.subscription;
const customer = result.customer;

Dunning Configuration (No-Code)

Chargebee's dunning is configured in the dashboard:

Dunning Schedule Example:
  Day 0:  Payment fails → Retry immediately
  Day 3:  Retry again + send "Your payment failed" email
  Day 7:  Retry + send "Update your payment method" email
  Day 14: Retry + send "Final warning" email
  Day 21: Cancel subscription + send cancellation confirmation

vs Stripe: You write this logic yourself in webhooks
// Chargebee handles retries — you just handle the final events
app.post("/chargebee-webhook", async (req, res) => {
  const event = req.body;

  switch (event.event_type) {
    case "subscription_activated":
      await db.users.update({ ... });
      break;

    case "subscription_cancelled":
      // Chargebee has already done 4 retries over 21 days
      await downgradeUser(event.content.subscription.customer_id);
      break;

    case "payment_failed":
      // Chargebee is managing retries — just log
      await logPaymentFailure(event);
      break;
  }

  res.json({ received: true });
});

Hosted Checkout and Portal

// Generate a Chargebee-hosted checkout page
const checkoutResult = await chargebee.hosted_page
  .checkout_new_for_items({
    subscription_items: [{
      item_price_id: "pro-monthly-usd",
    }],
    customer: {
      email: user.email,
    },
    redirect_url: "https://yourapp.com/checkout/success",
    cancel_url: "https://yourapp.com/checkout/cancel",
  })
  .request();

const checkoutUrl = checkoutResult.hosted_page.url;

Recurly: Enterprise Subscription Billing

Recurly is built for high-volume B2B — hierarchical account structures, complex metering, contract billing, and advanced revenue reporting.

Installation

npm install recurly

Creating an Account and Subscription

import recurly from "recurly";

const client = new recurly.Client(process.env.RECURLY_API_KEY!);

// Create account
const account = await client.createAccount({
  code: `user-${user.id}`,
  email: user.email,
  firstName: user.name.split(" ")[0],
  lastName: user.name.split(" ")[1] ?? "",
  billingInfo: {
    tokenId: billingToken,  // Recurly.js token from frontend
  },
});

// Create subscription
const subscription = await client.createSubscription({
  planCode: "pro-monthly",
  account: { code: account.code },
  currency: "USD",
  trialEndsAt: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toISOString(),
});

Metered (Add-On) Usage

// Report usage for metered add-ons
await client.createUsage(subscriptionAddOnId, {
  amount: 1,
  merchantTag: `api-call-${Date.now()}`,
  recordingTimestamp: new Date().toISOString(),
  usageTimestamp: new Date().toISOString(),
});

Feature Comparison

FeatureStripe BillingChargebeeRecurly
Developer API quality✅ Best in classGoodGood
Built-in dunningBasic (3 retries)✅ Advanced workflows✅ Smart Retry
Customer portal✅ No-code✅ No-code✅ No-code
Metered billing
Tax automation✅ (Stripe Tax)✅ (EU VAT)✅ (AvaTax integration)
Hierarchical accountsPartial
Revenue recoveryBasic✅ 4-8% lift
TypeScript SDK✅ Official
Pricing0.5-0.8% + Stripe fees$299/month+$199/month+
Global payment methods✅ 50+ via StripeLimitedLimited
Hosted checkout✅ Stripe Checkout
GitHub stars4k (SDK)N/AN/A

When to Use Each

Choose Stripe Billing if:

  • You're already on Stripe for one-time payments and want unified billing
  • Developer control over every aspect of billing logic is important
  • You need the widest global payment method support
  • Your team can write webhook handlers for subscription events
  • Pricing flexibility matters — 0.5% is cheaper than flat monthly fees at small scale

Choose Chargebee if:

  • Non-engineering teams (growth, finance) need to manage billing configuration
  • Advanced dunning workflows and automated payment recovery are priorities
  • You want pre-built revenue metrics dashboards (MRR, ARR, churn, LTV)
  • Mid-market SaaS product where billing complexity exceeds what you want to build

Choose Recurly if:

  • Enterprise B2B with complex pricing (usage-based, contract-based, volume tiers)
  • Hierarchical account structures (resellers, agencies, sub-accounts)
  • You need enterprise revenue reporting and financial integrations (NetSuite, Salesforce)
  • High-volume billing where dedicated enterprise support matters

Tax Compliance and Global Billing Complexity

Subscription billing across multiple countries introduces tax compliance requirements that significantly impact your platform choice. Stripe Tax handles VAT, GST, and US sales tax calculation and collection automatically — you enable it in the dashboard and Stripe calculates the correct tax rate based on the customer's location and your product's tax code. This covers the EU's digital services VAT rules (where you charge VAT in the customer's country of residence, not yours), which is legally required for SaaS products sold to EU consumers regardless of where your company is incorporated. Chargebee integrates with Avalara's AvaTax for US sales tax nexus determination and EU VAT handling, providing similar automatic tax calculation with additional audit trail reporting that finance teams require for tax filings. Recurly's tax handling is similar via AvaTax integration. For early-stage products, Stripe Tax's single-toggle simplicity is compelling. For products at series A and beyond with complex tax filing requirements across many jurisdictions, Chargebee and Recurly's dedicated tax reporting dashboards and export formats for accounting teams become more valuable than Stripe's simpler implementation.

Dunning Strategy and Revenue Recovery Implementation

The most significant revenue impact in subscription businesses often comes not from acquiring new customers but from recovering failed payment revenue. A typical SaaS product sees 5–10% of monthly renewals fail on first attempt due to expired cards, insufficient funds, or bank authorization issues. Stripe Billing's smart retries use machine learning to retry failed charges at times statistically likely to succeed, but the retry schedule is not configurable in detail. Chargebee's dunning configuration is granular: you set the exact retry schedule (Day 3, Day 7, Day 14), the email content for each retry attempt, and whether to show an in-app payment update prompt. Chargebee also offers a hosted dunning page where customers can update their payment method directly from the email link without logging into your application — reducing friction in the recovery flow. Recurly's Smart Retry logic similarly uses ML for timing but adds intelligent email sequencing. The 4–8% revenue recovery lift cited in vendor case studies is realistic for products that implement a full dunning sequence; without dunning, most of that revenue is simply lost.

Pricing Model Complexity and Usage-Based Billing

Subscription pricing has evolved from simple flat-rate plans to complex combinations of seats, usage tiers, and committed minimums. Stripe Billing's metered billing implementation reports usage via subscriptionItems.createUsageRecord() and charges it on the next invoice cycle — suitable for simple per-unit metering. More complex models (tiered pricing where the first 100 units are free, units 101–1000 are $0.10, and units 1001+ are $0.05) require custom price configurations that can become unwieldy to manage in the Stripe dashboard at scale. Chargebee has a more structured pricing model editor for tiered, volume, and graduated pricing, and supports true hybrid billing where a subscription combines a flat monthly fee with usage-based add-ons. Recurly's add-on system is the most mature for B2B scenarios where customers purchase a base contract plus metered add-ons for API calls, storage, or seats. If your pricing model has more than two or three tiers or combines multiple usage metrics, evaluate whether Stripe Billing's flexibility covers your needs before assuming the simplest API is sufficient.

Webhook Reliability and Idempotent Event Handling

Subscription billing generates a high volume of webhook events — subscription created, invoice generated, payment succeeded, payment failed, subscription updated — and your application must handle these events reliably at scale. All three platforms retry failed webhook deliveries (Stripe retries over 3 days, Chargebee over 24 hours), which means your handlers must be idempotent: processing the same event twice must produce the same result as processing it once. Implement idempotency by storing the event ID in your database and checking for it before applying state changes — if customer.subscription.updated fires twice with the same event ID, the second processing should be a no-op. For Stripe specifically, never trust the payload data sent in the webhook body without re-fetching the object from the API using stripe.subscriptions.retrieve(subscription.id) — this prevents replay attacks where modified webhook bodies could manipulate your billing state. Signature verification using stripe.webhooks.constructEvent() is mandatory in production.

Methodology

Data sourced from official Stripe Billing documentation (stripe.com/docs/billing), Chargebee documentation (chargebee.com/docs), Recurly documentation (recurly.com/developers), pricing pages as of February 2026, and community reviews from r/SaaS, Indie Hackers, and Patrick McKenzie's writing on SaaS billing. Revenue recovery statistics from Chargebee and Recurly published case studies.


Related: Polar vs Paddle vs Gumroad for developer-focused monetization platforms at smaller scale, or Resend vs SendGrid vs Brevo for sending the transactional emails that accompany subscription events.

See also: How to Add Payments to Your App: Stripe vs LemonSqueezy 2026 and Best Payment Integration Libraries for Node.js in 2026

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.