🎁

Gift Dopamine for Christmas — Perfect for the dev in your life

Billing Configuration

Only 10 minutes to configure the pricing that will best suit your SaaS

Configuring and managing SaaS plans, packages, and consumption credits is often a nightmare. You know the drill: messy code, countless edge cases, and inevitably a major refactor 6 months down the line because the initial system couldn't scale with your evolving needs.

In Dopamine, we've distilled all of this complexity into a single configuration file, a Stripe Service, and a Credit Service that handle every possible scenario.

This document walks through different configuration use cases to show you how it all works together.

Pricing Configuration Schema

The entire billing system is configured in a single TypeScript file:

  • packages/shared-models/src/billing/pricing.config.ts.

What it does

This configuration file serves as the single source of truth for all products and pricing. It's shared between frontend (to display pricing pages) andbackend (to process Stripe payments and grant entitlements). Changes here automatically propagate across your entire application.

Basic Structure

The config exports a pricingConfig array containing a list of products.

pricingConfig[]
├── Product (type: "plan")
 ├── id, name, description
 ├── entitlements { seat?, credit? }
 └── prices[] { stripePriceId, cost, interval, model }
└── Product (type: "topup")
 ├── id, name, description
 ├── entitlements { credit }
 └── prices[] { stripePriceId, cost }

Product is an object with the following properties:

  • type: "plan" (subscription) or "topup" (one-time credit purchase)
  • entitlements: What the customer gets (seats = users, credit = usage)

Price is an object with the following properties:

  • model: "per_seat" (multiply by team size) or "flat" (one-time cost)
  • interval: "month" or "year" (only for plans)
  • type: "recurring" (monthly/yearly) or "one_time" (one-time credit purchase)
  • cost: in cents
  • currency: "USD", "EUR", etc.
  • stripePriceId: the ID of the Stripe price for this product

Subscription Plans and Topups

A subscription is a recurring payment that provides a set of entitlements to the customer. Here is how we can define it:

const product: Product = {
  id: 'plan-pro', // will be used to identify the product in the database
  type: 'plan', // "plan" or "topup"
  name: 'Pro', // the name displayed to the user
  description: 'Best option for small teams', // the description displayed to the user
  entitlements: { seat: 1 }, // the entitlements the customer gets
  prices: [
    {
      id: 'pro-monthly',
      interval: 'month',
      type: 'recurring',
      model: 'per_seat',
      cost: 3000, // in cents
      currency: 'USD', // the currency of the price
      stripePriceId: 'STRIPE_PRO_MONTHLY_PRICE_ID', // the varenv name that stores the Stripe Price ID
    },
  ],
};

A topup is a one-time credit purchase that can be bought manually or automatically. Here is how we can define it:

const product: Product = {
  id: 'topup-small', // will be used to identify the product in the database
  type: 'topup', // "plan" or "topup"
  name: 'Small Pack', // the name displayed to the user
  description: 'Scale on Demand', // the description displayed to the user
  entitlements: { credit: 400 }, // the number of credits the customer gets
  canAutoReload: true, // if true, the product can be bought automatically when the credit balance is below a certain threshold
  prices: [
    {
      id: 'topup-small',
      type: 'one_time',
      model: 'flat',
      cost: 1000, // in cents
      currency: 'EUR', // the currency of the price
      stripePriceId: 'STRIPE_TOPUP_SMALL_PRICE_ID', // the varenv name that stores the Stripe Price ID
    },
  ],
};

Plans Use Cases

Per-Seat Billing

  • Classic free/pro/business plans
  • No credits, just on/off features

Simple Subscription Plans

Here is how we can define it:

export const pricingConfig: Product[] = [
  {
    id: 'plan-pro',
    type: 'plan',
    name: 'Pro',
    description: 'Best option for small teams',
    entitlements: {
      seat: 1,
    },
    prices: [
      {
        id: 'pro-monthly',
        interval: 'month',
        type: 'recurring',
        model: 'per_seat',
        cost: 3000, // $30
        currency: 'USD',
        stripePriceId: 'STRIPE_PRO_MONTHLY_PRICE_ID',
      },
    ],
  },
  {
    id: 'plan-business',
    type: 'plan',
    name: 'Business',
    description: 'Advanced features for growing teams',
    entitlements: {
      seat: 1,
    },
    prices: [
      {
        id: 'business-monthly',
        interval: 'month',
        type: 'recurring',
        model: 'per_seat',
        cost: 9000, // $90
        currency: 'USD',
        stripePriceId: 'STRIPE_BUSINESS_MONTHLY_PRICE_ID',
      },
    ],
  },
];

