Better Billing

Stripe Setup

Learn how to add Stripe payment processing to Better Billing

Stripe Setup

This guide shows you how to add Stripe payment processing to your existing Better Billing setup. Make sure you've completed the Getting Started guide first.

Installation

Install the Stripe SDK:

npm install stripe

Adding Stripe Plugin

Update your Better Billing configuration to include the Stripe plugin:

import drizzleAdapter from "@better-billing/db/adapters/drizzle";
import { betterBilling } from "better-billing";
import { corePlugin } from "better-billing/plugins/core";
import { stripePlugin } from "better-billing/plugins/stripe";
import Stripe from "stripe";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";

import * as schema from "./schema";

// Initialize database
const connectionString = process.env.DATABASE_URL!;
const client = postgres(connectionString);
const drizzleDb = drizzle(client);

// Initialize Stripe
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2023-10-16",
});

// Create Better Billing instance with Stripe
const billing = betterBilling({
  serverUrl: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
  adapter: drizzleAdapter(drizzleDb, {
    provider: "pg",
    schema,
  }),
  plugins: [
    // Core plugin (required)
    corePlugin({
      subscriptionPlans: [
        {
          planName: "Free",
        },
        {
          planName: "Pro",
          trialDays: 7,
        },
        {
          planName: "Enterprise",
          trialDays: 14,
        },
      ],
    }),
    
    // Stripe plugin for payment processing
    stripePlugin({
      stripe,
      subscriptionPlans: {
        monthly: [
          { planName: "Free", items: [{ price: "price_free_monthly" }] },
          { planName: "Pro", items: [{ price: "price_pro_monthly" }] },
          { planName: "Enterprise", items: [{ price: "price_enterprise_monthly" }] },
        ],
        yearly: [
          { planName: "Free", items: [{ price: "price_free_yearly" }] },
          { planName: "Pro", items: [{ price: "price_pro_yearly" }] },
          { planName: "Enterprise", items: [{ price: "price_enterprise_yearly" }] },
        ],
      },
      postSuccessUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard?success=true`,
      postCancelUrl: `${process.env.NEXT_PUBLIC_APP_URL}/pricing`,
      webhookEndpointSecret: process.env.STRIPE_WEBHOOK_SECRET!,
    }),
  ] as const,
});

export { billing };

Stripe Configuration

1. Create Stripe Products & Prices

In your Stripe Dashboard:

  1. Go to ProductsAdd product
  2. Create products for each plan (Free, Pro, Enterprise)
  3. Add prices for different billing cycles (monthly/yearly)
  4. Copy the price IDs to use in your configuration

2. Environment Variables

Add these environment variables:

# Core
DATABASE_URL=postgresql://...

# Stripe
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
NEXT_PUBLIC_APP_URL=https://yourdomain.com

3. Webhook Setup

  1. Go to DevelopersWebhooks in your Stripe Dashboard
  2. Click Add endpoint
  3. Enter your webhook URL: https://yourdomain.com/api/billing/stripe/webhook
  4. Select these events or "Select all events":
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.paid
    • invoice.payment_failed
  5. Copy the webhook signing secret to your environment variables

Using Stripe Features

Start Subscription Checkout

const checkoutSession = await billing.providers.stripe.startSubscriptionCheckout({
  billableId: "user_123",
  billableType: "user",
  planName: "Pro",
  cadence: "monthly",
  email: "customer@example.com",
  metadata: {
    userId: "123",
    source: "web_app",
  },
  allowPromotionCodes: true,
});

// Redirect to Stripe Checkout
window.location.href = checkoutSession.url;

Subscription Management

// Cancel a subscription
await billing.providers.stripe.cancelSubscription({
  subscriptionId: "sub_123",
  cancelAtPeriodEnd: true, // Cancel at end of current period
});

// Update subscription plan
await billing.providers.stripe.updateSubscription({
  subscriptionId: "sub_123",
  planName: "Enterprise",
  cadence: "yearly",
});

// Get subscription details
const subscription = await billing.providers.stripe.getSubscription({
  subscriptionId: "sub_123",
});

Customer Portal

Create a link to Stripe's Customer Portal for self-service:

const portalSession = await billing.providers.stripe.createPortalSession({
  customerId: "cus_123",
  returnUrl: `${process.env.NEXT_PUBLIC_APP_URL}/dashboard`,
});

// Redirect to Customer Portal
window.location.href = portalSession.url;

API Endpoints Setup

Make sure you have API endpoints set up to handle webhooks:

// app/api/billing/[...path]/route.ts
import { billing } from "@/lib/billing";
import { toNextJsHandler } from "better-billing/integrations/next-js";

const handler = toNextJsHandler(billing.api);

export const { GET, POST, PUT, DELETE, PATCH } = handler;

This creates the webhook endpoint: /api/billing/stripe/webhook

Error Handling

Always handle errors when calling Stripe methods:

try {
  const checkoutSession = await billing.providers.stripe.startSubscriptionCheckout({
    billableId: "user_123",
    billableType: "user",
    planName: "Pro",
    cadence: "monthly",
    email: "customer@example.com",
  });
  
  window.location.href = checkoutSession.url;
} catch (error) {
  console.error("Failed to create checkout session:", error);
  // Show user-friendly error message
  alert("Something went wrong. Please try again.");
}

Testing

Use Stripe's test mode for development:

  1. Use test API keys (starting with sk_test_)
  2. Use test webhook endpoints
  3. Use Stripe's test card numbers for testing

Production Checklist

Before going live:

  • Switch to live API keys
  • Update webhook endpoint to production URL
  • Test all subscription flows
  • Set up proper error handling and logging
  • Configure tax collection if required
  • Set up subscription lifecycle emails

Common Issues

Webhook Not Receiving Events

  • Verify webhook URL is publicly accessible
  • Check webhook signing secret is correct
  • Ensure API endpoints are set up correctly
  • Check server logs for webhook processing errors

Checkout Session Creation Fails

  • Verify Stripe price IDs are correct
  • Check that products are active in Stripe
  • Ensure all required fields are provided
  • Verify Stripe API key is valid

Next Steps