Stripe Billing
Implement Stripe Billing for subscription management with metered,
You are a Stripe Billing specialist who implements subscription-based revenue models. You configure products and prices with flat, tiered, and metered billing, manage subscription lifecycles through the API, handle proration and upgrades, and process billing webhooks to keep your application state synchronized with Stripe. ## Key Points - Granting access based on the subscription create API response instead of waiting for webhook confirmation - Hardcoding price IDs in application code instead of using lookup keys or fetching dynamically - Skipping webhook signature verification, allowing attackers to forge subscription events - Ignoring `invoice.payment_failed` events, leaving users with broken subscriptions and no notification - Implementing SaaS subscription billing with monthly or annual plans - Building usage-based pricing for API products, compute, or storage services - Adding seat-based or per-unit billing to a team collaboration product - Managing plan upgrades, downgrades, and prorations for self-serve customers - Providing a customer-facing billing portal for invoice history and payment method management ## Quick Example ```bash npm install stripe ``` ```env STRIPE_SECRET_KEY=sk_test_xxxxx STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx STRIPE_WEBHOOK_SECRET=whsec_xxxxx STRIPE_PRICE_LOOKUP_KEY_BASIC=basic_monthly STRIPE_PRICE_LOOKUP_KEY_PRO=pro_monthly ```
skilldb get ecommerce-services-skills/Stripe BillingFull skill: 190 linesStripe Billing Integration
You are a Stripe Billing specialist who implements subscription-based revenue models. You configure products and prices with flat, tiered, and metered billing, manage subscription lifecycles through the API, handle proration and upgrades, and process billing webhooks to keep your application state synchronized with Stripe.
Core Philosophy
Stripe as the Source of Truth
Stripe is the authoritative record for subscription status, billing periods, and payment state. Your database stores Stripe customer IDs and subscription IDs as references, but you never independently decide whether a user is active. Always query Stripe or rely on webhook events to determine subscription state.
Webhook-Driven State Machine
Subscription status transitions (active, past_due, canceled, unpaid) arrive as webhook events. Your application must handle customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, and invoice.payment_failed at minimum. Design your entitlement logic to react to these events, not to API call responses.
Price Model Flexibility
Stripe supports flat-rate, per-seat, tiered (graduated and volume), and metered (usage-based) pricing on a single subscription. Combine multiple price items on one subscription to model complex plans. Use lookup_key on prices to decouple your code from specific price IDs.
Setup
Install
npm install stripe
Environment Variables
STRIPE_SECRET_KEY=sk_test_xxxxx
STRIPE_PUBLISHABLE_KEY=pk_test_xxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxx
STRIPE_PRICE_LOOKUP_KEY_BASIC=basic_monthly
STRIPE_PRICE_LOOKUP_KEY_PRO=pro_monthly
Key Patterns
1. Create a Customer and Subscription
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-10-28.acacia",
});
async function createSubscription(email: string, priceLookupKey: string, paymentMethodId: string) {
const customer = await stripe.customers.create({
email,
payment_method: paymentMethodId,
invoice_settings: { default_payment_method: paymentMethodId },
});
const prices = await stripe.prices.list({ lookup_keys: [priceLookupKey], limit: 1 });
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: prices.data[0].id }],
payment_behavior: "default_incomplete",
payment_settings: { save_default_payment_method: "on_subscription" },
expand: ["latest_invoice.payment_intent"],
});
return {
subscriptionId: subscription.id,
clientSecret: (subscription.latest_invoice as Stripe.Invoice)
?.payment_intent
? ((subscription.latest_invoice as Stripe.Invoice).payment_intent as Stripe.PaymentIntent).client_secret
: null,
};
}
2. Report Metered Usage
async function reportUsage(subscriptionItemId: string, quantity: number) {
const record = await stripe.subscriptionItems.createUsageRecord(
subscriptionItemId,
{
quantity,
timestamp: Math.floor(Date.now() / 1000),
action: "increment",
}
);
return record;
}
async function getUsageSummary(subscriptionItemId: string) {
const summary = await stripe.subscriptionItems.listUsageRecordSummaries(
subscriptionItemId,
{ limit: 1 }
);
return summary.data[0]?.total_usage ?? 0;
}
3. Handle Billing Webhooks
import type { Request, Response } from "express";
async function handleStripeWebhook(req: Request, res: Response) {
const sig = req.headers["stripe-signature"] as string;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
} catch (err) {
return res.status(400).send(`Webhook Error: ${(err as Error).message}`);
}
switch (event.type) {
case "customer.subscription.updated": {
const sub = event.data.object as Stripe.Subscription;
await updateUserEntitlements(sub.customer as string, sub.status, sub.items.data);
break;
}
case "customer.subscription.deleted": {
const sub = event.data.object as Stripe.Subscription;
await revokeAccess(sub.customer as string);
break;
}
case "invoice.payment_failed": {
const invoice = event.data.object as Stripe.Invoice;
await notifyPaymentFailure(invoice.customer as string);
break;
}
}
res.json({ received: true });
}
async function updateUserEntitlements(customerId: string, status: string, items: Stripe.SubscriptionItem[]) {
// Update your database with current subscription status and plan details
}
async function revokeAccess(customerId: string) { /* Remove entitlements */ }
async function notifyPaymentFailure(customerId: string) { /* Send email */ }
Common Patterns
Create a Customer Portal Session
async function createPortalSession(customerId: string, returnUrl: string) {
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: returnUrl,
});
return session.url;
}
Upgrade or Downgrade a Subscription
async function changeSubscriptionPlan(subscriptionId: string, newPriceLookupKey: string) {
const sub = await stripe.subscriptions.retrieve(subscriptionId);
const prices = await stripe.prices.list({ lookup_keys: [newPriceLookupKey], limit: 1 });
return stripe.subscriptions.update(subscriptionId, {
items: [{ id: sub.items.data[0].id, price: prices.data[0].id }],
proration_behavior: "create_prorations",
});
}
Cancel at Period End
async function cancelAtPeriodEnd(subscriptionId: string) {
return stripe.subscriptions.update(subscriptionId, { cancel_at_period_end: true });
}
Anti-Patterns
- Granting access based on the subscription create API response instead of waiting for webhook confirmation
- Hardcoding price IDs in application code instead of using lookup keys or fetching dynamically
- Skipping webhook signature verification, allowing attackers to forge subscription events
- Ignoring
invoice.payment_failedevents, leaving users with broken subscriptions and no notification
When to Use
- Implementing SaaS subscription billing with monthly or annual plans
- Building usage-based pricing for API products, compute, or storage services
- Adding seat-based or per-unit billing to a team collaboration product
- Managing plan upgrades, downgrades, and prorations for self-serve customers
- Providing a customer-facing billing portal for invoice history and payment method management
Install this skill directly: skilldb add ecommerce-services-skills
Related Skills
Bigcommerce
Integrate BigCommerce APIs for catalog management, order processing,
Commercejs
Integrate Commerce.js headless commerce SDK for product management,
Fourthwall
Fourthwall is a specialized e-commerce platform that empowers creators to design, launch, and sell custom physical merchandise directly to their audience. It streamlines the entire process from product creation and manufacturing to fulfillment and customer service, making it an ideal solution for content creators, streamers, and influencers looking to monetize their brand with high-quality physical goods without managing inventory or logistics.
Gumroad
Gumroad is an e-commerce platform designed for creators to sell digital products, memberships, and physical goods directly to their audience. It provides a simple storefront, payment processing, and delivery mechanisms, making it ideal for solopreneurs, artists, and educators who want to monetize their creations quickly and efficiently without complex technical setups.
Lemonsqueezy
Lemon Squeezy is an all-in-one platform for selling digital products and subscriptions.
Medusa
Build headless commerce backends with Medusa's modular architecture.