octobat 0.0.12 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/Gemfile +2 -1
  4. data/Gemfile.lock +20 -10
  5. data/History.txt +33 -0
  6. data/VERSION +1 -1
  7. data/lib/octobat.rb +95 -19
  8. data/lib/octobat/api_operations/delete.rb +1 -0
  9. data/lib/octobat/api_operations/list.rb +16 -9
  10. data/lib/octobat/api_operations/update.rb +7 -4
  11. data/lib/octobat/api_resource.rb +5 -2
  12. data/lib/octobat/{payment_mode.rb → checkout.rb} +1 -1
  13. data/lib/octobat/coupon.rb +32 -0
  14. data/lib/octobat/credit_note.rb +35 -0
  15. data/lib/octobat/credit_note_numbering_sequence.rb +13 -0
  16. data/lib/octobat/customer.rb +10 -2
  17. data/lib/octobat/document.rb +17 -0
  18. data/lib/octobat/document_email_template.rb +19 -0
  19. data/lib/octobat/document_language.rb +19 -0
  20. data/lib/octobat/document_template.rb +33 -0
  21. data/lib/octobat/emails_setting.rb +19 -0
  22. data/lib/octobat/errors/api_connection_error.rb +1 -1
  23. data/lib/octobat/errors/api_error.rb +1 -1
  24. data/lib/octobat/errors/authentication_error.rb +1 -1
  25. data/lib/octobat/errors/invalid_request_error.rb +2 -4
  26. data/lib/octobat/errors/octobat_error.rb +31 -5
  27. data/lib/octobat/errors/octobat_lib_error.rb +20 -0
  28. data/lib/octobat/exports_setting.rb +19 -0
  29. data/lib/octobat/invoice.rb +61 -12
  30. data/lib/octobat/invoice_numbering_sequence.rb +18 -0
  31. data/lib/octobat/item.rb +64 -0
  32. data/lib/octobat/list_object.rb +12 -4
  33. data/lib/octobat/octobat_object.rb +13 -4
  34. data/lib/octobat/payment_recipient.rb +7 -0
  35. data/lib/octobat/{numbering_sequence.rb → payment_recipient_reference.rb} +1 -1
  36. data/lib/octobat/payment_source.rb +16 -0
  37. data/lib/octobat/{invoice_item.rb → tax_evidence.rb} +1 -1
  38. data/lib/octobat/tax_evidence_request.rb +5 -0
  39. data/lib/octobat/tax_region_setting.rb +27 -0
  40. data/lib/octobat/transaction.rb +11 -0
  41. data/lib/octobat/util.rb +28 -10
  42. data/lib/octobat/version.rb +1 -1
  43. data/octobat.gemspec +3 -3
  44. metadata +33 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c003fafc9cbcbe437c10d026bb43bf56a0d77c19
4
- data.tar.gz: df2387863f7ca20cc57d24cefd884134df02b877
3
+ metadata.gz: 041400ae286b51b2e0aaabcc19780b1b7e422640
4
+ data.tar.gz: 3db20d4d584da54c530ece341ce3451b11ef5bcf
5
5
  SHA512:
6
- metadata.gz: 417b0c965a6548d82da50b820d0809bf239f1c41a5be8c2f7171cf64f2d44c1e2b9d820c49bbe07c3b2d47641e9cc9ace6599572e952961665c4a6ae014ee559
7
- data.tar.gz: 7118109f8be2cf85cbff460a081bb0ac4cb36606a7231779b2715337ad20dcba83fe65e6ad96abe3f38229ce0936f8b5ad886072f7cf357572564bcec141791a
6
+ metadata.gz: 4cf9162c36bfa08b72dc53e069053a9d88513e438ffec9f49d42855a0b7259e756afdd808682e0c9c32844302777f6a9bfe54bd99f9237049b069d356143ceeb
7
+ data.tar.gz: 3b4792604be22a61edaa987e98da891801ca187ec8ecc241da9d15bb0dfc09df734525b972d6cc253a2ed2c1df726d1d5d153d74012155b59aee55aacce8dced
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  /octobat-*.gem
2
2
  .rvmrc
3
+ .ruby-version
data/Gemfile CHANGED
@@ -1,8 +1,9 @@
1
1
  source "https://rubygems.org"
2
2
  gemspec
3
3
 
4
- if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('1.9.3')
4
+ if Gem::Version.new(RUBY_VERSION.dup) < Gem::Version.new('2.0.0')
5
5
  gem 'i18n', '< 0.7'
