credit-metering
Implement or audit usage credits with atomic reservations, spending, grants, purchases, expiration, refunds, limits, UI state, and an immutable ledger. Use when an app meters consumable usage.
- Category
- monetization
- Package
- credit-metering/SKILL.md
- License
- MIT
- Author
- @tushaarmehtaa
- Tags
- creditsmeteringbillingpaymentsusageentitlements
Install
Swipe for more runtimes.
Codex
Skills directory: ~/.codex/skills
Install globally
npx skills add tushaarmehtaa/tushar-skills --skill credit-metering -g -a codex -yInvoke
$credit-metering or /skillsYou can also describe the task naturally; runtimes may select the skill from its description.
Required access
Claude Code
Skills directory: ~/.claude/skills
Install globally
npx skills add tushaarmehtaa/tushar-skills --skill credit-metering -g -a claude-code -yInvoke
/credit-meteringYou can also describe the task naturally; runtimes may select the skill from its description.
Required access
Cursor
Skills directory: ~/.cursor/skills
Install globally
npx skills add tushaarmehtaa/tushar-skills --skill credit-metering -g -a cursor -yInvoke
/credit-meteringYou can also describe the task naturally; runtimes may select the skill from its description.
Required access
local coding agent required
This skill requires project files, terminal commands, and network access. Uploading it to a chat app does not provide equivalent execution.
ChatGPT Skills
This workflow needs a local coding environment or capabilities that a chat-only Skills upload does not provide.
Why local agent required →Instructions
Source: SKILL.mdCredit metering
Build credits as a financial state machine. Preserve existing denominations, billing semantics, and provider integrations.
Workflow
- Inspect backend/runtime, database/ORM, auth and tenant keys, current balance/ledger fields, payment provider, usage paths, retries, queues, and frontend state.
- Infer existing costs, grants, and packs from code. Ask only for unresolved commercial policy: denomination, per-action or measured cost, free/recurring grants, expiration/rollover, refund/clawback behavior, and overspend/debt policy. Never default to a new payment provider when one exists.
- Read database schemas, then implement only the matching database variant. Do not treat PostgreSQL, MySQL/PlanetScale, Prisma providers, and MongoDB as interchangeable.
- Use an append-only ledger with a unique idempotency key/external event ID. Update cached balance and ledger in one database transaction, or derive balance from the ledger when scale permits. Enforce positive input amounts and the chosen non-negative/debt rule in the database.
- Implement explicit operations:
grantfor signup, subscription, promotion, or admin credit;reservebefore costly/concurrent work;captureafter measured success;releaseon cancellation/failure;refundorreverselinked to the original transaction;expire/rolloverwhen credits have lifecycle rules.
- Authenticate and authorize from server context. Never accept balance, price, pack value, or user identity from the browser. Map a server-owned product/price ID to the credit amount.
- Read payment providers only for the detected provider. Verify raw-body signatures, durably deduplicate events, and retry transient failures. Acknowledge before processing only after a durable queue/inbox write.
- Make promotions atomic. Use a redemption table with a unique
(promo_id, user_id)constraint when one redemption per user is intended; update global usage and grant credit in the same transaction. - Treat the UI balance as a projection. Reconcile after responses, account switches, multi-tab updates, refunds, and webhook grants. Optimistic display must not authorize work.
Verification
Test signup/recurring grant, reserve-capture, reserve-release, insufficient balance, variable final cost, duplicate request, concurrent spend, payment replay, out-of-order event, refund/clawback, promo race, account switching, and reconciliation after network failure. Assert ledger/balance invariants and run database concurrency tests plus repository lint/type/test/build commands.
Output
Report the denomination and policies, schema/migrations, ledger operations and invariants, protected usage paths, provider/product mapping, UI reconciliation, verification evidence, and remaining dashboard or production setup.
Bundled references
2 files · 133 lines
references/database-schemas.md
source ↗Credit database variants
Read this reference after detecting the database. Implement one compatible variant and keep all balance/ledger mutations transactional.
Contents
Shared invariants
- Amounts are positive integers at the operation boundary; ledger deltas carry the sign.
- Every logical operation has a unique idempotency key.
- Ledger rows are append-only and link reversals/refunds to the original operation.
- Balance and ledger update in one transaction, or balance is derived from the ledger.
- Reservation status supports pending/captured/released where concurrent or variable-cost work exists.
- Database constraints enforce the chosen no-debt/debt policy.
PostgreSQL and Supabase
alter table public.users
add column if not exists credit_balance bigint not null default 0,
add constraint users_credit_balance_nonnegative check (credit_balance >= 0);
create table public.credit_transactions (
id uuid primary key default gen_random_uuid(),
user_id uuid not null references public.users(id),
idempotency_key text not null unique,
kind text not null check (kind in (
'grant', 'reserve', 'capture', 'release', 'refund', 'reverse', 'expire', 'rollover'
)),
amount bigint not null check (amount <> 0),
balance_after bigint not null,
original_transaction_id uuid references public.credit_transactions(id),
external_reference text,
metadata jsonb not null default '{}'::jsonb,
created_at timestamptz not null default now()
);
create index credit_transactions_user_created_idx
on public.credit_transactions (user_id, created_at desc);
create unique index credit_transactions_external_reference_idx
on public.credit_transactions (external_reference)
where external_reference is not null;
Implement a database function or application transaction that locks/conditionally updates the balance and inserts the ledger row atomically. For spend/reserve, use a conditional update such as ... where credit_balance >= amount and confirm one affected row inside the transaction.
Apply RLS/grants based on the real auth model. Users may read their own history; only trusted server paths should mutate financial rows.
Prisma
Model the same fields and unique constraints in the provider-compatible Prisma schema. Use an interactive transaction for conditional balance update plus ledger insert. Raw conditional SQL may be necessary for strict atomic spend; do not implement read-then-write in separate calls. JSON defaults and UUID generation differ by database provider, so avoid copying PostgreSQL annotations into MySQL.
MySQL and PlanetScale
Use MySQL-compatible types (char(36)/binary UUID choice, json, datetime) and migrations supported by the project. gen_random_uuid(), jsonb, partial indexes, and ADD COLUMN IF NOT EXISTS are PostgreSQL-specific. Verify foreign-key support/configuration on the actual PlanetScale project. Preserve idempotency with unique indexes and use a transaction or single conditional update supported by the database.
MongoDB
Store balance on the user/account document and ledger in a separate collection with unique indexes on idempotencyKey and optional externalReference. Use a replica-set transaction for balance+ledger atomicity, or a rigorously designed single-document ledger/balance model. A transaction schema alone is insufficient.
Verification
- Concurrent spends cannot cross the permitted balance boundary.
- Replaying an idempotency key returns the original result without another delta.
- Ledger and cached balances reconcile after grants, capture/release, and refund.
- Mutation attempts from ordinary client credentials are denied.
- Migration applies from a clean database and upgrades an existing fixture safely.
references/payment-providers.md
source ↗Credit payment providers
Read this reference after detecting the existing provider and installed SDK version. Implement one provider path and verify API/event names against current primary documentation.
Contents
Shared checkout contract
The browser submits only a server-owned pack key. The server maps that key to provider price/product ID, currency, and credit amount.
POST /api/billing/credit-checkout
authenticated body: { pack: "starter" }
server lookup: starter -> provider product/price -> 100 credits
Attach stable internal account/user ID and pack key as provider metadata. Never accept credits, amount_cents, price IDs outside an allow-list, or user identity as authoritative browser input.
Stripe
Use the installed Stripe SDK’s Checkout Session pattern and a server-owned price mapping. Process the current successful-payment event appropriate to the checkout mode and payment status. Verify stripe-signature over the raw body with the endpoint secret. Store both event ID and payment/session reference as unique identifiers where useful.
Lemon Squeezy
Use the current Lemon Squeezy checkout API/SDK with a server-owned variant mapping and custom_data containing stable account/user ID plus pack key. Verify the webhook signature exactly as current primary docs specify and handle only the paid/order state that guarantees funds.
Dodo Payments
Use the current official dodopayments SDK and Checkout Sessions API with a server-owned product_cart. Store account/user ID plus pack key in metadata. Prefer the official SDK webhook verification helper and current event guide. Dodo also has provider-managed credit capabilities; decide deliberately whether the application ledger or Dodo wallet is the source of truth—do not update both without reconciliation.
Webhook processing
- Read the raw body and verify signature/timestamp with the provider-supported helper.
- Insert a webhook-inbox/event row with a unique provider event ID.
- If processing synchronously, grant credits and mark the event complete in a database transaction. Return non-2xx for transient failure so the provider retries.
- If acknowledging immediately, first durably enqueue/store the event, then return success and process with retry/dead-letter monitoring.
- Resolve pack value from the server-owned mapping, not mutable metadata credit amounts alone. Cross-check product/price, currency, amount, payment status, and environment.
- Grant with the payment/event reference as the ledger idempotency key.
- Handle refunds/disputes according to the explicit clawback/debt policy and link reversal entries to the purchase.
Do not “always return 200” after a database error in a synchronous handler; that permanently discards the provider’s retry opportunity.
Verification
- Unknown/tampered pack keys are rejected before checkout.
- Test and live product IDs cannot cross environments.
- Valid, invalid, duplicate, concurrent, and out-of-order events behave deterministically.
- A transient database failure is retried or remains in a durable queue.
- Refund/dispute behavior matches the documented balance policy.
- Ledger, cached balance, provider payment, and UI reconcile.