Skip to content

Commit

Permalink
First draft of file structure, port classes from Compete.
Browse files Browse the repository at this point in the history
  • Loading branch information
ataber committed Oct 1, 2014
1 parent ab98de0 commit 9df2067
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--color
--format documentation
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
source "https://rubygems.org"
gemspec
26 changes: 26 additions & 0 deletions lib/rubill.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Rubill
class << self
attr_accessor :configuration
end

def self.configure
self.configuration ||= Configuration.new
yield(configuration)
end

class Configuration
attr_accessor :user_name
attr_accessor :password
attr_accessor :dev_key
attr_accessor :org_id

def to_hash
{
"user_name" => user_name,
"password" => password,
"dev_key" => dev_key,
"org_id" => org_id,
}
end
end
end
63 changes: 63 additions & 0 deletions lib/rubill/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
module Rubill
class Base
attr_accessor :remote_record

delegate :[], to: :remote_record

class NotFound < StandardError; end

def initialize(remote)
self.remote_record = remote
end

def self.active
# There is also a way to list only active via the API but it's opaque
# and unlikely to be much faster than doing it in Ruby
all_remote.select do |record|
record[:isActive] == "1"
end
end

def id
remote_record[:id]
end

def update
self.class.update(remote_record)
end

def delete
self.class.delete(remote_record[:id])
end

def self.find(id)
new(session.read(remote_class_name, id))
end

def self.create(data)
new(session.create(remote_class_name, data.merge({entity: remote_class_name})))
end

def self.update(data)
session.update(remote_class_name, data)
end

def self.delete(id)
session.delete(remote_class_name, id)
end

def self.all
session.list(remote_class_name)
end

def self.session
Session.instance
end

private

def self.remote_class_name
raise NotImplementedError
end
end
end
15 changes: 15 additions & 0 deletions lib/rubill/bill.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Rubill
class Bill < Base
def self.send_payment(opts)
session.send_payment(opts)
end

def self.void_sent_payment(id)
session.void_sent_payment(id)
end

def self.remote_class_name
"Bill"
end
end
end
36 changes: 36 additions & 0 deletions lib/rubill/customer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module Rubill
class Customer < Base
def self.find_by_name(name)
record = active_remote.detect do |d|
d[:name] == name
end

raise NotFound unless record
new(record)
end

def self.receive_payment(opts)
session.receive_payment(opts)
end

def self.void_received_payment(id)
session.void_received_payment(id)
end

def create_credit(amount, description="")
data = {
customerId: remote_record.id,
amount: amount.to_f,
description: description,
paymentType: "5",
paymentDate: Date.today,
}

self.class.receive_payment(data)
end

def self.remote_class_name
"Customer"
end
end
end
32 changes: 32 additions & 0 deletions lib/rubill/invoice.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module Rubill
class Invoice < Base
def amount_paid
amount - amount_due
end

def amount
# to_s then to_d because it returns a float
remote_record[:amount].to_s.to_d
end

def amount_due
# to_s then to_d because it returns a float
remote_record[:amountDue].to_s.to_d
end

def self.invoice_line_item(amount, description, item_id)
{
entity: "InvoiceLineItem",
quantity: 1,
itemId: item_id,
# must to_f amount otherwise decimal will be converted to string in JSON
price: amount.to_f,
description: description,
}
end

def self.remote_class_name
"Invoice"
end
end
end
108 changes: 108 additions & 0 deletions lib/rubill/session.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
module Rubill
class Session
include HTTParty
include Singleton

base_uri "https://api.bill.com/api/v2"

CREDENTIALS = Rubill.configuration.to_hash

delegate :_post, to: self

attr_accessor :session_id

def initialize
login
end

def read(entity, id)
_post("/Crud/Read/#{entity}.json", options(id: id))
end

def create(entity, object={})
_post("/Crud/Create/#{entity}.json", options(obj: object))
end

def update(entity, object={})
_post("/Crud/Update/#{entity}.json", options(obj: object))
end

def delete(entity, id)
_post("/Crud/Delete/#{entity}.json", options(id: id))
end

def receive_payment(opts={})
_post("/RecordARPayment.json", options(opts))
end

def send_payment(opts={})
_post("/RecordAPPayment.json", options(opts))
end

def void_sent_payment(id)
_post("/VoidAPPayment.json", options(sentPayId: id))
end

def void_received_payment(id)
_post("/VoidARPayment.json", options(id: id))
end

def list(entity)
# Note: this method returns ALL of desired entity, including inactive
result = []
start = 0
step = 999
loop do
chunk = _post("/List/#{entity}.json", options(start: start, max: step)).presence

if chunk
result += chunk
start += step
else
break
end
end

result
end

def login
self.session_id = self.class.login
end

def self.login
login_options = {
headers: default_headers,
query: {
password: CREDENTIALS["password"],
userName: CREDENTIALS["user_name"],
devKey: CREDENTIALS["dev_key"],
orgId: CREDENTIALS["org_id"],
}
}
login = _post("/Login.json", login_options)
login[:sessionId]
end

def options(data={})
{
headers: self.class.default_headers,
query: {
sessionId: session_id,
devKey: CREDENTIALS["dev_key"],
data: data.to_json,
},
}
end

def self.default_headers
{"Content-Type" => "application/x-www-form-urlencoded"}
end

def self._post(url, options)
result = JSON.parse(post(url, options).body).with_indifferent_access
raise result[:response_data][:error_message] unless result[:response_status] == 0
result[:response_data]
end
end
end
16 changes: 16 additions & 0 deletions rubill.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Gem::Specification.new do |s|
s.name = 'rubill'
s.version = '0.0.1'
s.date = '2014-09-31'
s.summary = "Interface with Bill.com"
s.description = "A Ruby interface to Bill.com's API"
s.authors = ["Andrew Taber"]
s.email = '[email protected] '
s.files = ["lib/rubill.rb"]
s.homepage = 'http://rubygems.org/gems/rubill'
s.license = 'MIT'

s.add_dependency "httparty"

s.add_development_dependency "rspec"
end
8 changes: 8 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require "bundler/setup"
Bundler.setup

require "rubill"

RSpec.configure do |c|
end

0 comments on commit 9df2067

Please sign in to comment.