DOCS LLMs

Beyond credits: wallets for games, telecom, and marketplaces

RailsFast's credit system is built on a powerful ledger engine. If your product needs more than simple API credits — like game currencies, mobile data plans, or marketplace balances — you can use the underlying wallets gem directly.

When to go beyond credits

If you need... Use
API usage billing usage_credits (default)
Multiple currencies per user wallets
User-to-user transfers wallets
Game resources (wood, gold, gems) wallets
Marketplace seller balances wallets
Mobile data / telecom plans wallets

Quick comparison

┌─────────────────────────────────────────────────────────────┐
│                     usage_credits                           │
│  "User spends credits on operations"                        │
│                                                             │
│  user.spend_credits_on(:generate_image)                     │
│  user.credits  → 4,950                                      │
│                                                             │
│  Built on wallets — wallet-level transfers available:       │
│  user.credit_wallet.transfer_to(friend.credit_wallet, 100)  │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                        wallets                              │
│  "User has balances that move around"                       │
│                                                             │
│  user.wallet(:gold).transfer_to(friend.wallet(:gold), 100)  │
│  user.wallet(:gold).balance  → 900                          │
│  friend.wallet(:gold).balance  → 100                        │
└─────────────────────────────────────────────────────────────┘

Example: Mobile data app (eSIM cards)

Users get monthly data and can share unused GBs with friends:

# app/models/user.rb
class User < ApplicationRecord
  include Wallets::HasWallets

  has_wallets default_asset: :data_mb  # Store in MB for precision
end
# Grant monthly data (10 GB = 10,240 MB)
user.wallet(:data_mb).credit(
  10_240,
  category: :monthly_plan,
  expires_at: 1.month.from_now
)

# Network usage depletes balance
user.wallet(:data_mb).debit(512, category: :network_usage)

# User shares 3 GB with a friend
user.wallet(:data_mb).transfer_to(
  friend.wallet(:data_mb),
  3_072,
  category: :data_gift
)

user.wallet(:data_mb).balance  # → 6,656 MB remaining

Example: Game economy (FarmVille-style)

Multiple resources, trading between players, seasonal events:

class Player < ApplicationRecord
  include Wallets::HasWallets

  has_wallets default_asset: :gold
end
# Quest rewards multiple resources
player.wallet(:wood).credit(100, category: :quest_reward)
player.wallet(:stone).credit(50, category: :quest_reward)
player.wallet(:gold).credit(25, category: :quest_reward)

# Crafting consumes resources
player.wallet(:wood).debit(30, category: :crafting)

# Premium currency from in-app purchase
player.wallet(:gems).credit(500, category: :iap_purchase)

# Seasonal currency that expires
player.wallet(:snowflakes).credit(
  1_000,
  category: :winter_event,
  expires_at: Date.new(2024, 1, 7)
)

# Trading between players
player.wallet(:gold).transfer_to(
  other_player.wallet(:gold),
  100,
  category: :trade
)

Example: Marketplace (Etsy/Fiverr-style)

Seller balances, platform fees, payouts:

class User < ApplicationRecord
  include Wallets::HasWallets

  has_wallets default_asset: :usd_cents  # Always use cents for money
end
# Order completed — credit seller minus platform fee
order_total = 5000  # $50.00
platform_fee = (order_total * 0.10).to_i
seller_earnings = order_total - platform_fee

seller.wallet(:usd_cents).credit(
  seller_earnings,
  category: :sale,
  metadata: {
    order_id: order.id,
    gross: order_total,
    fee: platform_fee
  }
)

# Seller requests payout
seller.wallet(:usd_cents).debit(
  seller.wallet(:usd_cents).balance,
  category: :payout,
  metadata: { stripe_transfer: "tr_xxx" }
)

# Buyer uses store credit
buyer.wallet(:usd_cents).debit(2000, category: :purchase)

Example: Loyalty programs & Reward points

Whether you're building a Starbucks-style loyalty program, credit card rewards, airline miles, or a Sweatcoin-style earn-from-actions app — it's the same pattern:

┌─────────────────────────────────────────────────────────────┐
│                   Loyalty program flow                      │
├─────────────────────────────────────────────────────────────┤
│  EARN              │  Purchase, action, referral, promo     │
│  HOLD              │  Points accumulate, some may expire    │
│  TRANSFER          │  Gift to family, pool with friends     │
│  REDEEM            │  Rewards, discounts, gift cards        │
└─────────────────────────────────────────────────────────────┘
class User < ApplicationRecord
  include Wallets::HasWallets
  has_wallets default_asset: :points
end

# ═══════════════════════════════════════════════════════════
# EARN — from purchases, actions, referrals
# ═══════════════════════════════════════════════════════════

# Points from purchase (1 point per dollar)
user.wallet(:points).credit(order.total_cents / 100, category: :purchase)

# Referral bonus
user.wallet(:points).credit(500, category: :referral)

# Daily check-in streaks
user.wallet(:points).credit(50, category: :daily_checkin)

# Receipt scanning (Ibotta-style)
user.wallet(:points).credit(100, category: :receipt_scan)

# ═══════════════════════════════════════════════════════════
# EXPIRING PROMOS — use-it-or-lose-it campaigns
# ═══════════════════════════════════════════════════════════

# Welcome bonus that expires in 30 days
user.wallet(:points).credit(500, category: :welcome_bonus, expires_at: 30.days.from_now)

# Double points weekend
user.wallet(:points).credit(200, category: :promo, expires_at: Date.current.next_occurring(:monday))

# Birthday reward
user.wallet(:points).credit(1000, category: :birthday, expires_at: 1.month.from_now)

# ═══════════════════════════════════════════════════════════
# TRANSFER — gift to friends, pool with family
# ═══════════════════════════════════════════════════════════

# Gift points to another member
user.wallet(:points).transfer_to(friend.wallet(:points), 500, category: :gift)

# Family pooling
family_members.each do |member|
  member.wallet(:points).transfer_to(family_pool.wallet(:points), member.wallet(:points).balance, category: :family_pool)
end

# ═══════════════════════════════════════════════════════════
# REDEEM — rewards, discounts, cash out
# ═══════════════════════════════════════════════════════════

# Redeem for a reward
user.wallet(:points).debit(2500, category: :redemption, metadata: { reward: "free_coffee" })

# Redeem for gift card
user.wallet(:points).debit(10_000, category: :cash_out, metadata: { gift_card_value: 1000 })

This pattern fits:

  • Starbucks Stars, Dunkin' Rewards
  • Airline miles (Delta SkyMiles, United MileagePlus)
  • Credit card points (Chase Ultimate Rewards, Amex MR)
  • Hotel points (Marriott Bonvoy, Hilton Honors)
  • Retail loyalty (Sephora Beauty Insider, REI Co-op)
  • Cashback apps (Rakuten, Ibotta, Fetch)
  • Fitness rewards (Sweatcoin, Stepn)

Transfer expiration policies

When transferring between users, what happens to expiration dates?

By default, transfers preserve expiration buckets. If Alice transfers credits from multiple buckets with different expirations, Bob receives multiple inbound credit transactions so those expirations remain intact.

Policy Behavior Best for
:preserve (default) Keeps source bucket expirations Loyalty points, seasonal currencies
:none Receiver gets evergreen credits Money, store credit
:fixed Explicit expires_at on receive Promotional transfers
# Default — preserve expirations from source buckets
alice.wallet(:points).transfer_to(bob.wallet(:points), 100)

# Evergreen on receive (no expiration)
alice.wallet(:usd_cents).transfer_to(
  bob.wallet(:usd_cents),
  500,
  expiration_policy: :none
)

# Fixed expiration on receive
alice.wallet(:promo_credits).transfer_to(
  bob.wallet(:promo_credits),
  200,
  expiration_policy: :fixed,
  expires_at: 30.days.from_now
)

Using both together

You can use usage_credits for your API billing AND wallets for other balances in the same app:

class User < ApplicationRecord
  has_credits                              # API credits via usage_credits
  include Wallets::HasWallets              # Additional wallets
  has_wallets default_asset: :reward_points
end
# API operations use credits
user.spend_credits_on(:generate_report)

# Rewards use wallets
user.wallet(:reward_points).credit(100, category: :signup_bonus)

They're completely isolated — different tables, different configs, no conflicts.

Going deeper

The key insight: wallets is for holding and moving value, usage_credits is for selling and consuming value. Pick the right tool for your use case.


Back to Usage credits for the standard API billing setup.