6
6
  gem 'rest-client', '~> 1.6.8'
7
+ gem 'mime-types', '< 3.0'
7
8
  gem 'activesupport', '~> 3.2'
8
9
  end
data/Gemfile.lock CHANGED
@@ -1,23 +1,33 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- octobat (0.0.12)
5
- json (~> 1.8.1)
6
- mime-types (>= 1.25, < 3.0)
7
- rest-client (~> 1.4)
4
+ octobat (2.0.0)
5
+ rest-client (>= 1.4, < 4.0)
8
6
 
9
7
  GEM
10
8
  remote: https://rubygems.org/
11
9
  specs:
12
- json (1.8.3)
13
- mime-types (2.99.1)
14
- netrc (0.10.2)
15
- rest-client (1.7.2)
16
- mime-types (>= 1.16, < 3.0)
17
- netrc (~> 0.7)
10
+ domain_name (0.5.20161129)
11
+ unf (>= 0.0.5, < 1.0.0)
12
+ http-cookie (1.0.3)
13
+ domain_name (~> 0.5)
14
+ mime-types (3.1)
15
+ mime-types-data (~> 3.2015)
16
+ mime-types-data (3.2016.0521)
17
+ netrc (0.11.0)
18
+ rest-client (2.0.0)
19
+ http-cookie (>= 1.0.2, < 2.0)
20
+ mime-types (>= 1.16, < 4.0)
21
+ netrc (~> 0.8)
22
+ unf (0.1.4)
23
+ unf_ext
24
+ unf_ext (0.0.7.2)
18
25
 
19
26
  PLATFORMS
20
27
  ruby
21
28
 
22
29
  DEPENDENCIES
23
30
  octobat!
31
+
32
+ BUNDLED WITH
33
+ 1.13.7
data/History.txt CHANGED
@@ -1,3 +1,36 @@
1
+ === 2.0.0 2017-01-23
2
+ * 1 major enhancement:
3
+ * Bump to Octobat v2 API endpoint
4
+
5
+ === 0.0.12 2016-04-13
6
+ * 1 minor enhancement:
7
+ * Add pagination helpers
8
+
9
+ === 0.0.9 2016-04-13
10
+ * 1 major enhancement
11
+ * Can initialize Octobat Objects without persistence, and save them later with save method
12
+
13
+ === 0.0.8 2016-02-04
14
+ * 3 minor enhancements:
15
+ * Add the draft invoices feature, refactoring invoices#create to create only draft invoices
16
+ * Add invoices#confirm to confirm draft invoices and insert them into the numbering sequence
17
+ * Add invoice_items#create for creating invoice items
18
+
19
+ === 0.0.6 2015-10-13
20
+ * 2 minor enhancements:
21
+ * Add invoices#update for non already-sent invoices
22
+ * Add invoices#send to make the email sending programmable
23
+
24
+ === 0.0.5 2015-07-21
25
+ * 1 minor enhancement:
26
+ * Add invoices#all customer filter and Customer.invoices method
27
+
28
+ === 0.0.1 2015-02-06
29
+
30
+ * 1 major enhancement:
31
+ * Initial release
32
+
33
+
1
34
  === 0.0.12 2016-04-13
2
35
  * 1 minor enhancement:
3
36
  * Add pagination helpers
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.12
1
+ 2.0.0
data/lib/octobat.rb CHANGED
@@ -23,16 +23,32 @@ require 'octobat/api_resource'
23
23
  require 'octobat/singleton_api_resource'
24
24
  require 'octobat/list_object'
25
25
 
26
- require 'octobat/numbering_sequence'
27
- require 'octobat/payment_mode'
28
- require 'octobat/payment'
29
- require 'octobat/credit_note_numbering_sequence'
30
26
  require 'octobat/customer'
27
+ require 'octobat/transaction'
28
+ require 'octobat/item'
29
+ require 'octobat/document'
31
30
  require 'octobat/invoice'
32
- require 'octobat/invoice_item'
31
+ require 'octobat/credit_note'
32
+ require 'octobat/payment_recipient'
33
+ require 'octobat/payment_recipient_reference'
34
+ require 'octobat/payment_source'
35
+ require 'octobat/invoice_numbering_sequence'
36
+ require 'octobat/credit_note_numbering_sequence'
37
+ require 'octobat/document_template'
38
+ require 'octobat/document_language'
39
+ require 'octobat/checkout'
40
+ require 'octobat/coupon'
41
+ require 'octobat/tax_region_setting'
42
+ require 'octobat/tax_evidence'
43
+ require 'octobat/tax_evidence_request'
44
+ require 'octobat/document_email_template'
45
+ require 'octobat/exports_setting'
46
+ require 'octobat/emails_setting'
47
+
33
48
 
