Extface driver writing guide 2

First post about Extface driver programming (Extface driver writing guide), was about base send and receive methods. In this article, we will create the second layer methods, that will allow us to reach full device functionality. As I said before, we need to create a complex method that will:

  • build the packet for a specific command
  • send it to the device
  • re-transmit it if necessary
  • read and decode the response packet
  • check status bytes (returned with every command) for errors
  • and return unpacked data if everything is OK
  • or stop the execution by rising an exception

The following implementation is not so nice, but it works with Daisy driver, which is very similar to Datecs. My desire now is to go alive, and then we can optimize the code. First, we need to declare some constants about max retries and timeouts. Best place for them is at he beginning of the file, so we can easily manipulate them later to tune the process. And then to create the “smart” frecv and fsend methods.

app/models/extface/driver/datecs/fp550.rb

RESPONSE_TIMEOUT = 3  #seconds
INVALID_FRAME_RETRIES = 6  #count (bad length, bad checksum)
ACKS_MAX_WAIT = 60 #count / nothing is forever
NAKS_MAX_COUNT = 3 #count
def frecv(timeout) # return Frame or nil
  if frame_bytes = pull(timeout)
    return Frame.new(frame_bytes.b)
  else
    errors.add :base, "No data received from device"
    return nil
  end
end

def fsend(cmd, data = "") #return data or nil
    packet_data = build_packet(cmd, data)
    result = false
    invalid_frames = 0
    nak_messages = 0
    push packet_data
    ACKS_MAX_WAIT.times do |retries|
      errors.clear
      if resp = frecv(RESPONSE_TIMEOUT)
        if resp.valid?
          human_status_errors(resp.status)
          if errors.empty?
            result = resp.data
            break
          else
            raise errors.full_messages.join(',')
          end
        else #ack, nak or bad
          if resp.nak?
            nak_messages += 1
            if nak_messages > NAKS_MAX_COUNT
              errors.add :base, "#{NAKS_MAX_COUNT} NAKs Received. Abort!"
              break
            end
          elsif !resp.ack?
            invalid_frames += 1
            if nak_messages > INVALID_FRAME_RETRIES
              errors.add :base, "#{INVALID_FRAME_RETRIES} Broken Packets Received. Abort!"
              break
            end
          end
          push packet_data unless resp.ack?
        end
      end
      errors.add :base, "#{ACKS_MAX_WAIT} ACKs Received. Abort!"
    end
    return result
  end

Now we should find a way to test this method.The Extface core is using Redis server to communicate with the device. If Redis is available in test mode, we can run the command in a thread, then simulate the response and then join the thread to test the result. Extface core requires a job to be associated with the driver commands, otherwise it will raise an exception (This is a part of the queue mechanism, taking care of the consistent implementation of tasks. I was not planning to talk about this, but it is required for our test).

It is hard to create a fully functional test for the “fsend” method… I had to do an ActiveSupport::TestCase helper method “simulate_device_pull” to simulate the device connection. Here is the result:

test/models/extface/driver/datecs/fp550_test.rb

test "fsend" do
  job = extface_jobs(:one)
  job_thread = Thread.new do
    @driver.set_job(job)
    result = @driver.fsend(0x2C) # paper move command
  end
  simulate_device_pull(job)
  @driver.handle("\x01\x2C\x2F\x2D\x50\x04\x88\x80\xC0\x80\x80\xB0\x05\x30\x34\x35\x39\x03".b)
  simulate_device_pull(job)
  @driver.handle("\x01\x2C\x2F\x2D\x50\x04\x88\x80\xC0\x80\x80\xB0\x05\x30\x34\x35\x39\x03".b)
  job_thread.join
  assert @driver.errors.empty?
end

Hmm.. I have to think about some kind of device simulator to be able to test the drivers. This test simply checks if there is a syntax error in the code that will raise an exception. Anyway it is still useful.

I start writing this article, because I need this driver up and running in production. A friend of mine has prepared a real Datecs FP550 device connected to a windows computer in Bulgaria. It is 4700 miles away (about 8000 kilometers), but this is the magic of Extface – I will be able to control the device no mater where it is. I could not resist and did a simple paper cut command for a real test.

app/models/extface/driver/datecs/fp550.rb

def paper_cut
  device.session('Paper Cut') do |s|
    s.push build_packet(Printer::PAPER_CUT)
  end
end

app/views/extface/driver/datecs/fp550/_control.html.erb

<%= button_to 'Paper Cut', fiscal_device_path(@device), remote: true, name: :paper_cut, value: true %>