Credit-Based Consumption

  • Plans with included monthly credits
  • Topups to purchase additional credits
  • Can invite unlimited users for free

Credit-Based Consumption

export const pricingConfig: Product[] = [
  {
    id: 'plan-pro',
    type: 'plan',
    name: 'Pro',
    description: 'Best option for small teams',
    entitlements: {
      credit: 1000, // the number of credits the customer gets
    },
    prices: [
      {
        id: 'pro-monthly',
        interval: 'month',
        type: 'recurring',
        model: 'flat',
        cost: 3000,
        currency: 'USD',
        stripePriceId: 'STRIPE_PRO_MONTHLY_PRICE_ID',
      },
    ],
  },
  {
    id: 'plan-business',
    type: 'plan',
    name: 'Business',
    description: 'Advanced features for growing teams',
    entitlements: {
      credit: 3000, // the number of credits the customer gets
    },
    prices: [
      {
        id: 'business-monthly',
        interval: 'month',
        type: 'recurring',
        model: 'flat',
        cost: 9000,
        currency: 'USD',
        stripePriceId: 'STRIPE_BUSINESS_MONTHLY_PRICE_ID',
      },
    ],
  },
];

Hybrid Model

  • Per-Seat + consumption credits combination
  • Unlimited features for certain plans
  • Add packages to allow users to purchase additional credits

Hybrid Model

export const pricingConfig: Product[] = [
  // Subscription Plans
  {
    id: 'plan-pro',
    type: 'plan', // "plan" or "topup"
    // ... other properties ...
    entitlements: {
      seat: 1,
      credit: 1000,
    },
    prices: [
      {
        id: 'pro-monthly',
        interval: 'month',
        type: 'recurring',
        // ... other properties ...
      },
    ],
  },
  {
    id: 'plan-business',
    type: 'plan',
    // ... other properties ...
    entitlements: {
      seat: 1,
      credit: 3000,
    },
    prices: [
      // ... other properties ...
    ],
  },
 
  // Credit Topups
  {
    id: 'topup-small',
    type: 'topup', // "plan" or "topup"
    name: 'Small Pack',
    description: 'Scale on Demand',
    entitlements: {
      credit: 400,
    },
    canAutoReload: true,
    prices: [
      {
        id: 'topup-small',
        type: 'one_time', // "recurring" or "one_time"
        model: 'flat',
        cost: 1000, // €10
        currency: 'EUR',
        stripePriceId: 'STRIPE_TOPUP_SMALL_PRICE_ID',
      },
    ],
  },
  {
    id: 'topup-medium',
    type: 'topup', // "plan" or "topup"
    name: 'Medium Pack',
    description: 'Most Popular',
    entitlements: {
      credit: 900,
    },
    default: true,
    canAutoReload: true,
    prices: [
      {
        id: 'topup-medium',
        type: 'one_time', // "recurring" or "one_time"
        model: 'flat',
        cost: 2000, // €20
        currency: 'EUR',
        stripePriceId: 'STRIPE_TOPUP_MEDIUM_PRICE_ID',
      },
    ],
  },
  {
    id: 'topup-large',
    type: 'topup', // "plan" or "topup"
    name: 'Large Pack',
    description: 'Best Value',
    entitlements: {
      credit: 2500,
    },
    canAutoReload: true,
    prices: [
      {
        id: 'topup-large',
        type: 'one_time', // "recurring" or "one_time"
        model: 'flat',
        cost: 5000, // €50
        currency: 'EUR',
        stripePriceId: 'STRIPE_TOPUP_LARGE_PRICE_ID',
      },
    ],
  },
];

Auto-Reload Topups

  • Topups that can be bought automatically when the credit balance is below a certain threshold

To activate auto-reload, you need to set the canAutoReload property to true and set the autoReloadPackage property to the ID of the topup you want to use.

export const pricingConfig: Product[] = [
  {
    id: 'topup-small',
    type: 'topup', // "plan" or "topup"
    canAutoReload: true, //
    autoReloadPackage: 'topup-small',
  },
];

Auto-Reload Topups

In the example above, when the credit balance is below 20 credits, a topup of 900 credits will be added automatically.