octobat 0.0.12 → 2.0.0

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 (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