Stripe Billing vs Chargebee vs Recurly: SaaS Subscriptions 2026
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
| Feature | Stripe Billing | Chargebee | Recurly |
|---|---|---|---|
| Developer API quality | ✅ Best in class | Good | Good |
| Built-in dunning | Basic (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 accounts | ❌ | Partial | ✅ |
| Revenue recovery | Basic | ✅ 4-8% lift | ✅ |
| TypeScript SDK | ✅ Official | ✅ | ✅ |
| Pricing | 0.5-0.8% + Stripe fees | $299/month+ | $199/month+ |
| Global payment methods | ✅ 50+ via Stripe | Limited | Limited |
| Hosted checkout | ✅ Stripe Checkout | ✅ | ✅ |
| GitHub stars | 4k (SDK) | N/A | N/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
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.