34
49
  # Errors
35
50
  require 'octobat/errors/octobat_error'
51
+ require 'octobat/errors/octobat_lib_error'
36
52
  require 'octobat/errors/api_error'
37
53
  require 'octobat/errors/api_connection_error'
38
54
  require 'octobat/errors/invalid_request_error'
@@ -40,8 +56,13 @@ require 'octobat/errors/authentication_error'
40
56
 
41
57
  module Octobat
42
58
  #DEFAULT_CA_BUNDLE_PATH = File.dirname(__FILE__) + '/data/ca-certificates.crt'
43
- @api_base = 'https://api.octobat.com'
44
- #@api_base = 'http://api.octobat.local:3040'
59
+ @api_base = 'https://apiv2.octobat.com'
60
+ #@api_base = 'http://api.octobat.local:3052'
61
+
62
+ @max_network_retries = 0
63
+ @max_network_retry_delay = 2
64
+ @initial_network_retry_delay = 0.5
65
+
45
66
 
46
67
  #@ssl_bundle_path = DEFAULT_CA_BUNDLE_PATH
47
68
  #@verify_ssl_certs = true
@@ -50,6 +71,7 @@ module Octobat
50
71
 
51
72
  class << self
52
73
  attr_accessor :api_key, :api_base, :verify_ssl_certs, :api_version
74
+ attr_reader :max_network_retry_delay, :initial_network_retry_delay
53
75
  end
54
76
 
55
77
  def self.api_url(url='', api_base_url=nil)
@@ -98,19 +120,33 @@ module Octobat
98
120
  end
99
121
  end
100
122
 
