freshbooks.rb 3.0.18 → 3.0.25

Sign up to get free protection for your applications and to get access to all the features.
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