Skip to main content
A hybrid billing model combining subscriptions with usage credits and one-time top-ups. Common for AI generation platforms.
View the complete source code on GitHub.

Prerequisites

  • Flowglad account with API key
  • React 18+ with Vite
  • Express server (or similar) for the backend
  • PostgreSQL database

Project Structure

├── src/
│   ├── pages/
│   │   ├── home.tsx                  # Main dashboard UI
│   │   └── pricing.tsx               # Pricing page
│   ├── lib/
│   │   ├── auth-client.ts            # Better Auth client
│   │   └── billing-helpers.ts        # Usage & pricing utilities
│   └── components/
│       └── providers.tsx             # FlowgladProvider setup
├── server/
│   ├── index.ts                      # Express server with Flowglad handler
│   └── lib/auth.ts                   # Better Auth server config
├── pricing.yaml                      # Pricing configuration
└── package.json

Key Concepts

This pricing model combines three billing mechanisms:
  1. Subscription tiers - Monthly plans at fixed prices ($10, $30, $60/mo)
  2. Usage credit grants - Each tier includes credits that renew each billing period (200, 360, 750 generations)
  3. One-time top-ups - Customers can purchase additional credits when they run out
This model works well when you want predictable recurring revenue while giving customers flexibility to exceed their plan limits. Customers who find themselves consistently purchasing top-ups can choose to upgrade to a higher tier for better value.

Implementation

Pricing Configuration

The pricing.yaml file defines subscription tiers, credit allocations, and top-up products. See the full configuration in the repository. Pricing model diagram showing usage meters, subscription tiers, and top-ups The key distinction in pricing.yaml is renewalFrequency: use "every_billing_period" for subscription credits that reset monthly, and "once" for top-up credits that are consumed permanently.

Server Setup

Unlike Next.js, React + Vite requires a separate backend server. This example uses Express with Flowglad’s requestHandler.
server/index.ts
import { FlowgladServer, requestHandler } from '@flowglad/server';

const flowglad = (customerExternalId: string) => {
  return new FlowgladServer({
    customerExternalId,
    getCustomerDetails: async (externalId) => {
      const [user] = await db
        .select({ email: users.email, name: users.name })
        .from(users)
        .where(eq(users.id, externalId))
        .limit(1);
      return { email: user.email, name: user.name || '' };
    },
  });
};

const flowgladHandler = requestHandler({
  flowglad: (customerExternalId) => flowglad(customerExternalId),
  getCustomerExternalId: async (req) => {
    const session = await auth.api.getSession({
      headers: fromNodeHeaders(req.headers),
    });
    return session?.user?.id;
  },
});

app.all('/api/flowglad/*', async (req, res) => {
  // Route handling for Flowglad API
});
The requestHandler creates a catch-all route that proxies requests between your React frontend and Flowglad’s API.

Checking Usage Balance

Use checkUsageBalance to display remaining credits and gate access to generation features.
src/pages/home.tsx
import { useBilling } from '@flowglad/react';

const billing = useBilling();
const balance = billing.checkUsageBalance('fast_generations');

// balance.availableBalance - credits remaining
The balance includes both subscription credits and any purchased top-ups. Flowglad automatically draws from credits when you register a usage event.

Recording Usage

When a customer generates content, use createUsageEvent from the useBilling() hook to record usage directly from the client. This decrements the credit balance.
src/pages/home.tsx
const billing = useBilling();
const { createUsageEvent } = billing;

const handleGenerate = async () => {
  const amount = 1;

  const result = await createUsageEvent({
    usageMeterSlug: 'fast_generations',
    amount
  });

  if ('error' in result) {
    throw new Error(result.error.code || 'Failed to create usage event');
  }

  // Reload billing data to reflect updated balance
  if (billing.reload) {
    await billing.reload();
  }
};
After recording usage, call billing.reload() to fetch the updated balance from the server.

Checking Feature Access

Toggle features let you differentiate plans beyond just credit amounts. Use checkFeatureAccess to check if a user has access to gated premium features.
src/pages/home.tsx
const hasRelaxMode = billing.checkFeatureAccess('unlimited_relaxed_images');
const hasStealthMode = billing.checkFeatureAccess('stealth_mode');
This returns true if the customer’s current subscription includes the feature, false otherwise.

Purchasing Top-Up Credits

When customers run low on credits, let them purchase additional credits without changing their subscription.
src/pages/home.tsx
const price = billing.getPrice('fast_generation_top_up');

await billing.createCheckoutSession({
  priceId: price.id,
  successUrl: `${window.location.origin}?checkout=success`,
  cancelUrl: window.location.href,
  quantity: 1,
  autoRedirect: true,
});
This opens a checkout for the top-up product. On successful payment, the credits are immediately added to the customer’s balance. Reload billing data after checkout to reflect the new balance:
src/pages/home.tsx
useEffect(() => {
  const urlParams = new URLSearchParams(window.location.search);
  if (urlParams.has('checkout') && billing.reload) {
    billing.reload();
    window.history.replaceState({}, '', window.location.pathname);
  }
}, [billing.reload]);

Next Steps