We shipped a model picker last week. Users choose which AI writes their posts, see the cost per model up front, and pay the provider's exact measured rate with no markup on top.
Then we found a bug that could not have existed the week before.
Our Generate button had always returned three drafts per press. It was a UX call made back when generation was free and unmetered: more options, one click, nothing to think about. Nobody revisited it.
The moment a premium model sat behind that button, one press billed three times.
The obvious fix is wrong
The intuitive fix is to read which model the user selected and bill off that. It does not work, and the reason is the part worth stealing.
What the user picked is not what actually runs. If their plan lapsed, or their prepaid balance hit zero, generation degrades to the included model and the run costs nothing. Bill off the stored preference and you charge people for generations that were never premium in the first place.
So the check had to move. Ask the generation service what model it actually resolved to at runtime, then decide both how many drafts to produce and what to debit:
count = if AiModelRegistry.premium?(service.ai_model_key)
1
else
params[:variants].to_i.clamp(MIN_VARIANTS, MAX_VARIANTS)
end
One press, one billed generation, and only when the thing that ran was genuinely the paid model.
The transferable part
If you are adding usage-based pricing to an existing product, you are not adding a payment step. You are re-auditing every default the flow already had.
Batch sizes. Retries. Auto-refresh. Preview renders. Anything that quietly multiplies work was free before and none of it is now. Each one was a UX decision when it was written, and every one of them silently became a billing decision the day money showed up.
The second lesson is narrower but bit us harder: when a system can degrade or fall back, "what the user configured" and "what executed" are different values. Bill the second one. Log the second one. If your metering reads a stored preference anywhere, that is a bug waiting for someone's plan to lapse.
We build XreplyAI, a scheduler for 15 platforms, if it matters: https://xreplyai.com?utm_source=devto&utm_medium=social&utm_campaign=feature-2026-08-10
Top comments (0)