OpenBilling

Getting Started

Install OpenBilling packages and wire up a shared billing provider interface for Stripe or Dodo.

Getting Started

OpenBilling is split into a provider-neutral core package plus provider adapters.

Install packages

Stripe only:

pnpm add @openbilling/core @openbilling/stripe

Dodo only:

pnpm add @openbilling/core @openbilling/dodo

If your app switches providers at runtime:

pnpm add @openbilling/core @openbilling/stripe @openbilling/dodo

Required environment variables

Stripe setup:

STRIPE_API_KEY=...
STRIPE_WEBHOOK_SECRET=...

Dodo setup:

DODO_API_KEY=...
DODO_WEBHOOK_SECRET=...

These are the only environment variables required for package setup itself. Provider-specific checkout catalog IDs can come from config, your database, or app-specific environment variables.

Stripe setup

import { createStripeProvider } from '@openbilling/stripe';

export const billing = createStripeProvider({
  apiKey: process.env.STRIPE_API_KEY!,
  webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
});

Stripe checkout currently requires priceId:

const checkout = await billing.createCheckout({
  customerEmail: 'demo@example.com',
  priceId: 'price_123',
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
  mode: 'subscription',
});

Dodo setup

import { createDodoProvider } from '@openbilling/dodo';

export const billing = createDodoProvider({
  apiKey: process.env.DODO_API_KEY!,
  webhookSecret: process.env.DODO_WEBHOOK_SECRET!,
});

Dodo checkout currently requires productId:

const checkout = await billing.createCheckout({
  customerEmail: 'demo@example.com',
  productId: 'prod_123',
  successUrl: 'https://example.com/success',
  cancelUrl: 'https://example.com/cancel',
  mode: 'subscription',
});

Shared provider selection

If your app switches between providers, keep the selection in one place and return the shared BillingProvider interface.

import { Provider, type BillingProvider, type BillingProviderName } from '@openbilling/core';
import { createDodoProvider } from '@openbilling/dodo';
import { createStripeProvider } from '@openbilling/stripe';

export function getBillingProvider(providerName: BillingProviderName): BillingProvider {
  switch (providerName) {
    case Provider.Stripe:
      return createStripeProvider({
        apiKey: process.env.STRIPE_API_KEY!,
        webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
      });

    case Provider.Dodo:
      return createDodoProvider({
        apiKey: process.env.DODO_API_KEY!,
        webhookSecret: process.env.DODO_WEBHOOK_SECRET!,
      });

    default:
      throw new Error('Unsupported billing provider.');
  }
}

Common workflow

Once the provider is selected, the app-level workflow stays the same:

const billing = getBillingProvider(process.env.BILLING_PROVIDER!);

const portal = await billing.createPortalLink({
  customerId: 'cus_123',
  returnUrl: 'https://example.com/account',
});

const event = await billing.verifyWebhook({
  payload: rawBody,
  headers: Object.fromEntries(request.headers.entries()),
});

Next steps

On this page