Ruby on Rails app with billing and fiscalization (Part1: Billing model)


Example of using  billing and extface modules.
Create a new app:

$ rails new shop
$ cd shop

Add to your ‘Gemfile’:

gem "billing"
gem "extface"

$ bundle install

To use latest gems, make sure that your Rails version is minimum 4.0.5.
Let’s create our point of sale model and controller .
Note: Our model will be called Po. If you want to use singular “pos”, and plural “poses” you have to edit ‘config/initializers/inflections.rb ‘ (Rails Inflections)

$ bundle exec bin/rails g scaffold pos name:string

Edit config/routes.rb and set:

root 'pos#index' 

$ bundle exec bin/rake db:migrate $ bundle exec bin/rails server

Check that your shop application is up and running at http://localhost:3000/
You can create first pos object at http://localhost:3000/pos/new, choose name and save .

That was standard. Now add the billing module :

$ bundle exec bin/rake billing:install
$ bundle exec bin/rake db:migrate

To integrate billing to some model just add has_billing to it


class Po < ActiveRecord::Base

According to the rules of programming it’s time to write some model tests.


require 'test_helper'

class PoTest < ActiveSupport::TestCase
  setup do
    @pos = pos(:one)
  test 'check billing' do
    bill = do |b|
      b.charge 2
      b.modify(-1) #discount
    assert_equal '1 USD'.to_money,
    assert_equal '-1 USD'.to_money, bill.balance

$ bundle exec bin/rake test
Result: 8 runs, 16 assertions, 0 failures, 0 errors, 0 skips

OK, we have a working billing account!
An error like “undefined method `billing_accounts'” means that “has_billing” was not correctrly added to your pos model.  

Lets try payment by writing another test:

test 'check billing account payment' do
  bill = do |b|
    b.charge 2

Test will fail with message “Payments payment type can’t be blank” because we don’t have default payment type. If payment type is not specified, billing account is looking for #default_payment_type in it’s parent model. This method may be implemented in our pos model or delegated to some settings object. Actually, we don’t have any payment types yet.

Billing module comes with STI model Billing::PaymentType. The lazy way is to create one Billing::PaymentType.create!(name: ‘Cash’, cash: true) and point default_payment_type to #first, but it’s not cool.
We can create global payment types for all pos objects, or separate types for each of them.

$ bundle exec bin/rails g scaffold payment_type name:string cash:boolean fiscal:boolean –skip-migration

We don’t need migration (–skip-migration), cause we use Single Table Inheritance.
Now change the class declaration in app/models/payment_type.rb

class PaymentType < Billing::PaymentType

Create a payment type by the user interface at http://localhost:3000/payment_types/new
And now implement default_payment_type for our pos model (migration, model, view, controller, test).

$ bundle exec bin/rails g migration add_default_payment_type_to_pos default_payment_type_id:integer
$ bundle exec bin/rake db:migrate

Add to app/models/po.rb:

belongs_to :default_payment_type, class_name: 'PaymentType'

Modify view app/views/pos/_form.html.erb

<%= f.label :default_payment_type %>
<%= :default_payment_type, PaymentType.all.collect{ |pt| [,] } %>

Modify app/controllers/pos_controller.rb to allow new param (Strong Parameters)

params.require(:po).permit(:name, :default_payment_type_id)

Get back to the tests…
Add the new attributes to pos and payment_types fixtures:


  name: Pos1
  default_payment_type: one

  name: Pos2
  default_payment_type: two


  name: Cash
  cash: true
  fiscal: false
  type: PaymentType

Tests should pass.
Now add another test with partial payment by type:

test "check billing parital payment by type" do
  bill = do |b|
    b.charge 2 payment_types(:one), 1
  assert, "Save with payment failed, message: #{bill.errors.full_messages.join(', ')}"
  assert_equal '-1 USD'.to_money, bill.balance

Test should pass. We have billing accounts for pos model with charges, discounts, surcharges and payments by type.

Next part will deal with the visualization. 10x for reading!