Billing is a simple library to credit and debit accounts. It provides a simple interface to calculate costs for individual things inside an application. It also provides a way to charge accounts with those costs. It is a very lightweight library.
Billing is designed as an abstract process where the implementation details are left to the user.
It does not do any of the following things:
It does do the following:
Billing is generally insipired by CanCan. You include functionality into what objects you want via modules. You define costs by creating one or more classes and include a module. Billing only makes 3 fundamental assumptions:
Billing::AccountExample
Billing::Cost
Billing::Tab
Here is an example account
class Account
# You do not have to check if there are enough
# funds in the account. Billing does that sort
# of stuff for you.
#
# The actions should be atomic!
def debit_authorized?(amount)
end
def debit(amount)
end
def credit(amount)
end
end
You could implement a stateless Account like this:
class Account
def initialize
@balance = 0
end
def debit_authorized?(amount)
@balance >= amount
end
def debit(amount)
@balance = @balance - amount
end
def credit(amount)
@balance = @balance + amount
end
end
Now let's say your application deals with SMS. Sending a SMS costs a fixed amount of money (for simplicity). Define a cost class to handle that calcation. You can do more complex calculations, but we'll go over that later.
class SmsCalculator
include Billing::Cost
def sms
1
end
end
Now the final step is to create a handler. This class should do two things:
respond_to :current_tab => true
include Billing::Tab
Here is an exampler
class Biller
include Billing::Tab
def initialize(account)
@account = account
end
def current_tab
@account
end
end
At this point we are ready to start doing things on the account. Here is an example:
account = Account.new
bank = Biller.new account
# this ensures that the account has at least "1" in it.
# It puts "1" into holds ensuring that it will be available
# when account is ready to take the debit
bank.charge_authorized? :sms
# Charge something to their account
#
# This changes the account balance!
bank.debit :sms
# Add something to their account
#
# This change the account balance!
bank.credit :sms
# You can also use a Fixnum or Float
# if you want to credit or debit an arbitrary amount
bank.credit 591
bank.debit 382.75
# alias exists so you can use charge for `charge` for `debit` and
# `refund` for `credit`. Bang methods exist as well, altough there is not
# difference between them and the other methods. Use which ever syntax
# you prefer
bank.charge 55
bank.refund! :sms
bank.charge_for 16, :contacts
bank.refund_for! 4, :emails
Cost class methods may be called in a variety of ways. Here are some
examples and the corresponding method call. Assume the cost
refers to
an instance of a class with Billing::Cost
included. Assume bank
is
includes Billing::Helpers
Scenario: something always costs the same
bank.debit :sms
# means
cost.sms()
Scenario: cost depends on an instance of the object
sms = Sms.new
bank.debit sms
# means
cost.sms(sms)
Scenario: More information is needed to calculate a cost
bank.debit :sms, :region => :north_america
# means
cost.sms(:region => :north_america)
bank.debit Sms
# means
bank.debit :sms
Perhaps you want to hit the account only after you do some code. You can
pass the the debit
and credit
methods blocks. The debit or credit
will only hit the account if the block does not raise an error.
bank.debit :sms, :region => :north_america do
begin
Gateway.deliver sms
rescue GatewayError => ex
# oh damn, the gateway may an error or something
# even though the user's SMS is valid
# they shouldn't be charged for this.
log_exception ex
raise RuntimeError
end
end
You can credit/debit for multiple charges at once using _for
methods.
They work the same way, except take an a numeric argument first to
multiply the charge by.
bank.debit_for 5, :sms
bank.credit_for 13, :searches
bank.credit_for 5, :search, :engine => :hoovers do
# whatever you need to do
end
Billings is designed to be extended via Modules. You can include more modules into the managing class. Here is the current extension list:
You can log (and audit) all transcations made through your managers by
including the Billing::Extensions::Logging
into your class. The
default logger is Logger.new($stdout).
Here is an example
class Bank
include Billings::Tab
include Billings::Extensions::Logging
# redefine the logger method if you want to use your own
# custom logger.
#
# The class uses the "info" log level
def logger
Log4r.new $stdout
end
end
Enables before_*
and after_
callbacks on debit
and
credit
class Bank
include Billings::Tab
include Billings::Extensions::Callbacks
after_debit :send_charge_notification
after_credit :send_refund_notification
def send_charge_notification
# weeee
end
def send_refund_notification
# weee
end
end
Enables before_*
and after_
callbacks on debit
and
credit
for observers
class Bank
include Billings::Tab
include Billings::Extensions::Observers
end
class BankObserver < ActiveModel::Observer
def before_debit(*args)
end
def after_debit(*args)
end
def before_credit(*args)
end
def after_credit(*args)
end
end
Billing's future is solving the things it does not do by adding them through extensions. Billing will never do everything financially related. There are things Billing will never know about your application so it will always try to remain as abstract as possible. It will slowly creep toward this boundary: charging credit cards. I would like to see Billing go into the direction of doing everything but that while remaining as abstract as possible leaving only one implementation detail: charging credit cards.
I will add functionality as I need it. Look at extensions and other billings gems.