Designing a credit system that doesn't feel like a casino
3 min read
Every image QuickStaging generates costs me money, roughly $0.13 at the high-quality setting. So it charges in credits: one credit, one image. Credits are also how a lot of AI products end up feeling like slot machines. You pay, you pull, you hope, and when the result is bad you pay again.
I wanted the opposite. These are the rules I settled on.
1. You never pay for my failure
If generation fails, the credit comes back. That sounds simple, but "fails" comes in a few shapes, and each one needs its own path:
- Permanent errors (bad input, a rejected request) refund straight away.
- Capacity errors (the model provider is busy) keep the credit and retry: after 2, 5, 12, 30 and 60 minutes. If all of that fails, the credit is refunded.
- Stuck jobs get caught by a scheduled job. Anything still processing after 15 minutes is marked as timed out, and every credit it claimed goes back.
- Bad results that an automated quality check flags are refunded too. The user keeps the image anyway.
There's also a manual refund button for ten minutes after a result, once a day.
2. All or nothing
A staging job can make one to three variants, each a credit. If you ask for three and have two, it doesn't make two and surprise you. The claim either takes all three credits or none.
3. Credits don't expire
Credits used to freeze when a plan ended. I removed that. Expiry doesn't make people use the product more; it makes them feel robbed. Purchased and monthly credits now never expire. The one exception is the Listing Pass, which is sold as a 30-day product. Its clock starts on the first photo, not on purchase day.
4. Fixing should be cheaper than starting over
When a staged room is 90% right, the worst option is to roll the whole photo again. Fix area lets an agent paint the region that's wrong and describe the change. It's still a credit, but it's one credit aimed at the problem instead of a fresh gamble on the whole image.
The bugs
The bugs that taught me the most were all about doing something twice.
Double grants. The payment provider can send the same webhook more than once. Early on, that could credit a purchase twice. The fix is a database row written before anything else, with a unique index on the provider's transaction ID. The second webhook hits the index and stops.
Double refunds. The refund route updates a record only if it isn't already refunded. There's a comment in the code above that line saying it's "the ONLY thing standing between a double-click and a double refund". I left it there on purpose.
A cheaper pack that wasn't. At one point the 90-credit pack cost more per credit than the 60-credit monthly plan. Subscribing and cancelling straight away was the better deal, and people found that out. The pack is 100 credits now.
Pricing I tried
The prices moved several times. What changed them was always the same thing: what people actually used.
- Buyers of the $18 / 30-credit pack used about 35% of it. That led to a $10 Solo plan with 20 credits.
- Monthly allowances sat unused and got cancelled. That led to the Listing Pass: $19 for 25 photos, for one listing.
- Today the pricing page offers just three things: a 100-credit pack, a 60-credit monthly plan, and the Listing Pass. The other tiers still work for people who bought them. They just aren't for sale.
Every price clears at least a 55% gross margin. That's the one number I didn't move.