Skip to main content
Watch: Claude Code Just Killed After Effects

Billing

Credit Metering for SaaS

Use when charging for AI or compute-heavy actions in a SaaS with a credit balance rather than per-call Stripe charges. Covers the per-action cost table, an atomic debit, free actions, an owner bypass, and turning an insufficient balance into a pricing redirect rather than a broken action. Also use when a balance can go negative under concurrency, or a failed action still charged the member. If an action costs the member something, use this skill.

billingcreditssaasmeteringmonetization

1 file

Description

Use when charging for AI or compute-heavy actions in a SaaS with a credit balance rather than per-call Stripe charges. Covers the per-action cost table, an atomic debit, free actions, an owner bypass, and turning an insufficient balance into a pricing redirect rather than a broken action. Also use when a balance can go negative under concurrency, or a failed action still charged the member. If an action costs the member something, use this skill.

Install
SKILL.md

name: credit-metering-saas description: Use when charging for AI or compute-heavy actions in a SaaS with a credit balance model. Covers the cost table, atomic debits, free actions, owner bypass, and turning insufficient balance into a pricing redirect rather than a broken action.

Credit Metering for SaaS

When to trigger

Monetizing per-action usage: an AI generation, a heavy export, a compute job. The user holds a credit balance and each action costs credits.

Define costs in one place

Keep a single cost table so pricing is auditable and consistent:

export const CREDIT_COSTS = {
  viewItem: 0,        // free actions cost nothing
  fillForm: 0,
  aiGenerate: 3,      // LLM actions cost a few
  heavyExport: 8,     // heavy compute costs more
} as const;

Free actions (viewing, form fills, alerts) cost 0. AI actions cost a small amount. Heavy compute costs more. Never charge for navigation.

Debit atomically

Check and deduct in one transaction so two concurrent requests cannot both spend the last credit:

const ok = await db.transaction(async (tx) => {
  const bal = await tx.getBalance(userId);
  if (bal < cost) return false;
  await tx.decrement(userId, cost);
  return true;
});
if (!ok) return new Response("Insufficient credits", { status: 402 });

Owner bypass

Gate the whole check behind an owner flag so internal accounts are never charged: if (user.isOwner) return runAction();. Read the flag from the user record, not a hardcoded id.

Handle 402 in the UI

A 402 is a product moment, not an error. Catch it and route the user to /pricing (or a top-up modal) with context on what they tried to do. Do not let a user reach a screen where every action silently 402s.

Pitfalls

  • Checking balance and debiting in two separate queries lets a race spend credits you did not have. Make it one atomic operation.
  • Charging before the action succeeds means a failed generation still costs the user. Debit on success, or refund on failure.
  • A dashboard that lets unsubscribed users in and then 402s every button is a dead end. Gate access up front and send sign-ups to pricing.
AI Development Stack

Get Smarter About AI Dev

New tutorials, open-source projects, and deep dives on coding agents - delivered weekly.

One email per weekReal code, not theoryFree forever