octobat 0.0.12 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +20 -10
- data/History.txt +33 -0
- data/VERSION +1 -1
- data/lib/octobat.rb +95 -19
- data/lib/octobat/api_operations/delete.rb +1 -0
- data/lib/octobat/api_operations/list.rb +16 -9
- data/lib/octobat/api_operations/update.rb +7 -4
- data/lib/octobat/api_resource.rb +5 -2
- data/lib/octobat/{payment_mode.rb → checkout.rb} +1 -1
- data/lib/octobat/coupon.rb +32 -0
- data/lib/octobat/credit_note.rb +35 -0
- data/lib/octobat/credit_note_numbering_sequence.rb +13 -0
- data/lib/octobat/customer.rb +10 -2
- data/lib/octobat/document.rb +17 -0
- data/lib/octobat/document_email_template.rb +19 -0
- data/lib/octobat/document_language.rb +19 -0
- data/lib/octobat/document_template.rb +33 -0
- data/lib/octobat/emails_setting.rb +19 -0
- data/lib/octobat/errors/api_connection_error.rb +1 -1
- data/lib/octobat/errors/api_error.rb +1 -1
- data/lib/octobat/errors/authentication_error.rb +1 -1
- data/lib/octobat/errors/invalid_request_error.rb +2 -4
- data/lib/octobat/errors/octobat_error.rb +31 -5
- data/lib/octobat/errors/octobat_lib_error.rb +20 -0
- data/lib/octobat/exports_setting.rb +19 -0
- data/lib/octobat/invoice.rb +61 -12
- data/lib/octobat/invoice_numbering_sequence.rb +18 -0
- data/lib/octobat/item.rb +64 -0
- data/lib/octobat/list_object.rb +12 -4
- data/lib/octobat/octobat_object.rb +13 -4
- data/lib/octobat/payment_recipient.rb +7 -0
- data/lib/octobat/{numbering_sequence.rb → payment_recipient_reference.rb} +1 -1
- data/lib/octobat/payment_source.rb +16 -0
- data/lib/octobat/{invoice_item.rb → tax_evidence.rb} +1 -1
- data/lib/octobat/tax_evidence_request.rb +5 -0
- data/lib/octobat/tax_region_setting.rb +27 -0
- data/lib/octobat/transaction.rb +11 -0
- data/lib/octobat/util.rb +28 -10
- data/lib/octobat/version.rb +1 -1
- data/octobat.gemspec +3 -3
- metadata +33 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 041400ae286b51b2e0aaabcc19780b1b7e422640
|
4
|
+
data.tar.gz: 3db20d4d584da54c530ece341ce3451b11ef5bcf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cf9162c36bfa08b72dc53e069053a9d88513e438ffec9f49d42855a0b7259e756afdd808682e0c9c32844302777f6a9bfe54bd99f9237049b069d356143ceeb
|
7
|
+
data.tar.gz: 3b4792604be22a61edaa987e98da891801ca187ec8ecc241da9d15bb0dfc09df734525b972d6cc253a2ed2c1df726d1d5d153d74012155b59aee55aacce8dced
|
data/.gitignore
CHANGED
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('
|
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
|
5
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
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/
|
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://
|
44
|
-
#@api_base = 'http://api.octobat.local:
|
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
|
-
|
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[:
|
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
|
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
|
@@ -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
|
-
|
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,
|
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
|
-
|
59
|
+
protected
|
57
60
|
def save_url
|
58
61
|
if self[:id] == nil && self.class.respond_to?(:create)
|
59
62
|
self.class.url
|
data/lib/octobat/api_resource.rb
CHANGED
@@ -23,8 +23,11 @@ module Octobat
|
|
23
23
|
refresh_from(response, api_key)
|
24
24
|
end
|
25
25
|
|
26
|
-
def self.retrieve(id,
|
27
|
-
|
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
|
@@ -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
|