Note: This guide walks you how we setup stripe in the boilerplate. you don’t need to implement it in the boiler plate since its already implemented.

Prerequisites

  1. A Stripe account (Sign up at stripe.com)
  2. Go to your dashboard top right corner and turn on test mode
  3. Create a product in the dashboard
  4. Required environment variables: in apps/server/.env
    STRIPE_SECRET_KEY=your_stripe_secret_key
    NEXT_PUBLIC_STRIPE_PRODUCT_PRICE_ID=your_product_price_id
    

Installation

Install the required dependencies:
bun add @better-auth/stripe stripe @stripe

Server-Side Configuration

  1. Initialize the Stripe client: in apps/server/src/lib/stripe.ts
import Stripe from "stripe";

export const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2023-10-16",
});
  1. Configure BetterAuth with Stripe plugin: in apps/server/src/lib/auth.ts
import { betterAuth } from "better-auth";
import { stripe } from "@better-auth/stripe";

export const auth = betterAuth({
  plugins: [
    stripe({
      stripeClient,
      createCustomerOnSignUp: true,
      subscription: {
        enabled: true,
        plans: stripeProduct.map((product) => ({
          name: product.plan,
          priceId: product.priceId,
        })),
        getCheckoutSessionParams: () => ({
          params: {
            allow_promotion_codes: true,
          },
        }),
      },
    }),
  ],
});

Client-Side Configuration

Set up the auth client with Stripe support:
import { createAuthClient } from "better-auth/react";
import { stripeClient } from "@better-auth/stripe/client";

export const authClient = createAuthClient({
  baseURL: process.env.NEXT_PUBLIC_SERVER_URL!,
  plugins: [
    stripeClient({
      subscription: true,
    }),
  ],
});

Product Configuration

Define your products in a configuration file: in apps/server/src/utils/stripeProduct.ts
// src/utils/stripeProduct.ts
export const stripeProduct = [
  {
    plan: "Basic",
    priceId: "price_basic",
    features: ["Feature 1", "Feature 2"],
  },
  {
    plan: "Pro",
    priceId: "price_pro",
    features: ["Feature 1", "Feature 2", "Feature 3"],
  },
];
// better auth handles webhooks for you already. but if you want to handle them manually, you can do so by creating a webhook handler in apps/server/src/lib/auth.ts

Webhook Handling

The plugin automatically handles common webhook events:
  • checkout.session.completed: Updates subscription status after checkout
  • customer.subscription.updated: Updates subscription details when changed
  • customer.subscription.deleted: Marks subscription as canceled
You can also handle custom events: in apps/server/src/lib/auth.ts
stripe({
    // ... other options
    onEvent: async (event) => {
        // Handle any Stripe event
        switch (event.type) {
            case "invoice.paid":
                // Handle paid invoice
                break;
            case "payment_intent.succeeded":
                // Handle successful payment
                break;
        }
    }
})

Managing Subscription

Users can manage their subscription through the Stripe Customer Portal:
const portalUrl = await authClient.createPortalSession();
window.location.href = portalUrl;

UI Components

The boilerplate includes pre-built UI components for Stripe integration:
// Example subscription status display
<Card>
  <CardHeader>
    <CardTitle>Stripe Subscription</CardTitle>
    <CardDescription>
      Your current subscription details
    </CardDescription>
  </CardHeader>
  <CardContent>
    {/* Subscription details */}
  </CardContent>
</Card>

Common Issues

  1. Webhook Signature Verification
    • Ensure the webhook secret is correctly set
    • Verify the signature in the webhook handler
  2. Subscription Status Sync
    • Implement proper webhook handling
    • Update the database on subscription changes
  3. Checkout Session
    • Set correct success and cancel URLs
    • Handle failed payments gracefully

Next Steps