freshbooks.rb 3.0.18 → 3.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/History.txt +8 -0
  4. data/Manifest.txt +21 -1
  5. data/README.md +61 -0
  6. data/Rakefile +8 -13
  7. data/freshbooks.rb.gemspec +43 -0
  8. data/lib/freshbooks.rb +16 -2
  9. data/lib/freshbooks/address.rb +7 -0
  10. data/lib/freshbooks/api.rb +7 -0
  11. data/lib/freshbooks/autobill.rb +9 -0
  12. data/lib/freshbooks/base.rb +35 -9
  13. data/lib/freshbooks/budget.rb +7 -0
  14. data/lib/freshbooks/card.rb +9 -0
  15. data/lib/freshbooks/client.rb +3 -0
  16. data/lib/freshbooks/connection.rb +34 -25
  17. data/lib/freshbooks/contact.rb +9 -0
  18. data/lib/freshbooks/credits.rb +7 -0
  19. data/lib/freshbooks/estimate.rb +3 -3
  20. data/lib/freshbooks/expiration.rb +8 -0
  21. data/lib/freshbooks/gateway.rb +10 -0
  22. data/lib/freshbooks/gateway_transaction.rb +8 -0
  23. data/lib/freshbooks/invoice.rb +2 -2
  24. data/lib/freshbooks/language.rb +9 -0
  25. data/lib/freshbooks/line.rb +2 -0
  26. data/lib/freshbooks/links.rb +1 -1
  27. data/lib/freshbooks/payment.rb +2 -1
  28. data/lib/freshbooks/project.rb +4 -3
  29. data/lib/freshbooks/recurring.rb +5 -2
  30. data/lib/freshbooks/schema/definition.rb +2 -2
  31. data/lib/freshbooks/staff.rb +5 -0
  32. data/lib/freshbooks/system.rb +14 -0
  33. data/lib/freshbooks/tax.rb +12 -0
  34. data/lib/freshbooks/time_entry.rb +1 -0
  35. data/lib/freshbooks/xml_serializer.rb +5 -0
  36. data/lib/freshbooks/xml_serializer/serializers.rb +2 -2
  37. data/test/fixtures/freshbooks_credentials.yml +3 -0
  38. data/test/fixtures/recurring_create_response.xml +4 -0
  39. data/test/fixtures/recurring_get_response.xml +59 -0
  40. data/test/fixtures/recurring_list_response.xml +61 -0
  41. data/test/live_connection_test.rb +28 -0
  42. data/test/test_base.rb +59 -0
  43. data/test/test_connection.rb +39 -32
  44. data/test/test_helper.rb +20 -5
  45. data/test/test_invoice.rb +23 -37
  46. data/test/test_recurring.rb +98 -0
  47. metadata +67 -46
  48. data/README +0 -44
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ data.tar.gz: 298b0e8370286fe4a1ab625edf137ecf67cc7873
4
+ metadata.gz: 1cd14616e90a9f14b70982739d87b7e728b65bf2
5
+ SHA512:
6
+ data.tar.gz: 5d7c77d67d5bc1773ccc33580c1ce21fe115be11e4e51002218555df4f50be8d1919fa45ae7c11bf008887950d9dd66105c4c157829e3af43b5acbf12a9c7a36
7
+ metadata.gz: ada57b413deb19cfcf39756563d5c2a42681fd01345d2377156c6325bc9d8a19c6e7b1e9fdf7f0cd865144d4222a6675f1eda01c27b5aba58d6e52e3c653b662
File without changes
@@ -1,3 +1,11 @@
1
+ == 2011-11-20
2
+ * Updated API to include currency code & language (In most API calls) [anlek]
3
+ * Updated time_entry to include "billed" attribute [anlek]
4
+ * Added new API calls: Gateway, Language, System, Tax [anlek]
5
+ * Added Staff.current [anlek]
6
+ * Added gateway_transaction to payment [anlek]
7
+ * Ensured that deprecated items are no longer included in the results (This MIGHT break some existing code) [anlek]
8
+
1
9
  == 0.0.1 2009-02-25
2
10
 
3
11
  * 3.0 major enhancement:
