This is an opinionated library to help you implement payments with Stripe.
- Define plans in code which sync to your Stripe account
- No manual webhook setup - the library handles webhooks and syncs Stripe data to your DB
- Simple APIs for subscriptions, credits, wallet balances, top-ups, and usage-based billing
- Support for seat based billing, tax collection, plan upgrades and downgrades (including sane handling of credits)
- Optional callbacks (
onSubscriptionCreated, etc.) for custom logic
This guide assumes you have a Next.js app and a PostgreSQL database. We recommend starting with a test mode Stripe API key so you can test your setup locally in your dev environment. Then, the guide will walk you through how to set up your app for production.
npm install stripe-no-webhooks stripe
npx stripe-no-webhooks initYou'll be prompted for:
- Stripe test key (for eg,
sk_test_...) - get it from Stripe dashboard - Database URL – PostgreSQL connection string (for example:
postgresql://postgres:password@localhost:5432/app_db) - Site URL - For eg,
http://localhost:3000for local dev
This will update your .env file with your credentials and create the following files:
billing.config.ts: Your config file with your planslib/billing.ts: Your core billing client instanceapp/api/stripe/[...all]/route.ts: Your webhook handler and API routes
npx stripe-no-webhooks migrateThis will create the stripe schema in your database with the necessary tables for syncing Stripe data and tracking credits + usage.
Edit billing.config.ts to define your plans for the test environment. Here's an example:
const billingConfig: BillingConfig = {
test: {
plans: [
{
name: "Free",
price: [{ amount: 0, currency: "usd", interval: "month" }],
},
{
name: "Pro",
price: [
{ amount: 2000, currency: "usd", interval: "month" }, // $20/mo
{ amount: 20000, currency: "usd", interval: "year" }, // $200/yr
],
},
],
},
production: {
plans: [], // Add when going live
},
};Plans can also include credits, wallet, and usage-based billing:
{
name: "Pro",
price: [{ amount: 2000, currency: "usd", interval: "month" }],
features: {
api_calls: {
credits: { allocation: 1000 }, // 1000 included/month
pricePerCredit: 1, // $0.01 per extra call (used for top-ups and usage billing)
trackUsage: true, // enable usage-based billing for overages
},
},
wallet: {
allocation: 500, // $5.00 prepaid balance
},
}See Credits, Wallet, and Usage Billing docs for details.
npx stripe-no-webhooks syncCreates products/prices in Stripe and updates your config with their IDs.
Update lib/billing.ts to specify how to get the userId in the resolveUser function. For example, with Clerk:
import { Billing } from "stripe-no-webhooks";
import { auth } from "@clerk/nextjs/server"; // or your auth
import billingConfig from "../billing.config";
export const billing = new Billing({
billingConfig,
successUrl: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
cancelUrl: process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
resolveUser: async () => {
const { userId } = await auth();
return userId ? { id: userId } : null;
},
});Start your Next.js app, then in another terminal, forward Stripe webhooks:
stripe listen --forward-to localhost:3000/api/stripe/webhookYour setup is complete! Now let's use it.
stripe-no-webhooks lets you generate a full pricing page with plan selection, monthly/yearly toggle, and checkout flow built-in. Or you can call checkout() directly:
import { checkout } from "stripe-no-webhooks/client";
<button onClick={() => checkout({ planName: "Pro", interval: "month" })}>
Upgrade to Pro
</button>;Test card: 4242 4242 4242 4242, any future MM/YY expiry, any CVC.
import { billing } from "@/lib/billing";
const subscription = await billing.subscriptions.get({ userId });
if (subscription?.status === "active") {
console.log("Plan:", subscription.plan?.name);
}When a user completes checkout:
- Stripe sends a webhook to your app
- The library receives it and syncs the data to your database. If credits / wallet are enabled, it will also update the credits / wallet balances
billing.subscriptions.get({ userId })now returns the subscription based on the Stripe data that's synced to your database- Credits / wallet are tracked automatically through a credit balance and a ledger of transactions via the library's internal APIs. These APIs are all idempotent and you don't have to worry about double counting or missing transactions
You can verify this by checking your database's stripe.subscriptions and stripe.credit_balances and stripe.credit_ledger.
// Credits: consume included units
if (await billing.credits.hasCredits({ userId, key: "api_calls", amount: 1 })) {
await billing.credits.consume({ userId, key: "api_calls", amount: 1 });
}
// Wallet: deduct from prepaid balance (in cents)
await billing.wallet.consume({
userId,
amount: 50,
description: "AI generation",
});
// Usage: record for end-of-period billing
await billing.usage.record({ userId, key: "api_calls", amount: 1 });See the full set of features in the Credits, Wallet, and Usage docs.
Let users manage their subscription:
import { customerPortal } from "stripe-no-webhooks/client";
<button onClick={() => customerPortal()}>
Manage Billing
</button>export const billing = new Billing({
billingConfig,
callbacks: {
onSubscriptionCreated: async (subscription) => {
// Send welcome email
},
onSubscriptionCancelled: async (subscription) => {
// Clean up resources
},
// List of full callbacks in docs/reference.md
},
});npx stripe-no-webhooks generate pricing-pageThis creates a fully customizable pricing page component at components/PricingPage.tsx:
// in your pricing page, for eg /pricing
import { PricingPage } from "@/components/PricingPage";
export default function Pricing() {
return <PricingPage />;
}Automatically handles: plan fetching, current subscription detection, monthly/yearly toggle, checkout flow, redirect handling, error handling, and more.
- Add plans to the
productionsection ofbilling.config.ts - Run
npx stripe-no-webhooks syncand choose "Set up for production". This will:
- Sync your plans to Stripe live mode
- Create a webhook endpoint for your app in Stripe
- Display the webhook URL and secret in the CLI output
- Add the webhook secret to your production environment (for eg, Vercel environment variables):
STRIPE_WEBHOOK_SECRET=whsec_... STRIPE_SECRET_KEY=sk_live_... # IMPORTANT: Your live Stripe secret key DATABASE_URL=postgresql://[username]:[password]@[production-db-url]:5432/[production-db-name] NEXT_PUBLIC_APP_URL=https://your-production-app.com
| Feature | Use Case |
|---|---|
| Credits | "1000 API calls/month included" - consumable units |
| Wallet | "$5/month for AI usage" - prepaid spending balance |
| Top-ups | Let users buy more credits on demand |
| Usage Billing | "Pay $0.10 per API call" - post-paid metered billing |
| Team Billing | Bill organizations, per-seat pricing |
| Tax Collection | Automatic VAT/GST calculation and ID collection |
| Payment Failures | Handle declined cards, retry logic |
| Command | Description |
|---|---|
init |
Set up config files and .env |
migrate |
Create database tables |
sync |
Sync plans to Stripe |
generate pricing-page |
Generate pricing component |
backfill |
Import existing Stripe data |
See docs/reference.md for the complete API.
If you are an LLM, full documentation is at https://github.com/pretzelai/stripe-no-webhooks/blob/main/docs/llms.txt