Usage credits
RailsFast ships with a fully working usage credits system, so you can keep track of how many credits each of your users has, and allow them to spend credits on your app.
If you don't need this in your app, you can just ignore it! There's nothing else to do.
If you do want usage credits in your app, everything is ready to go! You just need to define how your users get credits and how much each credit-consuming operation costs in usage_credits.rb.
The 30-second version
# Users get credits from subscriptions
subscription_plan :pro do
stripe_price "price_xyz"
gives 10_000.credits.every(:month)
end
# Operations cost credits
operation :generate_image do
costs 50.credits
end
# Spend credits safely
@user.spend_credits_on(:generate_image) do
ImageGenerator.call(params) # Credits only deducted if this succeeds
end
That's it. Stripe handles billing, RailsFast handles fulfillment, you handle your product.
What you can build
| Product Type | Credits Model | Example |
|---|---|---|
| AI/ML API | Per-request or per-token | "Generate image costs 50 credits" |
| SaaS with limits | Monthly allocation | "Pro plan: 10,000 API calls/month" |
| Metered billing | Pay-as-you-go | "1 credit per MB processed" |
| Freemium | Trial + paid tiers | "Free: 100 credits, Pro: unlimited" |
Real examples
AI Image Generator (like Midjourney)
# config/initializers/usage_credits.rb
subscription_plan :creator do
stripe_price month: "price_creator_monthly", year: "price_creator_yearly"
gives 1_000.credits.every(:month)
unused_credits :rollover # Unused credits carry over
end
operation :generate_image do
costs 10.credits
end
operation :upscale_image do
costs 25.credits
end
operation :generate_video do
costs 100.credits + 10.credits_per(:second)
end
# In your controller
def create
@user.spend_credits_on(:generate_image) do
@image = ImageAI.generate(params[:prompt])
end
rescue UsageCredits::InsufficientCredits
redirect_to pricing_path, alert: "You need more credits!"
end
Document Processing API (like DocuSign API)
operation :process_document do
costs 5.credits + 1.credit_per(:page)
end
# Usage
pages = PDF.page_count(uploaded_file)
cost = @user.estimate_credits_to(:process_document, pages: pages)
if @user.has_enough_credits_to?(:process_document, pages: pages)
@user.spend_credits_on(:process_document, pages: pages) do
DocumentProcessor.process(uploaded_file)
end
end
Transcription Service (like Whisper API)
operation :transcribe_audio do
costs 1.credit_per(:minute) # 1 credit per minute of audio
end
# Show cost before processing
duration_minutes = AudioFile.duration(file) / 60.0
puts "This will cost #{@user.estimate_credits_to(:transcribe_audio, minutes: duration_minutes)} credits"
Credit lifecycle
┌─────────────────────────────────────────────────────────────┐
│ How users get credits │
├─────────────────────────────────────────────────────────────┤
│ Subscription │ "Pro plan gives 10,000/month" │
│ Credit pack │ "Buy 5,000 credits for $49" │
│ Bonus │ "Referral bonus: 500 credits" │
│ Trial │ "Free trial: 100 credits" │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ What happens to credits │
├─────────────────────────────────────────────────────────────┤
│ Spent │ spend_credits_on(:operation) │
│ Expired │ expires_at: 30.days.from_now │
│ Rolled over │ unused_credits :rollover │
│ Refunded │ Automatic on Stripe refund │
└─────────────────────────────────────────────────────────────┘
Subscription + credit packs together
Users can have a subscription AND buy extra credit packs:
# Monthly subscription
subscription_plan :pro do
stripe_price "price_pro_monthly"
gives 5_000.credits.every(:month)
end
# One-time booster packs
credit_pack :small_boost do
gives 1_000.credits
costs 9.dollars
end
credit_pack :large_boost do
gives 10_000.credits
costs 49.dollars
end
# In your controller - let users buy extra credits anytime
def purchase_credits
pack = UsageCredits.find_credit_pack(params[:pack])
session = pack.create_checkout_session(current_user)
redirect_to session.url
end
Handling edge cases
Low balance alerts
UsageCredits.configure do |config|
config.low_balance_threshold = 100
config.on_low_balance_reached do |ctx|
UserMailer.low_credits(ctx.owner).deliver_later
end
end
Show remaining credits in UI
<!-- In your navbar or dashboard -->
<span class="credits-badge">
<%= current_user.credits %> credits remaining
</span>
<!-- With formatted display -->
<span><%= current_user.credit_wallet.formatted_balance %></span>
Transaction history for billing
# Show users their credit usage
@transactions = current_user.credit_history.recent.limit(50)
<% @transactions.each do |tx| %>
<tr>
<td><%= tx.created_at.strftime("%b %d") %></td>
<td><%= tx.category.humanize %></td>
<td class="<%= tx.amount.positive? ? 'text-green' : 'text-red' %>">
<%= tx.formatted_amount %>
</td>
<td><%= tx.formatted_balance_after %></td>
</tr>
<% end %>
Quick setup checklist
- Define your plans in
config/initializers/usage_credits.rb - Define your operations with their costs
- Uncomment the fulfillment job in
config/recurring.yml:refill_credits: class: UsageCredits::FulfillmentJob queue: default schedule: every 5 minutes - Wire up Stripe prices to your plans
- Add
spend_credits_oncalls where you want to charge credits
Wallet-level operations
usage_credits is built on the wallets gem, which provides the underlying ledger. This means wallet-level operations like transfers are available:
# Transfer credits between users (refunds, gifting, etc.)
seller.credit_wallet.transfer_to(
buyer.credit_wallet,
500,
category: :refund,
metadata: { order_id: 42 }
)
Transfers preserve expiration buckets by default. For cash-like behavior (evergreen on receive):
seller.credit_wallet.transfer_to(
buyer.credit_wallet,
500,
expiration_policy: :none
)
Going deeper
The usage_credits gem documentation covers:
- Dynamic pricing (
costs 10.credits + 1.credit_per(:mb)) - Credit expiration and rollover policies
- Upgrade/downgrade handling
- Refund behavior
- Full callback system for analytics
- Transaction audit trails
- Wallet-level transfers and expiration policies
For multi-currency wallets, game resources, or marketplace balances, see Beyond credits: wallets for games, telecom, and marketplaces.