RORK LABJP
SDK58 — The Expo SDK 58 beta is open. It ships the React Native 0.88 release candidate, and the beta period is stated as three to four weeks11/01 — For anyone who requested an extension, Google Play's target API deadline lands on November 1. Forty-four days outEASENV — A long-open report: secrets handed to a local build arrive as the literal variable name rather than its value, and the damage surfaces much laterNEW — The replacement the table recommended had already shut down. A record of reconciling all 74 rows of the deprecation listUISCENE — iOS 27 requires the new scene lifecycle. SDK 57 makes it something you opt into; it only becomes the default in 58CREDIT — What "AI errors don't cost credits" actually covers becomes clear once you record a day of asking for the same fix more than onceSDK58 — The Expo SDK 58 beta is open. It ships the React Native 0.88 release candidate, and the beta period is stated as three to four weeks11/01 — For anyone who requested an extension, Google Play's target API deadline lands on November 1. Forty-four days outEASENV — A long-open report: secrets handed to a local build arrive as the literal variable name rather than its value, and the damage surfaces much laterNEW — The replacement the table recommended had already shut down. A record of reconciling all 74 rows of the deprecation listUISCENE — iOS 27 requires the new scene lifecycle. SDK 57 makes it something you opt into; it only becomes the default in 58CREDIT — What "AI errors don't cost credits" actually covers becomes clear once you record a day of asking for the same fix more than once
Articles/AI Models
AI Models/2026-06-17Advanced

Making Credits Add Up in a Rork AI Image App — Field Notes on Atomic Ledgers and Moderation

Credit billing in a Rork AI image and video app breaks in production because of the order between generation and deduction. Here are the field notes — atomic ledger consumption, idempotency, refunds on failure, and moderation — with Supabase code you can ship.

Rork569AI image generationfal.aiSupabase34credit billingidempotency3content moderationadvanced7

Premium Article

The first AI image app I shipped got a support message on launch day: "Five images were generated but only one credit was deducted." It looked like a happy report, but it was proof the billing was broken. Tapping the generate button quickly five times sent five requests to the server almost simultaneously, and every one of them read the balance before any deduction landed.

The hard part of a credit-based app is not generating the image. Throw the request at fal.ai and a result comes back in a second or two. The hard part is keeping an external operation (generation) and an internal state (the balance) in agreement, every time, without drift. Get this wrong and you either give images away for free or, worse, charge for failures and collect one-star reviews. Running paid apps as a solo developer, the second one costs you more than the lost revenue — it costs trust.

These are field notes for a React Native app generated with Rork, backed by Supabase Edge Functions. I will show where the naive implementation falls apart, then move to atomic consumption, idempotency, refunds on failure, and moderation.

Three ways naive "check → generate → deduct" breaks in production

Most tutorials are written in this order: read the balance, generate if it is enough, subtract one on success. It works in development. The problem is that production has concurrency and network jitter.

Failure pathWhat happensResult
Concurrency (TOCTOU)Multiple requests read the same pre-deduction balanceSeveral images generated on a balance of 1; charges leak
Crash/timeout before deductGeneration succeeded but the deduct UPDATE never landsFree generations keep flowing
Deduct after failureThe API returned 5xx but you already subtractedCharging for failures; bad reviews

The first one is the nastiest. There is a time gap between SELECT credits and UPDATE credits = credits - 1, and another request slips into that gap. Disabling the button on the client does not save you: retries, double taps, and duplicate sends on slow networks still get through. The boundary you must defend lives inside the database, not in the client.

Hold credits as a ledger, not a balance column

Change the data model first. Stop treating a single users.credits column as the source of truth, and make a ledger — one row per change — the source of truth. The balance becomes the sum, or a derived value kept for convenience. A ledger lets you trace exactly when, on what, and how many credits were spent or returned, which makes refunds and audits simple.

-- The ledger is the source of truth for credit changes
create table credit_ledger (
  id          uuid primary key default gen_random_uuid(),
  user_id     uuid not null references auth.users(id),
  delta       integer not null,        -- purchase +, consumption -, refund +
  reason      text not null,           -- 'purchase' | 'image' | 'video' | 'refund'
  ref_id      text,                    -- generation job id or checkout session id
  idem_key    text,                    -- idempotency key (below)
  created_at  timestamptz not null default now()
);
 
-- Physically forbid double-recording under the same idempotency key
create unique index credit_ledger_idem_uniq
  on credit_ledger (user_id, idem_key)
  where idem_key is not null;
 
create index credit_ledger_user_idx on credit_ledger (user_id);

The current balance is select coalesce(sum(delta), 0) from credit_ledger where user_id = $1. If the growing ledger worries you, you can fold it into monthly snapshot rows, but at a solo-developer scale (tens of thousands of rows) the sum query is plenty fast.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
The exact paths where check-then-generate-then-deduct breaks, and how to consume atomically in a Postgres function
Idempotency keys that stop double charges, plus a refund ledger that reliably returns credits on failure or cancel
Two-stage moderation on prompt input and image output, and the operational limits that keep you through App Store review
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

Related Articles

AI Models2026-09-13
Is That API Key Sitting Inside Your App? Rork Environment Variables vs. Supabase Edge Functions
Rork gives you two places to put an API key: its own environment variables, or a secret inside a Supabase Edge Function. Here is how I decide between them based on what I can revoke and who gets the bill, plus three checks to run after the key is in place.
AI Models2026-08-07
Cutting MCP Tools Didn't Make Anything Lighter — 2,377 Bytes of Definitions vs 110,298 Bytes of Response
I wrote a minimal MCP server for a wallpaper catalog and measured the byte cost of tool definitions against the byte cost of responses. Here is which side actually matters, and the real reason to merge tools.
AI Models2026-05-06
7 Real Challenges When Building a Tarot App with Rork (And How to Solve Them)
A practical guide to building a daily tarot and oracle card app with Rork. Covers card data architecture, AI interpretation generation, daily reset logic, monetization design, and App Store review pitfalls specific to fortune-telling apps.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links