0.0
The project is in a healthy, maintained state
Basic Wallet implementation for your Ruby on Rails app
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

>= 7.0.0
 Project Readme

SimpleWallet

A lightweight, drop-in wallet engine for Ruby on Rails apps. simple_wallet adds a balance ledger to any of your ActiveRecord models — users, accounts, organisations — letting you credit, debit, and query balances with minimal setup.

Features

  • Attach a wallet to any ActiveRecord model with a few lines of code
  • Credit and debit operations backed by a double-entry-style transaction log
  • Query current balance and full transaction history per wallet owner
  • Database-backed — no external services required
  • Rails engine: migrations and models are mounted directly into your app

Requirements

  • Ruby >= 3.0
  • Rails >= 7.0
  • PostgreSQL

Installation

Add the gem to your Gemfile:

gem "simple_wallet"

Install it:

bundle install

Copy and run the migrations:

bin/rails simple_wallet_engine:install:migrations
bin/rails db:migrate db:test:prepare

Usage

Attaching a wallet to a model

bin/rails g migration add_account_to_users simple_wallet_account:references:uniq

Tweak the migration to allow null values (if you have existing records):

class AddAccountToUsers < ActiveRecord::Migration[8.0]
  def change
    add_reference :users, :simple_wallet_account, index: {unique: true}, null: true, foreign_key: true
  end
end

Run migrations:

bin/rails db:migrate db:test:prepare

And finally add Account reference to your model:

class User < ApplicationRecord
  belongs_to :account,
    class_name: "::SimpleWallet::Account",
    optional: true,
    foreign_key: :simple_wallet_account_id

  before_create :create_simple_wallet_account
  
  private
  
  def create_simple_wallet_account
    create_account!
  end
end

Querying the balance

user = User.create(first_name: "Marcin", last_name: "Urbanski")

user.account
=>
  #<SimpleWallet::Account:0x0000710ebe38cac8
    id: 1,
    balance: 0,
    income: 0,
    outcome: 0,
    created_at: "2026-04-11 09:05:41.534180000 -0700",
    updated_at: "2026-04-11 09:05:41.534180000 -0700">

Crediting

SimpleWallet::AccountCreditingService.new(account: user.account, amount: 1_000, source: User.admins.first, note: "Bonus!").credit

user.reload.account
=>
  #<SimpleWallet::Account:0x0000710ebfc29108
    id: 1,
    balance: 1000,
    income: 1000,
    outcome: 0,
    created_at: "2026-04-11 09:11:57.198448000 -0700",
    updated_at: "2026-04-11 09:11:57.198448000 -0700">

Debiting

SimpleWallet::AccountDebitingService.new(account: user.account, amount: 200, source: User.admins.first, note: "AI model invoice").debit

user.reload.account
=>
  #<SimpleWallet::Account:0x0000710ebd85d350
    id: 1,
    balance: 800,
    income: 1000,
    outcome: -200,
    created_at: "2026-04-11 09:11:57.198448000 -0700",
    updated_at: "2026-04-11 09:11:57.198448000 -0700">

Insufficient funds

service = SimpleWallet::AccountDebitingService.new(account: user.account, amount: 1_000_000, source: User.admins.first, note: "AI model invoice")

service.debit
=> false

service.errors.messages
=> {:amount=>["exceeds available balance"]}

# Debit up to account balance:
service = SimpleWallet::AccountDebitingService.new(account: user.account, amount: 1_000_000, source: User.admins.first, note: "AI model invoice", up_to_account_balance: true)

service.debit
=> true

user.reload.account
=>
  #<SimpleWallet::Account:0x0000710ebd8bc080
    id: 5,
    balance: 0,
    income: 1000,
    outcome: -1000,
    created_at: "2026-04-11 09:11:57.198448000 -0700",
    updated_at: "2026-04-11 09:11:57.198448000 -0700">

user.account.transactions
=>
  [
   # ...
   #<SimpleWallet::Transaction::Debit:0x0000710ebd8ba3c0
     id: 2,
     type: "SimpleWallet::Transaction::Debit",
     account_id: 1,
     source_type: "User", # Admin
     source_id: 11,
     pre_account_balance: 800,
     amount: -800, # Debited as much as we could.
     note: "AI model invoice",
     created_at: "2026-04-11 09:27:38.958150000 -0700",
     updated_at: "2026-04-11 09:27:38.958150000 -0700">]

Transfering between accounts

employer = User.create(first_name: "Rich", last_name: "Employer")
employee = User.create(first_name: "Marcin", last_name: "Urbanski")

SimpleWallet::AccountCreditingService.new(account: employer.account, amount: 1_000_000, note: "From venture capitalist").credit
SimpleWallet::TransferService.new(from: employer.reload.account, to: employee.reload.account, amount: 15, note: "We really appreciate your efforts!").transfer

employer.account.transactions.last
=>
  #<SimpleWallet::Transaction::Debit:0x0000710ebf569d90
    id: 6,
    type: "SimpleWallet::Transaction::Debit",
    account_id: 6,
    source_type: nil,
    source_id: nil,
    pre_account_balance: 1000000,
    amount: -15,
    note: "We really appreciate your efforts!",
    created_at: "2026-04-11 09:40:27.018567000 -0700",
    updated_at: "2026-04-11 09:40:27.018567000 -0700">

employee.account.transactions.last
=>
  #<SimpleWallet::Transaction::Credit:0x0000710ebf567f90
    id: 7,
    type: "SimpleWallet::Transaction::Credit",
    account_id: 7,
    source_type: nil,
    source_id: nil,
    pre_account_balance: 0,
    amount: 15,
    note: "We really appreciate your efforts!",
    created_at: "2026-04-11 09:40:27.024512000 -0700",
    updated_at: "2026-04-11 09:40:27.024512000 -0700">

Transaction history

user.account.transactions

=>
  [#<SimpleWallet::Transaction::Credit:0x0000710ebd85a510
    id: 1,
    type: "SimpleWallet::Transaction::Credit",
    account_id: 1,
    source_type: "User", # Admin
    source_id: 11,
    pre_account_balance: 0,
    amount: 1000,
    note: "Bonus!",
    created_at: "2026-04-11 09:12:19.260240000 -0700",
    updated_at: "2026-04-11 09:12:19.260240000 -0700">,
   #<SimpleWallet::Transaction::Debit:0x0000710ebd85a3d0
    id: 2,
    type: "SimpleWallet::Transaction::Debit",
    account_id: 1,
    source_type: "User", # Admin
    source_id: 11,
    pre_account_balance: 1000,
    amount: -200,
    note: "AI model invoice",
    created_at: "2026-04-11 09:15:16.248670000 -0700",
    updated_at: "2026-04-11 09:15:16.248670000 -0700">]

Contributing

Bug reports and pull requests are welcome on GitHub.

  1. Fork the repository
  2. Create a feature branch (git checkout -b my-feature)
  3. Commit your changes (git commit -am 'Add my feature')
  4. Push the branch (git push origin my-feature)
  5. Open a Pull Request

Please run rubocop before submitting — the project ships with a .rubocop.yml.

License

The gem is available as open source under the terms of the MIT License.