@@ -1,17 +1,29 @@
1
1
  History.txt
2
2
  LICENSE
3
3
  Manifest.txt
4
- README
4
+ README.md
5
5
  Rakefile
6
+ freshbooks.rb.gemspec
6
7
  lib/freshbooks.rb
8
+ lib/freshbooks/address.rb
9
+ lib/freshbooks/api.rb
10
+ lib/freshbooks/autobill.rb
7
11
  lib/freshbooks/base.rb
12
+ lib/freshbooks/budget.rb
13
+ lib/freshbooks/card.rb
8
14
  lib/freshbooks/category.rb
9
15
  lib/freshbooks/client.rb
10
16
  lib/freshbooks/connection.rb
17
+ lib/freshbooks/contact.rb
18
+ lib/freshbooks/credits.rb
11
19
  lib/freshbooks/estimate.rb
12
20
  lib/freshbooks/expense.rb
21
+ lib/freshbooks/expiration.rb
22
+ lib/freshbooks/gateway.rb
23
+ lib/freshbooks/gateway_transaction.rb
13
24
  lib/freshbooks/invoice.rb
14
25
  lib/freshbooks/item.rb
26
+ lib/freshbooks/language.rb
15
27
  lib/freshbooks/line.rb
16
28
  lib/freshbooks/links.rb
17
29
  lib/freshbooks/list_proxy.rb
@@ -22,7 +34,9 @@ lib/freshbooks/response.rb
22
34
  lib/freshbooks/schema/definition.rb
23
35
  lib/freshbooks/schema/mixin.rb
24
36
  lib/freshbooks/staff.rb
37
+ lib/freshbooks/system.rb
25
38
  lib/freshbooks/task.rb
39
+ lib/freshbooks/tax.rb
26
40
  lib/freshbooks/time_entry.rb
27
41
  lib/freshbooks/xml_serializer.rb
28
42
  lib/freshbooks/xml_serializer/serializers.rb
@@ -30,10 +44,15 @@ script/console
30
44
  script/destroy
31
45
  script/generate
32
46
  test/fixtures/freshbooks_credentials.sample.yml
47
+ test/fixtures/freshbooks_credentials.yml
33
48
  test/fixtures/invoice_create_response.xml
34
49
  test/fixtures/invoice_get_response.xml
35
50
  test/fixtures/invoice_list_response.xml
51
+ test/fixtures/recurring_create_response.xml
52
+ test/fixtures/recurring_get_response.xml
53
+ test/fixtures/recurring_list_response.xml
36
54
  test/fixtures/success_response.xml
55
+ test/live_connection_test.rb
37
56
  test/mock_connection.rb
38
57
  test/schema/test_definition.rb
39
58
  test/schema/test_mixin.rb
@@ -43,3 +62,4 @@ test/test_helper.rb
43
62
  test/test_invoice.rb
44
63
  test/test_list_proxy.rb
45
64
  test/test_page.rb