101
- request_opts.update(:headers => request_headers(api_key).update(headers),
123
+ request_opts.update(:headers => request_headers(api_key, method).update(headers),
102
124
  :method => method, :open_timeout => 30,
103
125
  :payload => payload, :url => url, :timeout => 80)
104
126
 
127
+ response = execute_request_with_rescues(request_opts, api_base_url)
128
+ [parse(response), api_key]
129
+ end
130
+
131
+ def self.max_network_retries
132
+ @max_network_retries
133
+ end
134
+
135
+ def self.max_network_retries=(val)
136
+ @max_network_retries = val.to_i
137
+ end
138
+
139
+
140
+ def self.execute_request_with_rescues(request_opts, api_base_url, retry_count = 0)
105
141
  begin
106
142
  response = execute_request(request_opts)
107
143
  rescue SocketError => e
108
- handle_restclient_error(e, api_base_url)
144
+ response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
109
145
  rescue NoMethodError => e
110
146
  # Work around RestClient bug
111
147
  if e.message =~ /\WRequestFailed\W/
112
148
  e = APIConnectionError.new('Unexpected HTTP response code')
113
- handle_restclient_error(e, api_base_url)
149
+ response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
114
150
  else
115
151
  raise
116
152
  end
@@ -118,13 +154,13 @@ module Octobat
118
154
  if rcode = e.http_code and rbody = e.http_body
119
155
  handle_api_error(rcode, rbody)
120
156
  else
121
- handle_restclient_error(e, api_base_url)
157
+ response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
122
158
  end
123
159
  rescue RestClient::Exception, Errno::ECONNREFUSED => e
124
- handle_restclient_error(e, api_base_url)
160
+ response = handle_restclient_error(e, request_opts, retry_count, api_base_url)
125
161
  end
126
162
 
127
- [parse(response), api_key]
163
+ response
128
164
  end
129
165
 
130
166
  private
@@ -174,13 +210,17 @@ module Octobat
174
210
  map { |k,v| "#{k}=#{Util.url_encode(v)}" }.join('&')
175
211
  end
176
212
 
177
- def self.request_headers(api_key)
213
+ def self.request_headers(api_key, method)
178
214
  headers = {
179
215
  :user_agent => "Octobat/v1 RubyBindings/#{Octobat::VERSION}",
180
216
  :authorization => 'Basic ' + Base64.encode64( "#{api_key}:" ).chomp,
181
217
  :content_type => 'application/x-www-form-urlencoded'
182
218
  }
183
219
 
220
+ if [:post, :delete, :patch].include?(method) && self.max_network_retries > 0
221
+ headers[:idempotency_key] ||= SecureRandom.uuid
222
+ end
223
+
184
224
  headers[:octobat_version] = api_version if api_version
185
225
 
186
226
  begin
@@ -216,14 +256,14 @@ module Octobat
216
256
  begin
217
257
  error_obj = JSON.parse(rbody)
218
258
  error_obj = Util.symbolize_names(error_obj)
219
- error = error_obj[:error] or raise OctobatError.new # escape from parsing
259
+ error = error_obj[:errors] or raise OctobatError.new # escape from parsing
220
260
 
221
261
  rescue JSON::ParserError, OctobatError
222
262
  raise general_api_error(rcode, rbody)
223
263
  end
224
264
 
225
265
  case rcode
226
- when 400, 404
266
+ when 400, 402, 404, 422
227
267
  raise invalid_request_error error, rcode, rbody, error_obj
228
268
  when 401
229
269
  raise authentication_error error, rcode, rbody, error_obj
@@ -234,8 +274,7 @@ module Octobat
234
274
  end
235
275
 
236
276
  def self.invalid_request_error(error, rcode, rbody, error_obj)
237
- InvalidRequestError.new(error[:message], error[:param], rcode,
238
- rbody, error_obj)
277
+ InvalidRequestError.new(error, rcode, rbody, error_obj)
239
278
  end
240
279
 
241
280
  def self.authentication_error(error, rcode, rbody, error_obj)
@@ -251,7 +290,16 @@ module Octobat
251
290
  APIError.new(error[:message], rcode, rbody, error_obj)
252
291
  end
253
292
 
254
- def self.handle_restclient_error(e, api_base_url=nil)
293
+ def self.handle_restclient_error(e, request_opts, retry_count, api_base_url=nil)
294
+
295
+ if should_retry?(e, retry_count)
296
+ retry_count = retry_count + 1
297
+ sleep sleep_time(retry_count)
298
+ response = execute_request_with_rescues(request_opts, api_base_url, retry_count)
299
+ return response
300
+ end
301
+
302
+
255
303
  api_base_url = @api_base unless api_base_url
256
304
  connection_message = "Please check your internet connection and try again. " \
257
305
  "If this problem persists, you should check Octobat's service status at " \
@@ -282,6 +330,34 @@ module Octobat
282
330
 
283
331
  end
284
332
 
333
+ if retry_count > 0
334
+ message += " Request was retried #{retry_count} times."
335
+ end
336
+
285
337
  raise APIConnectionError.new(message + "\n\n(Network error: #{e.message})")
286
338
  end
339
+
340
+
341
+ def self.should_retry?(e, retry_count)
342
+ puts "Retry count: #{retry_count}"
343
+ return false if retry_count >= self.max_network_retries
344
+ #return false if e.is_a?(RestClient::SSLCertificateNotVerified)
345
+ return true
346
+ end
347
+
348
+ def self.sleep_time(retry_count)
349
+ # This method was adapted from https://github.com/ooyala/retries/blob/master/lib/retries.rb
350
+
351
+ # The sleep time is an exponentially-increasing function of base_sleep_seconds. But, it never exceeds
352
+ # max_sleep_seconds.
353
+ sleep_seconds = [initial_network_retry_delay * (2 ** (retry_count - 1)), max_network_retry_delay].min
354
+ # Randomize to a random value in the range sleep_seconds/2 .. sleep_seconds
355
+
356
+ sleep_seconds = sleep_seconds * (0.5 * (1 + rand()))
357
+ # But never sleep less than base_sleep_seconds
358
+ sleep_seconds = [initial_network_retry_delay, sleep_seconds].max
359
+
360
+ sleep_seconds
361
+ end
362
+
287
363
  end
@@ -3,6 +3,7 @@ module Octobat
3
3
  module Delete
4
4
  def delete(params = {}, opts={})
5
5
  api_key, headers = Util.parse_opts(opts)
6
+
6
7
  response, api_key = Octobat.request(:delete, url, api_key || @api_key, params, headers)
7
8
  refresh_from(response, api_key)
8
9
  end
@@ -2,23 +2,30 @@ module Octobat
2
2
  module APIOperations
3
3
  module List
4
4
  def list(filters={}, opts={})
5
+ set_parent_resource(filters)
5
6
  api_key, headers = Util.parse_opts(opts)
6
- api_key ||= @api_key
7
-
8
- #opts = Util.normalize_opts(opts)
9
- #opts = @opts.merge(opts) if @opts
10
7
 
11
- response, api_key = Octobat.request(:get, url, api_key, filters, headers)
8
+ api_key ||= @api_key
9
+
10
+ f = filters.select{|request_filter| !@parent_resource.has_key?(request_filter)}
11
+
12
+ response, api_key = Octobat.request(:get, url, api_key, f, headers)
12
13
  obj = ListObject.construct_from(response, api_key)
13
-
14
+
14
15
  obj.filters = filters.dup
15
16
  obj.cursors[:ending_before] = obj.filters.delete(:ending_before)
16
17
  obj.cursors[:starting_after] = obj.filters.delete(:starting_after)
17
-
18
-
18
+
19
+ obj.filters.delete(:expand)
20
+ obj.parent_resource = @parent_resource
21
+
19
22
  obj
20
23
  end
21
-
24
+
25
+ def set_parent_resource(filters)
26
+ @parent_resource = {}
27
+ end
28
+
22
29
  alias :all :list
23
30
  end
24
31
  end
@@ -1,8 +1,11 @@
1
1
  module Octobat
2
2
  module APIOperations
3
3
  module Update
4
- def save(opts={})
4
+ def save(opts={}, headers = {})
5
5
  values = serialize_params(self).merge(opts)
6
+
7
+ api_key, headers = Util.parse_opts(headers)
8
+ api_key ||= @api_key
6
9
 
7
10
  if @values[:metadata]
8
11
  values[:metadata] = serialize_metadata
@@ -11,7 +14,7 @@ module Octobat
11
14
  if values.length > 0
12
15
  values.delete(:id)
13
16
 
14
- response, api_key = Octobat.request(save_method, save_url, @api_key, values)
17
+ response, api_key = Octobat.request(save_method, save_url, api_key, values)
15
18
  refresh_from(response, api_key)
16
19
  end
17
20
  self
@@ -37,7 +40,7 @@ module Octobat
37
40
  def serialize_params(obj)
38
41
  case obj
39
42
  when nil
40
- ''
43
+ self[:id].nil? ? nil : ''
41
44
  when OctobatObject
42
45
  unsaved_keys = obj.instance_variable_get(:@unsaved_values)
43
46
  obj_values = obj.instance_variable_get(:@values)
@@ -53,7 +56,7 @@ module Octobat
53
56
  end
54
57
  end
55
58
 
56
- private
59
+ protected
57
60
  def save_url
58
61
  if self[:id] == nil && self.class.respond_to?(:create)
59
62
  self.class.url
@@ -23,8 +23,11 @@ module Octobat
23
23
  refresh_from(response, api_key)
24
24
  end
25
25
 
26
- def self.retrieve(id, api_key=nil)
27
- instance = self.new(id, api_key)
26
+ def self.retrieve(id, opts={})
27
+ api_key, headers = Util.parse_opts(opts)
28
+ opts[:api_key] ||= @api_key
29
+
30
+ instance = self.new(id, opts)
28
31
  instance.refresh
29
32
  instance
30
33
  end
@@ -1,5 +1,5 @@
1
1
  module Octobat
2
- class PaymentMode < APIResource
2
+ class Checkout < APIResource
3
3
  extend Octobat::APIOperations::List
4
4
  include Octobat::APIOperations::Create
5
5
  include Octobat::APIOperations::Update
@@ -0,0 +1,32 @@
1
+ module Octobat
2
+ class Coupon < APIResource
3
+ extend Octobat::APIOperations::List
4
+ include Octobat::APIOperations::Create
5
+ include Octobat::APIOperations::Update
6
+
7
+ def activate
8
+ response, api_key = Octobat.request(:patch, activate_url, @api_key)
9
+ refresh_from(response, api_key)
10
+ end
11
+
12
+ def unactivate
13
+ response, api_key = Octobat.request(:patch, unactivate_url, @api_key)
14
+ refresh_from(response, api_key)
15
+ end
16
+
17
+ def delete
18
+ response, api_key = Octobat.request(:delete, url, @api_key)
19
+ refresh_from(response, api_key)
20
+ end
21
+
22
+ private
23
+
24
+ def activate_url
25
+ url + '/activate'
26
+ end
27
+
28
+ def unactivate_url
29
+ url + '/unactivate'
30
+ end
31
+ end
32
+ end