Commit, gem push, bundle update extface 😉 deploy! And surprisingly the command works.

Extface gem provides a low level communication log in ‘log/extface/:device_id/:driver_name.log’. Let’s take a look at it:

D, [2015-05-21T02:00:49.669430 #9703] DEBUG — : –> 01 25 20 4A 58 05 30 30 3E 3C 03
D, [2015-05-21T02:00:51.375926 #9736] DEBUG — : <– 01 2B 20 4A 04 80 80 92 8F 80 B2 05 30 33 3F 31 03
D, [2015-05-21T02:00:51.391514 #9703] DEBUG — : –> 01 24 21 2D 05 30 30 37 37 03
D, [2015-05-21T02:00:51.504858 #9727] DEBUG — : <– 16
D, [2015-05-21T02:00:51.573943 #9703] DEBUG — : <– 16
D, [2015-05-21T02:00:51.642706 #9727] DEBUG — : <– 16
D, [2015-05-21T02:00:51.714377 #9739] DEBUG — : <– 16 16
D, [2015-05-21T02:00:51.785687 #9712] DEBUG — : <– 16 16 01 2C 21 2D 46 04 80 80 92 8F 80 B2 05 30 34 31
D, [2015-05-21T02:00:51.855080 #9739] DEBUG — : <– 16 01 2C 21 2D 46 04 80 80 92 8F 80 B2 05 30 34 31 3C 03

This log contains the binary packets data in a way it is transferred via the TCP protocol. First the driver sends a GET_STATUS (0x4A) command, if there is no error in the response (check status bits), the driver begins to execute commands in the job session. We can see the PAPER_CUT (0x2D) command on row 3. Since the operation is delayed, the device sends several ACK packets until the paper is cut, and then returns the response packet. Looks like it is transferred in two pieces, but our driver is ready to handle this behavior, and the job ends with the receipt of 0x01..0x03 pattern packet.

The final step is to overwrite the base methods in ‘/app/models/extface/driver/base/fiscal.rb‘, which are required for a fiscal memory device driver model. This will get in the next article coming soon.

10x for reading!

Advertisements

Sencha Touch Rails


Why to mix front-end MVC framework with back-end MVC framework?

There is a trend in web programming for increasingly widespread use of javascript MVC frameworks like Angular JS , Backbone or Sencha Touch. Since we have the power of Ruby on Rails model-view-controller architectural pattern, what is the point to duplicate it on the front? The server resources are expensive and the clients are already powerful enough to deal with the visualization part of the application. Once the application is loaded, we can use Rails back-end more like API, providing data in permissive way and keeping our network and computing resources.

Therefore I upgraded my good old sencha-touch-rails gem and now will explain a little how to use it.
It provides the GPL version of Sencha Touch to Rails assets pipeline. After adding the gem to your project Gemfile, you can load the javascript part of Sencha Touch in your application.js file with:

app/assets/javascripts/application.js

//= require sencha-touch-rails

It will insert only the core of Sencha Touch framework, but with enabled Ext.Loader, so the components will be loaded on the fly when it’s requested.

Now lets rename our application.css to application.scss, and we’ll be able to use @import rather than *= require (Sencha Touch is using a lot of Sass mixins and variables in different places, so @import is more friendly to it and does not require extra tuning and load order attention).

app/assets/stylesheets/application.scss

@import "sencha-touch/themes/sencha-touch";

Sencha Touch comes with set of ready to use themes (sencha-touch, cupertino, cupertino-classic, tizen, bb10, wp and mountainview).
As I start a fresh project, I wonder how to name my first controller…  Thе minimum is not to create controller at all. I’m gonna create a blank action with enabled layout in my ApplicationController, and forward root to ‘application#blank’. It’s working.

app/controller/application_controller.rb

def blank; render inline: "", layout: true; end

app/config/routes.rb

root 'application#blank'

We need a Sencha application initialization script, and it will be nice to use coffeescript. Create init.coffee file:

app/assets/javascripts/init.coffee

Ext.application
  name: 'Sencha'
  launch: ->
    Ext.create "Ext.tab.Panel",
      fullscreen: true
      tabBarPosition: 'bottom'
      items: [
        title: 'Home'
        iconCls: 'home'
        html: 'Home'
      ,
        title: 'Settings'
        iconCls: 'settings'
        html: 'Settings'
      ]

That’s it. We have a nice looking page with bottom navigation bar and page transition effect.

Extface driver writing guide (Ruby)


I would like to show in practice how to write a new driver for https://github.com/AlexVangelov/extface module.

My example device will be Datecs Fiscal Printer FP550, which requires fast two-way communication.

First, let’s take a glance at the protocol.
We have packet messages from host to printer with sequence number and control sum:

and packet or non-packet messages from printer to host:

0x15 (NAK) – means that we have to re-transmit last packet with same sequence number (of course not infinity)
0x16 (ACK) – device has job to do and the host must wait (but nothing is forever again)

For sending packets we need a function with 2 input parameters (cmd, data). Length, sequence number and check sum will be generated automatically.
For receiving data, we can decide that if the stream contains 0x15, one or more of 0x16, or 0x03 – it may be a valid packet and must be processed.

Fork the project and create a new device driver skeleton:

git clone git://github.com/AlexVangelov/extface.git
cd ./extface
bundle install
bundle exec bin/rails generate extface:driver datecs/fp550

The last command will create:
app/models/extface/driver/datecs/fp550.rb
app/views/extface/driver/datecs/fp550/_settings.html.erb
app/views/extface/driver/datecs/fp550/_control.html.erb
test/models/extface/driver/datecs/fp550_test.rb

Layer 1 (Send & Receive)

Extface base driver functionality eliminates the need of thinking how data is transferred through the network. Sending data is easy, just call push(some_data) and it will be delivered to the device. For receiving data we use data = pull(timeout_in_seconds), but before that we should tell the driver what to expect from the input stream. There is a build in FIFO (First in – first out) buffer, that contains everything received from the device, and it is served to the driver through #handle(buffer) callback-like method. The method should return number of bytes processed, which will be auto-deleted from the beginning of buffer. If the received data is not enough to recognize a packet, the method may return nil and inspect the buffer next time, when a fresh data will be appended to it. We have to override that method:

def handle(buffer)
  if i = buffer.index(/[\x03\x16\x15]/)   # find position of frame possible delimiter
    rpush buffer[0..i]                    # this will make data available for #pull(timeout) method
    return i+1                            # return number of bytes processed
  end
end

For an unpretentious driver like simple terminal communication with line delimiter, nothing more needed. Just replace regex with index(‘\r\n\’) and you will be able to talk with device like:

device.session(‘Raw session’) do |s|
s.push “Extface rocks!”
data = s.pull(5) #wait for response 5 seconds
s.push “Extface really rocks!” if data.present?
end

Back to fiscal driver, check the frame recognition by writing #handle method test ( ‘test/models/extface/driver/datecs/fp550_test.rb’ ). Serial communication is unstable and the driver must be ready to process any random bytes without rising exception yet.

require 'test_helper'
module Extface
  class Driver::Datecs::Fp550Test < ActiveSupport::TestCase
    setup do
      @driver = extface_drivers(:datecs_fp550) # require device and driver fixtures
      @driver.flush # clear receive buffer
    end
    
    test "handle" do
      assert_equal nil, @driver.handle('bad packet')
      assert_equal 6, @driver.handle("\x01data\x03data"), "Frame not match"
      assert_equal 9, @driver.handle("pre\x01data\x03data"), "Frame with preamble not match"
      assert_equal 1, @driver.handle("\x16\x16\x01data\x03data"), "Frame with ACK not match"
      assert_equal 4, @driver.handle("pre\x15"), "NAK not match"
    end
  end
end

It’s time to declare all the command constants described in device specification. Creating a separate file keeps code clear and allows reuse it for the future Datecs drivers.

‘app/models/extface/driver/datecs/commands_v1.rb’

module Extface
  module Driver::Datecs::CommandsV1
    STX = 0x01
    PA1 = 0x05
    PA2 = 0x04
    ETX = 0x03
    NAK = 0x15
    SYN = 0x16
    module Init
      SET_MEMORY_SWITCHES         = 0x29
      SET_FOOTER                  = 0x2B
      ...
    end
    module Info
      GET_DATE_HOUR               = 0x3E
      ...
      GET_STATUS                  = 0x4A #Receiving the status bytes
    end
  end
end

Add ‘include Extface::Driver::Datecs::CommandsV1‘ to the driver model.
We need a method for building packets with 2 input parameters – 1 byte command and data binary string (not required). Auto generated sequence number, and check sum calculation procedure does not need to be public.

def build_packet(cmd, data = "")
  "".b.tap() do |packet|
    packet << STX                    #Preamble. 1 byte long. Value: 01H.
    packet << 0x20 + 4 + data.length #Number of bytes from  preamble (excluded) to  (included) plus the fixed offset of 20H
    packet << sequence_number        #Sequence number of the frame. Length : 1 byte. Value: 20H – FFH.
    packet << cmd                    #Length: 1 byte. Value: 20H - 7FH.
    packet << data                   #Length: 0 - 218 bytes for Host to printer
    packet << PA1                    #Post-amble. Length: 1 byte. Value: 05H.
    packet << bcc(packet[1..-1])     #Control sum (0000H-FFFFH). Length: 4 bytes. Value of each byte: 30H-3FH
    packet << ETX                    #Terminator. Length: 1 byte. Value: 03H.   end end private   def bcc(buffer)     sum = 0     buffer.each_byte{ |b| sum += b }     "".b.tap() do |bcc|       4.times do |halfbyte|         bcc.insert 0, (0x30 + ((sum >> (halfbyte*4)) & 0x0f)).chr
      end
    end
  end
 
  def sequence_number
    @seq ||= 0x1f
    @seq += 1
    @seq = 0x1f if @seq == 0x7f
    @seq
  end

To test the packet generation, find a valid packet example in documentation or obtain it by monitoring the vendor driver communication.

test "build packet" do
  assert_equal "\x01\x24\x20\x4a\x05\x30\x30\x39\x33\x03".b, @driver.build_packet(0x4a), "packet without data"
  assert_equal "\x01\x25\x21\x4a\x58\x05\x30\x30\x3e\x3d\x03".b, @driver.build_packet(0x4a, 'X'), "packet with data"
end

To keep the pleasure of programming, we can try to send (unconditionally) a simple command like paper cut to a real device. Prepare a rails application with extface module included in Gemfile with relative path (gem ‘extface’, path: ‘../extface’), a model with ‘has_extface_devices’ , and route ‘extface_for :model’  in resources section (see https://github.com/AlexVangelov/extface readme).  Go to model_extface_path and create a new device. The new driver ‘Datecs FP550’ is now available for selection (group Fiscal Printers & Cash Registers). Copy ‘Client Pull Url’ from device page and run extface client.

extface.exe http://localhost:3000/shops/1/shop_extface/a649a221ec1cebd0cacbc3ccf4846dba COM1,9600,8N1

There is only windows software client realized and if your development machine is unix based (like mine), you have to run it in a virtual windows machine or on a separate computer. In this case replace ‘localhost’ with IP address, and make sure your firewall settings will not block the port.

Our simple command will be push(build_packet(Printer::PAPER_CUT)). To make it available from the interface, we have to create method ‘paper_cut‘ in the driver model, and add a link in _control.html.erb (it’s the driver control panel and we will come back to it when the driver is ready)

app/models/extface/driver/datecs/fp550.rb

def paper_cut
  device.session('Paper Cut') do |s|
    s.push build_packet(Printer::PAPER_CUT)
  end
end

app/views/extface/driver/datecs/fp550/_control.html.erb

<%= button_to 'Paper Cut', fiscal_device_path(@device), remote: true, name: :paper_cut, value: true %>

The paper cut button will be accessible in control section of device page. In ideal conditions it should work, but it’s just a test and is not enough.
In the next layer of the driver we need to create a complex method that will build packet, send it to the device, re-transmit it if necessary, read the response packet, check status bytes (returned with every command) for errors, and return unpacked data if everything is OK.
But before that the driver should be able to receive packets from device.

A good approach to deal with a response packet, is to convert it to an object with clean properties (data length, sequence number, command, data bytes, status, check sum and error messages). It can be a private subclass of the driver model, with ActiveModel::Validations included. Any errors of the packet will be accessible through build in Errors object after initialization, nice!
Now I’m a little confused because it will repeat a similar functionality (check sum calculation). May be building packet should use the same subclass object… Anyway, for now we will make check sum calculation as a class method and reuse it. At the end we can play with optimizations and test memory and processor consumption for different variants.

class Frame
  include ActiveModel::Validations
  attr_reader :frame, :len, :seq, :cmd, :data, :status, :bcc
  
  validates_presence_of :frame, unless: :unpacked?
  validate :bcc_validation
  validate :len_validation
  
  def initialize(buffer)
    if match = buffer.match(/\x01(.{1})(.{1})(.{1})(.*)\x04(.{6})\x05(.{4})\x03/nm)
      @frame = match.to_a.first
      @len, @seq, @cmd, @data, @status, @bcc = match.captures
    else
      if buffer[/^\x16+$/] # only ACKs
        @ack = true
      elsif buffer.index("\x15")
        @nak = true
      end
    end
  end
  
  def ack?; !!@ack; end #should wait, response is yet to come
        
  def nak?; !!@nak; end #should retry command with same seq

  private
    def unpacked? # is it packed or unpacked message?
      @ack || @nak
    end

    def bcc_validation
      if unpacked?
        calc_bcc = self.class.bcc frame[1..-6]
        errors.add(:bcc, I18n.t('errors.messages.invalid')) if bcc != calc_bcc
      end
    end
    
    def len_validation
      unless unpacked?
        errors.add(:len, I18n.t('errors.messages.invalid')) if frame.nil? || len.ord != (frame[1..-6].length + 0x20)
      end
    end
  
    class << self       def bcc(buffer) #TODO remove old implementation         sum = 0         buffer.each_byte{ |b| sum += b }         "".tap() do |bcc|           4.times do |halfbyte|             bcc.insert 0, (0x30 + ((sum >> (halfbyte*4)) & 0x0f)).chr
          end
        end
      end
    end
end

I’m not gonna talk about regular expressions in ruby. You can extract the packet parts with any code you like. My personal practice is to play some time with an online regexp tester an then put it in the code. Then the tests will show whether it is correct. Find an example response packet and test the new class:

test "response frame" do
  frame_class = @driver.class::Frame
  assert frame_class.new("\x15").nak?, "NAK message failed"
  assert frame_class.new("\x16\x16").ack?, "ACK message failed"
  assert_nothing_raised do
    assert_equal false, frame_class.new("bad data\x01\x25\x21\x4asome broken packet\x58\x05\x30\x30\x3e\x3d\x03".b).valid?
  end
  frame = frame_class.new("\x16\x01\x2C\x2F\x2D\x50\x04\x88\x80\xC0\x80\x80\xB0\x05\x30\x34\x35\x39\x03".b)
  assert frame.valid?, "Vailid frame not recognized"
  assert_equal "\x01\x2C\x2F\x2D\x50\x04\x88\x80\xC0\x80\x80\xB0\x05\x30\x34\x35\x39\x03".b, frame.frame
  assert_equal "\x2c".b, frame.len
  assert_equal "\x2f".b, frame.seq
  assert_equal "\x2d".b, frame.cmd
  assert_equal "\x50".b, frame.data
  assert_equal "\x88\x80\xC0\x80\x80\xB0".b, frame.status
  assert_equal "\x30\x34\x35\x39".b, frame.bcc
  #bad check sum
  frame = frame_class.new("\x01\x2C\x2F\x2D\x50\x04\x88\x80\xC0\x80\x80\xB0\x05\x30\x34\x35\x38\x03".b)
  assert_equal false, frame.valid?
  assert frame.errors.messages[:bcc]
  #bad length
  frame = frame_class.new("\x01\x2b\x2F\x2D\x50\x04\x88\x80\xC0\x80\x80\xB0\x05\x30\x34\x35\x38\x03".b)
  assert_equal false, frame.valid?
  assert frame.errors.messages[:len]
end

The last thing we need before we move to the next layer is the ability to decode status bytes (included in each response packet). Messages must be human readable. Read device documentation carefully and check the bits that must stop session execution.

def human_status_errors(status) #inspect 6 bytes status
  status_0 = status[0].ord
  errors.add :base, "Fiscal Device General Error" unless (status_0 & 0x20).zero?
  ...
end

The topic is very spacious for a single article, so that’s it for now (will be continue)
10x for reading!

20 May 2015: Extface driver writing guide 2

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

app/models/po.rb

class Po < ActiveRecord::Base
  has_billing
end

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

test/model/po_test.rb

require 'test_helper'

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

$ 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 = @pos.billing_accounts.new do |b|
    b.charge 2
    b.pay
  end
  assert bill.save
  assert bill.balance.zero?
end

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 %>
<%= f.select :default_payment_type, PaymentType.all.collect{ |pt| [pt.name, pt.id] } %>

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:

app/tests/fixtures/pos.yml

one:
  name: Pos1
  default_payment_type: one

two:
  name: Pos2
  default_payment_type: two

app/tests/fixtures/payment_types.yml

one:
  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 = @pos.billing_accounts.new do |b|
    b.charge 2
    b.pay payment_types(:one), 1
  end
  assert bill.save, "Save with payment failed, message: #{bill.errors.full_messages.join(', ')}"
  assert_equal '-1 USD'.to_money, bill.balance
end

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!