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:
- Accept credit cards
- Talk to payment processors
- Manage application subscriptions
- Manage application service plans
- Make any assumptions about the billing/finanical side of your application
It does do the following:
- Allow you to debit accounts
- Allow you to hold funds in accounts
- Allow you to release funds in accounts
- Define cost classes for calculating services fees for individual resources.
- Allow you to implement any of the things it does not do in a standard fashion.
- Give you a mixin to allow you to manage billing in any part of your application.
- Allow you to implement guards based on an account's finanical standing.
- Allow you to do the things it does not do by including extensions.
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:
- You will have a class that acts like
- You will have classes that include
- You will manage the accounts by including
Defining An Account
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
Creating an Account Manager
Now the final step is to create a handler. This class should do two things:
respond_to :current_tab => true
Here is an exampler
class Biller include Billing::Tab def initialize(account) @account = account end def current_tab @account end end
Using the interface
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
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
Handling Failure & Advanced Usage
Perhaps you want to hit the account only after you do some code. You can
pass the the
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
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:
- Logging (can also be used for auditing)
You can log (and audit) all transcations made through your managers by
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
after_ callbacks on
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
after_ callbacks on
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.