DOCS LLMs

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

  1. Define your plans in config/initializers/usage_credits.rb
  2. Define your operations with their costs
  3. Uncomment the fulfillment job in config/recurring.yml:
    refill_credits:
      class: UsageCredits::FulfillmentJob
      queue: default
      schedule: every 5 minutes
  4. Wire up Stripe prices to your plans
  5. Add spend_credits_on calls 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.