65
+ test/test_recurring.rb
@@ -0,0 +1,61 @@
1
+ ## Home
2
+
3
+ https://github.com/bcurren/freshbooks.rb
4
+
5
+ ## Todo
6
+
7
+ Please send me a message if you are interested in contributing. Here is a quick overview of some things I'd like to add to the gem:
8
+ * Merge in forked versions of this gem. (Any idea what forks are worth while?)
9
+ * Provide a way to retrieve the error message with actions beside list are called.
10
+ * Make ListProxy optional on list actions. Some people have valid reason to deal with pagination.
11
+ * Add OAuth.
12
+ * Add WebHooks.
13
+ * More tests, including integration tests.
14
+ * Write some documentation and examples.
15
+ * Launch an official 3.0 (maybe call it 4.0) version of the gem.
16
+ * Post the official version to rubygems.
17
+ * Ensure rubygems aren't required for this gem.
18
+
19
+ ## About
20
+
21
+ FreshBooks.rb is a Ruby interface to the FreshBooks API. It exposes easy-to-use classes and methods for interacting with your FreshBooks account.
22
+
23
+ ***NOTE:** These examples are out of date and need to be updated. I will be writing documentation for all the updates soon and will be pushing the changes to rubyforge in the near future.*
24
+
25
+ ## Examples
26
+
27
+ Initialization:
28
+
29
+ FreshBooks::Base.establish_connection('sample.freshbooks.com', 'mytoken')
30
+
31
+ Updating a client name:
32
+
33
+ clients = FreshBooks::Client.list
34
+ client = clients[0]
35
+ client.first_name = 'Suzy'
36
+ client.update
37
+
38
+ Updating an invoice:
39
+
40
+ invoice = FreshBooks::Invoice.get(4)
41
+ invoice.lines[0].quantity += 1
42
+ invoice.update
43
+
44
+ Creating a new item
45
+
46
+ item = FreshBooks::Item.new
47
+ item.name = 'A sample item'
48
+ item.create
49
+
50
+ ## License
51
+
52
+ This work is distributed under the MIT License. Use/modify the code however you like.
53
+
54
+ ## Download
55
+
56
+ `sudo gem install bcurren-freshbooks.rb`
57
+
58
+ ## Credits
59
+
60
+ FreshBooks.rb is written and maintained by [Ben Curren](https://github.com/bcurren) at [Outright.com](http://outright.com/). [Ben Vinegar](http://benv.ca/) was the original developer and we have taken over maintenance of the gem from now on.
61
+
data/Rakefile CHANGED
@@ -1,27 +1,22 @@
1
- %w[rubygems rake rake/clean fileutils newgem rubigen hoe].each { |f| require f }
1
+ %w[rubygems rake rake/clean fileutils rubigen hoe].each { |f| require f }
2
2
  require File.dirname(__FILE__) + '/lib/freshbooks'
3
3
 
4
+ Hoe.plugin :newgem
5
+ Hoe.plugin :gemcutter
6
+
4
7
  # Generate all the Rake tasks
5
8
  # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
9
  $hoe = Hoe.spec('freshbooks.rb') do |p|
7
- p.developer('Ben Curren', 'ben@outright.com')
10
+ p.developer('Ben Curren', 'ben@thecurrens.com')
8
11
  p.summary = ''
9
12
  p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
10
- p.rubyforge_name = p.name # TODO this is default value
11
- p.extra_deps = [ ['activesupport', '>= 0'] ]
13
+ p.readme_file = "README.md"
14
+ p.rubyforge_name = p.name
15
+ p.extra_deps = [ ['activesupport', '>= 3.0.0'] ]
12
16
  p.extra_dev_deps = [
13
17
  ['newgem', ">= #{::Newgem::VERSION}"],
14
18
  ['mocha', ">= 0.9.4"]
15
19
  ]
16
20
 
17
21
  p.clean_globs |= %w[**/.DS_Store tmp *.log]
18
- path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
19
- p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
20
- p.rsync_args = '-av --delete --ignore-errors'
21
22
  end
22
-
23
- require 'newgem/tasks' # load /tasks/*.rake
24
- Dir['tasks/**/*.rake'].each { |t| load t }
25
-
26
- # TODO - want other tests/tasks run by default? Add them to the list
27
- # task :default => [:spec, :features]
@@ -0,0 +1,43 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "freshbooks.rb"
5
+ s.version = "3.0.25"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Ben Curren"]
9
+ s.date = "2014-02-13"
10
+ s.description = ""
11
+ s.email = ["ben@thecurrens.com"]
12
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "README.md"]
13
+ s.files = ["History.txt", "LICENSE", "Manifest.txt", "README.md", "Rakefile", "freshbooks.rb.gemspec", "lib/freshbooks.rb", "lib/freshbooks/address.rb", "lib/freshbooks/api.rb", "lib/freshbooks/autobill.rb", "lib/freshbooks/base.rb", "lib/freshbooks/budget.rb", "lib/freshbooks/card.rb", "lib/freshbooks/category.rb", "lib/freshbooks/client.rb", "lib/freshbooks/connection.rb", "lib/freshbooks/contact.rb", "lib/freshbooks/credits.rb", "lib/freshbooks/estimate.rb", "lib/freshbooks/expense.rb", "lib/freshbooks/expiration.rb", "lib/freshbooks/gateway.rb", "lib/freshbooks/gateway_transaction.rb", "lib/freshbooks/invoice.rb", "lib/freshbooks/item.rb", "lib/freshbooks/language.rb", "lib/freshbooks/line.rb", "lib/freshbooks/links.rb", "lib/freshbooks/list_proxy.rb", "lib/freshbooks/payment.rb", "lib/freshbooks/project.rb", "lib/freshbooks/recurring.rb", "lib/freshbooks/response.rb", "lib/freshbooks/schema/definition.rb", "lib/freshbooks/schema/mixin.rb", "lib/freshbooks/staff.rb", "lib/freshbooks/system.rb", "lib/freshbooks/task.rb", "lib/freshbooks/tax.rb", "lib/freshbooks/time_entry.rb", "lib/freshbooks/xml_serializer.rb", "lib/freshbooks/xml_serializer/serializers.rb", "script/console", "script/destroy", "script/generate", "test/fixtures/freshbooks_credentials.sample.yml", "test/fixtures/freshbooks_credentials.yml", "test/fixtures/invoice_create_response.xml", "test/fixtures/invoice_get_response.xml", "test/fixtures/invoice_list_response.xml", "test/fixtures/recurring_create_response.xml", "test/fixtures/recurring_get_response.xml", "test/fixtures/recurring_list_response.xml", "test/fixtures/success_response.xml", "test/live_connection_test.rb", "test/mock_connection.rb", "test/schema/test_definition.rb", "test/schema/test_mixin.rb", "test/test_base.rb", "test/test_connection.rb", "test/test_helper.rb", "test/test_invoice.rb", "test/test_list_proxy.rb", "test/test_page.rb", "test/test_recurring.rb", ".gemtest"]
14
+ s.homepage = "https://github.com/bcurren/freshbooks.rb"
15
+ s.licenses = ["MIT"]
16
+ s.rdoc_options = ["--main", "README.md"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = "freshbooks.rb"
19
+ s.rubygems_version = "2.0.14"
20
+ s.summary = ""
21
+ s.test_files = ["test/schema/test_definition.rb", "test/schema/test_mixin.rb", "test/test_base.rb", "test/test_connection.rb", "test/test_helper.rb", "test/test_invoice.rb", "test/test_list_proxy.rb", "test/test_page.rb", "test/test_recurring.rb", "test/live_connection_test.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ s.specification_version = 4
25
+
26
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
+ s.add_runtime_dependency(%q<activesupport>, [">= 3.0.0"])
28
+ s.add_development_dependency(%q<newgem>, [">= 1.5.3"])
29
+ s.add_development_dependency(%q<mocha>, [">= 0.9.4"])
30
+ s.add_development_dependency(%q<hoe>, ["~> 3.9"])
31
+ else
32
+ s.add_dependency(%q<activesupport>, [">= 3.0.0"])
33
+ s.add_dependency(%q<newgem>, [">= 1.5.3"])
34
+ s.add_dependency(%q<mocha>, [">= 0.9.4"])
35
+ s.add_dependency(%q<hoe>, ["~> 3.9"])
36
+ end
37
+ else
38
+ s.add_dependency(%q<activesupport>, [">= 3.0.0"])
39
+ s.add_dependency(%q<newgem>, [">= 1.5.3"])
40
+ s.add_dependency(%q<mocha>, [">= 0.9.4"])
41
+ s.add_dependency(%q<hoe>, ["~> 3.9"])
42
+ end
43
+ end
@@ -8,15 +8,27 @@ rescue LoadError
8
8
  gem 'activesupport'
9
9
  require 'active_support'
10
10
  end
11
+ require 'active_support/all'
11
12
 
12
13
  require 'freshbooks/base'
14
+ require 'freshbooks/address'
15
+ require 'freshbooks/api'
16
+ require 'freshbooks/autobill'
17
+ require 'freshbooks/budget'
18
+ require 'freshbooks/card'
13
19
  require 'freshbooks/category'
14
20
  require 'freshbooks/client'
15
21
  require 'freshbooks/connection'
22
+ require 'freshbooks/credits'
16
23
  require 'freshbooks/estimate'
17
24
  require 'freshbooks/expense'
25
+ require 'freshbooks/expiration'
26
+ require 'freshbooks/gateway'
27
+ require 'freshbooks/gateway_transaction'
18
28
  require 'freshbooks/invoice'
29
+ require 'freshbooks/contact'
19
30
  require 'freshbooks/item'
31
+ require 'freshbooks/language'
20
32
  require 'freshbooks/line'
21
33
  require 'freshbooks/links'
22
34
  require 'freshbooks/list_proxy'
@@ -25,7 +37,9 @@ require 'freshbooks/project'
25
37
  require 'freshbooks/recurring'
26
38
  require 'freshbooks/response'
27
39
  require 'freshbooks/staff'
40
+ require 'freshbooks/system'
28
41
  require 'freshbooks/task'
42
+ require 'freshbooks/tax'
29
43
  require 'freshbooks/time_entry'
30
44
 
31
45
  require 'net/https'
@@ -60,7 +74,7 @@ require 'logger'
60
74
  #
61
75
  #==============================================================================
62
76
  module FreshBooks
63
- VERSION = '3.0.18' # Gem version
77
+ VERSION = '3.0.25' # Gem version
64
78
  API_VERSION = '2.1' # FreshBooks API version
65
79
  SERVICE_URL = "/api/#{API_VERSION}/xml-in"
66
80
 
@@ -72,7 +86,7 @@ module FreshBooks
72
86
  class ApiAccessNotEnabledError < Error; end;
73
87
  class InvalidAccountUrlError < Error; end;
74
88
  class AccountDeactivatedError < Error; end;
75
-
89
+
76
90
  class ParseError < StandardError
77
91
  attr_accessor :original_error, :xml
78
92
 
@@ -0,0 +1,7 @@
1
+ module FreshBooks
2
+ class Address < FreshBooks::Base
3
+ define_schema do |s|
4
+ s.string :street1, :street2, :city, :state, :country, :code
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module FreshBooks
2
+ class Api < FreshBooks::Base
3
+ define_schema do |s|
4
+ s.fixnum :requests, :request_limit
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module FreshBooks
2
+ class Autobill < FreshBooks::Base
3
+
4
+ define_schema do |s|
5
+ s.string :gateway_name
6
+ s.object :card
7
+ end
8
+ end
9
+ end
@@ -4,6 +4,11 @@ require 'freshbooks/xml_serializer'
4
4
  module FreshBooks
5
5
  class Base
6
6
  include FreshBooks::Schema::Mixin
7
+ attr_reader :error_msg
8
+
9
+ def initialize(args = {})
10
+ args.each_pair {|k, v| send("#{k}=", v)}
11
+ end
7
12
 
8
13
  @@connection = nil
9
14
  def self.connection
@@ -15,11 +20,12 @@ module FreshBooks
15
20
  end
16
21
 
17
22
  def self.new_from_xml(xml_root)
18
- object = self.new
23
+ object = self.new()
19
24
 
20
25
  self.schema_definition.members.each do |member_name, member_options|
21
- node = xml_root.elements[member_name]
26
+ node = xml_root.elements[member_name.dup]
22
27
  next if node.nil?
28
+ next if XmlSerializer.deprecated? node
23
29
 
24
30
  value = FreshBooks::XmlSerializer.to_value(node, member_options[:type])
25
31
  object.send("#{member_name}=", value)
@@ -28,7 +34,10 @@ module FreshBooks
28
34
  return object
29
35
 
30
36
  rescue => e
31
- raise ParseError.new(e, xml_root.to_s)
37
+ error = ParseError.new(e, xml_root.to_s)
38
+ error.set_backtrace(e.backtrace)
39
+ raise error
40
+ # raise ParseError.new(e, xml_root.to_s)
32
41
  end
33
42
 
34
43
  def to_xml(elem_name = nil)
@@ -81,10 +90,15 @@ module FreshBooks
81
90
  end
82
91
  end
83
92
 
84
- def self.define_class_method(symbol, &block)
85
- self.class.send(:define_method, symbol, &block)
93
+ def self.metaclass
94
+ class << self; self; end
86
95
  end
87
96
 
97
+ def self.define_class_method(symbol, &block)
98
+ metaclass.instance_eval do
99
+ define_method symbol, &block
100
+ end
101
+ end
88
102
 
89
103
  def self.actions(*operations)
90
104
  operations.each do |operation|
@@ -145,7 +159,7 @@ module FreshBooks
145
159
 
146
160
  def api_action(action_name)
147
161
  response = FreshBooks::Base.connection.call_api(
148
- "#{self.class.api_class_name}.#{action_name}",
162
+ "#{self.class.api_class_name}.#{action_name}",
149
163
  "#{self.class.api_class_name}_id" => primary_key_value)
150
164
  response.success?
151
165
  end
@@ -154,15 +168,27 @@ module FreshBooks
154
168
  response = FreshBooks::Base.connection.call_api(
155
169
  "#{self.class.api_class_name}.#{action_name}",
156
170
  self.class.api_class_name => self)
157
- self.primary_key_value = response.elements[1].text.to_i if response.success?
158
- response.success?
171
+ if response.success?
172
+ self.primary_key_value = response.elements[1].text.to_i
173
+ @error_msg = nil
174
+ true
175
+ else
176
+ @error_msg = response.error_msg
177
+ false
178
+ end
159
179
  end
160
180
 
161
181
  def api_update_action(action_name)
162
182
  response = FreshBooks::Base.connection.call_api(
163
183
  "#{self.class.api_class_name}.#{action_name}",
164
184
  self.class.api_class_name => self)
165
- response.success?
185
+ if response.success?
186
+ @error_msg = nil
187
+ true
188
+ else
189
+ @error_msg = response.error_msg
190
+ false
191
+ end
166
192
  end
167
193
 
168
194
  end
@@ -0,0 +1,7 @@
1
+ module FreshBooks
2
+ class Budget < FreshBooks::Base
3
+ define_schema do |s|
4
+ s.float :hours
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module FreshBooks
2
+ class Card < FreshBooks::Base
3
+
4
+ define_schema do |s|
5
+ s.string :number, :name
6
+ s.object :expiration
7
+ end
8
+ end
9
+ end
@@ -6,10 +6,13 @@ module FreshBooks
6
6
  s.string :mobile, :fax, :notes, :p_street1, :p_street2, :p_city
7
7
  s.string :p_state, :p_country, :p_code, :s_street1, :s_street2
8
8
  s.string :s_city, :s_state, :s_country, :s_code
9
+ s.string :language, :currency_code
9
10
  s.float :credit, :read_only => true
11
+ s.object :credits, :read_only => true
10
12
  s.date_time :updated, :read_only => true
11
13
  s.fixnum :client_id
12
14
  s.object :links, :read_only => true
15
+ s.array :contacts
13
16
  end
14
17
 
15
18
  actions :list, :get, :create, :update, :delete
@@ -5,37 +5,46 @@ require 'logger'
5
5
  module FreshBooks
6
6
  class Connection
7
7
  attr_reader :account_url, :auth_token, :utc_offset, :request_headers
8
-
8
+
9
9
  @@logger = Logger.new(STDOUT)
10
10
  def logger
11
11
  @@logger
12
12
  end
13
13
 
14
+ def logger=(value)
15
+ @@logger = value
16
+ end
17
+
14
18
  def self.log_level=(level)
15
19
  @@logger.level = level
16
20
  end
17
21
  self.log_level = Logger::WARN
18
-
22
+
19
23
  def initialize(account_url, auth_token, request_headers = {}, options = {})
20
- raise InvalidAccountUrlError.new unless account_url =~ /^[0-9a-zA-Z\-_]+\.freshbooks\.com$/
21
-
24
+ raise InvalidAccountUrlError.new("account_url is expected to be in the form www.example.com without any protocol string or trailing query parameters") unless account_url =~ /^[0-9a-zA-Z\-_]+\.(freshbooks|billingarm)\.com$/
25
+
22
26
  @account_url = account_url
23
27
  @auth_token = auth_token
24
28
  @request_headers = request_headers
25
29
  @utc_offset = options[:utc_offset] || -4
26
30
  @start_session_count = 0
27
31
  end
28
-
32
+
29
33
  def call_api(method, elements = [])
30
34
  request = create_request(method, elements)
31
35
  result = post(request)
32
36
  Response.new(result)
33
37
  end
34
-
38
+
39
+ def direct_post(xml)
40
+ result = post(xml)
41
+ Response.new(result)
42
+ end
43
+
35
44
  def start_session(&block)
36
45
  @connection = obtain_connection if @start_session_count == 0
37
46
  @start_session_count = @start_session_count + 1
38
-
47
+
39
48
  begin
40
49
  block.call(@connection)
41
50
  ensure
@@ -43,45 +52,45 @@ module FreshBooks
43
52
  close if @start_session_count == 0
44
53
  end
45
54
  end
46
-
55
+
47
56
  protected
48
-
57
+
49
58
  def create_request(method, elements = [])
50
59
  doc = REXML::Document.new '<?xml version="1.0" encoding="UTF-8"?>'
51
60
  request = doc.add_element('request')
52
61
  request.attributes['method'] = method
53
-
62
+
54
63
  elements.each do |element|
55
64
  if element.kind_of?(Hash)
56
65
  element = element.to_a
57
66
  end
58
67
  key = element.first
59
68
  value = element.last
60
-
69
+
61
70
  if value.kind_of?(Base)
62
71
  request.add_element(REXML::Document.new(value.to_xml))
63
72
  else
64
73
  request.add_element(REXML::Element.new(key.to_s)).text = value.to_s
65
74
  end
66
75
  end
67
-
76
+
68
77
  doc.to_s
69
78
  end
70
-
79
+
71
80
  def obtain_connection(force = false)
72
81
  return @connection if @connection && !force
73
-
82
+
74
83
  @connection = Net::HTTP.new(@account_url, 443)
75
84
  @connection.use_ssl = true
76
85
  @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
77
86
  @connection.start
78
87
  end
79
-
88
+
80
89
  def reconnect
81
90
  close
82
91
  obtain_connection(true)
83
92
  end
84
-
93
+
85
94
  def close
86
95
  begin
87
96
  @connection.finish if @connection
@@ -90,7 +99,7 @@ module FreshBooks
90
99
  end
91
100
  @connection = nil
92
101
  end
93
-
102
+
94
103
  def post(request_body)
95
104
  result = nil
96
105
  request = Net::HTTP::Post.new(FreshBooks::SERVICE_URL)
@@ -100,19 +109,19 @@ module FreshBooks
100
109
  @request_headers.each_pair do |name, value|
101
110
  request[name.to_s] = value
102
111
  end
103
-
112
+
104
113
  result = post_request(request)
105
-
114
+
106
115
  if logger.debug?
107
116
  logger.debug "Request:"
108
117
  logger.debug request_body
109
118
  logger.debug "Response:"
110
119
  logger.debug result.body
111
120
  end
112
-
121
+
113
122
  check_for_api_error(result)
114
123
  end
115
-
124
+
116
125
  # For connections that take a long time, we catch EOFError's and reconnect seamlessly
117
126
  def post_request(request)
118
127
  response = nil
@@ -122,7 +131,7 @@ module FreshBooks
122
131
  response = connection.request(request)
123
132
  rescue EOFError => e
124
133
  raise e if has_reconnected
125
-
134
+
126
135
  has_reconnected = true
127
136
  connection = reconnect
128
137
  retry
@@ -130,10 +139,10 @@ module FreshBooks
130
139
  end
131
140
  response
132
141
  end
133
-
142
+
134
143
  def check_for_api_error(result)
135
144
  return result.body if result.kind_of?(Net::HTTPSuccess)
136
-
145
+
137
146
  case result
138
147
  when Net::HTTPRedirection
139
148
  if result["location"] =~ /loginSearch/
@@ -146,7 +155,7 @@ module FreshBooks
146
155
  when Net::HTTPBadRequest
147
156
  raise ApiAccessNotEnabledError.new("API not enabled.")
148
157
  end
149
-
158
+
150
159
  raise InternalError.new("Invalid HTTP code: #{result.class}")
151
160
  end
152
161
  end