bill_forward 1.2014.296 → 1.2015.183

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +29 -27
  3. data/.idea/compiler.xml +23 -23
  4. data/.idea/copyright/profiles_settings.xml +2 -2
  5. data/.idea/encodings.xml +5 -5
  6. data/.idea/inspectionProfiles/Project_Default.xml +10 -10
  7. data/.idea/inspectionProfiles/profiles_settings.xml +6 -6
  8. data/.idea/misc.xml +23 -23
  9. data/.idea/modules.xml +9 -9
  10. data/.idea/scopes/scope_settings.xml +4 -4
  11. data/.idea/vcs.xml +7 -7
  12. data/.rspec +1 -1
  13. data/Gemfile +13 -9
  14. data/LICENSE.md +22 -22
  15. data/README.md +285 -227
  16. data/Rakefile +73 -73
  17. data/bill_forward.gemspec +28 -29
  18. data/bill_forward.iml +28 -28
  19. data/lib/bill_forward/billing_entity.rb +295 -262
  20. data/lib/bill_forward/client.rb +350 -355
  21. data/lib/bill_forward/entities/account.rb +18 -18
  22. data/lib/bill_forward/entities/amendments/issue_invoice_amendment.rb +10 -0
  23. data/lib/bill_forward/entities/amendments/product_rate_plan_migration_amendment.rb +18 -0
  24. data/lib/bill_forward/entities/api_configuration.rb +12 -0
  25. data/lib/bill_forward/entities/invoice.rb +29 -3
  26. data/lib/bill_forward/entities/invoice_parts/taxation_link.rb +5 -5
  27. data/lib/bill_forward/entities/organisation.rb +36 -36
  28. data/lib/bill_forward/entities/payment_method.rb +4 -4
  29. data/lib/bill_forward/entities/payment_method_subscription_link.rb +5 -5
  30. data/lib/bill_forward/entities/pricing_component.rb +21 -21
  31. data/lib/bill_forward/entities/pricing_component_tier.rb +5 -5
  32. data/lib/bill_forward/entities/pricing_component_value.rb +5 -5
  33. data/lib/bill_forward/entities/pricing_component_value_migration_amendment_mapping.rb +31 -0
  34. data/lib/bill_forward/entities/product.rb +5 -5
  35. data/lib/bill_forward/entities/product_rate_plan.rb +43 -19
  36. data/lib/bill_forward/entities/profile.rb +14 -14
  37. data/lib/bill_forward/entities/refund.rb +5 -0
  38. data/lib/bill_forward/entities/role.rb +3 -3
  39. data/lib/bill_forward/entities/stripe_ach_token.rb +9 -0
  40. data/lib/bill_forward/entities/subscription.rb +83 -53
  41. data/lib/bill_forward/entities/subscription_charge.rb +14 -0
  42. data/lib/bill_forward/insertable_entity.rb +21 -31
  43. data/lib/bill_forward/mutable_entity.rb +26 -46
  44. data/lib/bill_forward/resource_path.rb +10 -10
  45. data/lib/bill_forward/type_check.rb +20 -20
  46. data/lib/bill_forward/version.rb +4 -4
  47. data/lib/bill_forward.rb +17 -17
  48. data/scratch/Gemfile +9 -0
  49. data/scratch/scratch.example.rb +8 -0
  50. data/spec/component/account_spec.rb +199 -199
  51. data/spec/component/billing_entity_spec.rb +152 -152
  52. data/spec/functional/account_spec.rb +24 -24
  53. data/spec/functional/bad_citizen/account_spec.rb +102 -102
  54. data/spec/functional/bad_citizen/credit_note_spec.rb +14 -13
  55. data/spec/functional/bad_citizen/payment_method_spec.rb +1 -1
  56. data/spec/functional/bad_citizen/product_rate_plan_migration_amendment_spec.rb +379 -0
  57. data/spec/functional/bad_citizen/product_rate_plan_spec.rb +103 -47
  58. data/spec/functional/bad_citizen/product_spec.rb +16 -9
  59. data/spec/functional/bad_citizen/situational/authorize_net_token_spec.rb +4 -0
  60. data/spec/functional/bad_citizen/situational/malordered_entity_spec.rb +1 -1
  61. data/spec/functional/bad_citizen/situational/payment_method_spec.rb +6 -1
  62. data/spec/functional/bad_citizen/situational/subscription_chargeable_spec.rb +96 -65
  63. data/spec/functional/bad_citizen/subscription_spec.rb +99 -60
  64. data/spec/functional/bad_citizen/subscription_with_credit_spec.rb +121 -91
  65. data/spec/functional/bad_citizen/unit_of_measure_spec.rb +14 -7
  66. data/spec/functional/client_spec.rb +23 -23
  67. data/spec/functional/organisation_spec.rb +27 -27
  68. data/spec/setup_test_constants.rb +72 -72
  69. data/spec/spec_helper.rb +10 -10
  70. data/spec/syntax/account_spec.rb +23 -23
  71. data/spec/syntax/billing_entity_spec.rb +92 -92
  72. data/tools/RSpec hardcoded.sublime-build +16 -0
  73. data/tools/RSpec.sublime-build +13 -0
  74. data/tools/Ruby legacy.sublime-build +7 -0
  75. data/tools/local_bundle_build.sh +8 -0
  76. data/tools/local_bundle_install.sh +9 -0
  77. metadata +30 -85
@@ -1,355 +1,350 @@
1
- module BillForward
2
- class ClientException < Exception
3
- attr_accessor :response
4
-
5
- def initialize(message, response=nil)
6
- super(message)
7
-
8
- begin
9
- if response.nil?
10
- self.response = nil
11
- else
12
- self.response = JSON.parse response
13
- end
14
- rescue => e
15
- self.response = nil
16
- end
17
- end
18
- end
19
-
20
- class ClientInstantiationException < Exception
21
- end
22
-
23
- class ApiError < Exception
24
- attr_reader :json
25
- attr_reader :raw
26
-
27
- def initialize(json, raw)
28
- @json = json
29
- @raw = raw
30
- end
31
- end
32
-
33
- class ApiAuthorizationError < ApiError
34
- end
35
-
36
- class ApiTokenException < ClientException
37
-
38
- end
39
-
40
- class Client
41
- attr_accessor :host
42
- attr_accessor :use_logging
43
- attr_accessor :api_token
44
-
45
- # provide access to self statics
46
- class << self
47
- # default client is a singleton client
48
- attr_reader :default_client
49
- def default_client=(default_client)
50
- if (default_client == nil)
51
- # meaningless, but required for resetting this class after a test run
52
- @default_client = nil
53
- return
54
- end
55
-
56
- TypeCheck.verifyObj(Client, default_client, 'default_client')
57
- @default_client = default_client
58
- end
59
- def default_client()
60
- raise ClientInstantiationException.new("Failed to get default BillForward API Client; " +
61
- "'default_client' is nil. Please set a 'default_client' first.") if
62
- @default_client.nil?
63
- @default_client
64
- end
65
- end
66
-
67
-
68
- # Constructs a client, and sets it to be used as the default client.
69
- # @param options={} [Hash] Options with which to construct client
70
- #
71
- # @return [Client] The constructed client
72
- def self.make_default_client(options)
73
- constructedClient = self.new(options)
74
- self.default_client = constructedClient
75
- end
76
-
77
- def initialize(options={})
78
- TypeCheck.verifyObj(Hash, options, 'options')
79
- @use_logging = options[:use_logging]
80
-
81
- if options[:host]
82
- @host = options[:host]
83
- else
84
- raise ClientInstantiationException.new "Failed to initialize BillForward API Client\n" +
85
- "Required parameters: :host, and either [:api_token] or all of [:client_id, :client_secret, :username, :password].\n" +
86
- "Supplied Parameters: #{options}"
87
- end
88
-
89
- if options[:use_proxy]
90
- @use_proxy = options[:use_proxy]
91
- @proxy_url = options[:proxy_url]
92
- end
93
-
94
- if options[:api_token]
95
- @api_token = options[:api_token]
96
- else
97
- @api_token = nil
98
- if options[:client_id] and options[:client_secret] and options[:username] and options[:password]
99
- @client_id = options[:client_id]
100
- @client_secret = options[:client_secret]
101
- @username = options[:username]
102
- @password = options[:password]
103
- else
104
- raise ClientException.new "Failed to initialize BillForward API Client\n"+
105
- "Required parameters: :host and :use_logging, and either [:api_token] or all of [:client_id, :client_secret, :username, :password].\n" +
106
- "Supplied Parameters: #{options}"
107
- end
108
-
109
- end
110
-
111
- @authorization = nil
112
- end
113
-
114
-
115
-
116
- # def get_results(url)
117
- # response = get(url)
118
-
119
- # return [] if response.nil? or response["results"].length == 0
120
-
121
- # response["results"]
122
- # end
123
-
124
- def get_first(url, params={})
125
- response = get(url, params)
126
-
127
- raise IndexError.new("Cannot get first; request returned empty list of results.") if response.nil? or response["results"].length == 0
128
-
129
- response["results"][0]
130
- end
131
-
132
- def retire_first(url, params={})
133
- response = retire(url, params)
134
-
135
- raise IndexError.new("Cannot get first; request returned empty list of results.") if response.nil? or response["results"].length == 0
136
-
137
- response["results"][0]
138
- end
139
-
140
- def put_first(url, data, params={})
141
- response = put(url, data, params)
142
-
143
- raise IndexError.new("Cannot get first; request returned empty list of results.") if response.nil? or response["results"].length == 0
144
-
145
- response["results"][0]
146
- end
147
-
148
- def post_first(url, data, params={})
149
- response = post(url, data, params)
150
-
151
- raise IndexError.new("Cannot get first; request returned empty list of results.") if response.nil? or response["results"].length == 0
152
-
153
- response["results"][0]
154
- end
155
-
156
- def execute_request(method, url, token, payload=nil)
157
- # Enable Fiddler:
158
- if @use_proxy
159
- RestClient.proxy = @proxy_url
160
- end
161
-
162
- # content_type seems to be broken on generic execute.
163
- # darn.
164
- # RestClient::Request.execute(options)
165
- options = {
166
- :Authorization => "Bearer #{token}",
167
- :accept => 'application/json'
168
- }
169
- if (method == 'post' || method == 'put')
170
- options.update(:content_type => 'application/json'
171
- )
172
- end
173
-
174
- if (method == 'post')
175
- RestClient.post(url, payload, options)
176
- elsif (method == 'put')
177
- RestClient.put(url, payload, options)
178
- elsif (method == 'get')
179
- RestClient.get(url, options)
180
- elsif (method == 'delete')
181
- RestClient.delete(url, options)
182
- end
183
- end
184
-
185
- def get(url, params={})
186
- TypeCheck.verifyObj(Hash, params, 'params')
187
- request('get', url, params, nil)
188
- end
189
-
190
- def retire(url, params={})
191
- TypeCheck.verifyObj(Hash, params, 'params')
192
- request('delete', url, params, nil)
193
- end
194
-
195
- def post(url, data, params={})
196
- TypeCheck.verifyObj(String, data, 'data')
197
- TypeCheck.verifyObj(Hash, params, 'params')
198
- request('post', url, params, data)
199
- end
200
-
201
- def put(url, data, params={})
202
- TypeCheck.verifyObj(String, data, 'data')
203
- TypeCheck.verifyObj(Hash, params, 'params')
204
- request('put', url, params, data)
205
- end
206
-
207
- private
208
- def uri_encode(params = {})
209
- TypeCheck.verifyObj(Hash, params, 'params')
210
-
211
- encoded_params = Array.new
212
-
213
- params.each do |key, value|
214
- encoded_key = ERB::Util.url_encode key
215
- encoded_value = ERB::Util.url_encode value
216
- encoded_params.push("#{encoded_key}=#{encoded_value}")
217
- end
218
- query = encoded_params.join '&'
219
-
220
- end
221
-
222
- def request(method, url, params={}, payload=nil)
223
- full_url = "#{@host}#{url}"
224
-
225
- # Make params into query parameters
226
- full_url += "?#{uri_encode(params)}" if params && params.any?
227
- token = get_token
228
-
229
- log "#{method} #{url}"
230
- log "token: #{token}"
231
-
232
- begin
233
- response = execute_request(method, full_url, token, payload)
234
-
235
- parsed = JSON.parse(response.to_str)
236
- pretty = JSON.pretty_generate(parsed)
237
- log "response: \n#{pretty}"
238
-
239
- return parsed
240
- rescue SocketError => e
241
- handle_restclient_error(e)
242
- rescue NoMethodError => e
243
- # Work around RestClient bug
244
- if e.message =~ /\WRequestFailed\W/
245
- e = APIConnectionError.new('Unexpected HTTP response code')
246
- handle_restclient_error(e)
247
- else
248
- raise
249
- end
250
- rescue RestClient::ExceptionWithResponse => e
251
- if rcode = e.http_code and rbody = e.http_body
252
- handle_api_error(rcode, rbody)
253
- else
254
- handle_restclient_error(e)
255
- end
256
- rescue RestClient::Exception, Errno::ECONNREFUSED => e
257
- handle_restclient_error(e)
258
- end
259
- end
260
-
261
- def handle_restclient_error(e)
262
- connection_message = "Please check your internet connection and try again. "
263
-
264
- case e
265
- when RestClient::RequestTimeout
266
- message = "Could not connect to BillForward (#{@host}). #{connection_message}"
267
- when RestClient::ServerBrokeConnection
268
- message = "The connection to the server (#{@host}) broke before the " \
269
- "request completed. #{connection_message}"
270
- when SocketError
271
- message = "Unexpected error communicating when trying to connect to BillForward. " \
272
- "Please confirm that (#{@host}) is a BillForward API URL. "
273
- else
274
- message = "Unexpected error communicating with BillForward. "
275
- end
276
-
277
- raise ClientException.new(message + "\n\n(Network error: #{e.message})")
278
- end
279
-
280
- def handle_api_error(rcode, rbody)
281
- begin
282
- # Example error JSON:
283
- # {
284
- # "errorType" : "ValidationError",
285
- # "errorMessage" : "Validation Error - Entity: Subscription Field: type Value: null Message: may not be null\nValidation Error - Entity: Subscription Field: productID Value: null Message: may not be null\nValidation Error - Entity: Subscription Field: name Value: null Message: may not be null\n",
286
- # "errorParameters" : [ "type", "productID", "name" ]
287
- # }
288
-
289
- error = JSON.parse(rbody)
290
-
291
- errorType = error['errorType']
292
- errorMessage = error['errorMessage']
293
- if (error.key? 'errorParameters')
294
- errorParameters = error['errorParameters']
295
- raise_message = "\n====\n#{rcode} API Error.\nType: #{errorType}\nMessage: #{errorMessage}\nParameters: #{errorParameters}\n====\n"
296
- else
297
- if (errorType == 'Oauth')
298
- split = errorMessage.split(', ')
299
-
300
- error = split.first.split('=').last
301
- description = split.last.split('=').last
302
-
303
- raise_message = "\n====\n#{rcode} Authorization failed.\nType: #{type}\nError: #{error}\nDescription: #{description}\n====\n"
304
-
305
- raise ApiAuthorizationError.new(error, rbody), raise_message
306
- else
307
- raise_message = "\n====\n#{rcode} API Error.\nType: #{errorType}\nMessage: #{errorMessage}\n====\n"
308
- end
309
- end
310
-
311
- raise ApiError.new(error, rbody), raise_message
312
- end
313
-
314
- raise_message = "\n====\n#{rcode} API Error.\n Response body: #{rbody}\n====\n"
315
- raise ApiError.new(nil, rbody), raise_message
316
- end
317
-
318
- def log(*args)
319
- if @use_logging
320
- puts *args
321
- end
322
- end
323
-
324
- def get_token
325
- if @api_token
326
- @api_token
327
- else
328
- if @authorization and Time.now < @authorization["expires_at"]
329
- return @authorization["access_token"]
330
- end
331
- begin
332
- response = RestClient.get("#{@host}oauth/token", :params => {
333
- :username => @username,
334
- :password => @password,
335
- :client_id => @client_id,
336
- :client_secret => @client_secret,
337
- :grant_type => "password"
338
- }, :accept => :json)
339
-
340
- @authorization = JSON.parse(response.to_str)
341
- @authorization["expires_at"] = Time.now + @authorization["expires_in"]
342
-
343
- @authorization["access_token"]
344
- rescue => e
345
- if e.respond_to? "response"
346
- log "BILL FORWARD CLIENT ERROR", e.response
347
- else
348
- log "BILL FORWARD CLIENT ERROR", e, e.to_json
349
- end
350
- nil
351
- end
352
- end
353
- end
354
- end
355
- end
1
+ module BillForward
2
+ class ClientException < Exception
3
+ attr_accessor :response
4
+
5
+ def initialize(message, response=nil)
6
+ super(message)
7
+
8
+ begin
9
+ if response.nil?
10
+ self.response = nil
11
+ else
12
+ self.response = JSON.parse response
13
+ end
14
+ rescue => e
15
+ self.response = nil
16
+ end
17
+ end
18
+ end
19
+
20
+ class ClientInstantiationException < Exception
21
+ end
22
+
23
+ class ApiError < Exception
24
+ attr_reader :json
25
+ attr_reader :raw
26
+
27
+ def initialize(json, raw)
28
+ @json = json
29
+ @raw = raw
30
+ end
31
+ end
32
+
33
+ class ApiAuthorizationError < ApiError
34
+ end
35
+
36
+ class ApiUnexpectedResponseFormatError < ApiError
37
+ end
38
+
39
+ class ApiTokenException < ClientException
40
+
41
+ end
42
+
43
+ class Client
44
+ @@payload_verbs = ['post', 'put']
45
+ @@no_payload_verbs = ['get', 'delete']
46
+ @@all_verbs = @@payload_verbs + @@no_payload_verbs
47
+
48
+ attr_accessor :host
49
+ attr_accessor :use_logging
50
+ attr_accessor :api_token
51
+
52
+ # provide access to self statics
53
+ class << self
54
+ attr_accessor :all_verbs
55
+
56
+ # default client is a singleton client
57
+ attr_reader :default_client
58
+ def default_client=(default_client)
59
+ if (default_client == nil)
60
+ # meaningless, but required for resetting this class after a test run
61
+ @default_client = nil
62
+ return
63
+ end
64
+
65
+ TypeCheck.verifyObj(Client, default_client, 'default_client')
66
+ @default_client = default_client
67
+ end
68
+ def default_client()
69
+ raise ClientInstantiationException.new("Failed to get default BillForward API Client; " +
70
+ "'default_client' is nil. Please set a 'default_client' first.") if
71
+ @default_client.nil?
72
+ @default_client
73
+ end
74
+ end
75
+ @all_verbs = @@all_verbs
76
+
77
+
78
+ # Constructs a client, and sets it to be used as the default client.
79
+ # @param options={} [Hash] Options with which to construct client
80
+ #
81
+ # @return [Client] The constructed client
82
+ def self.make_default_client(options)
83
+ constructedClient = self.new(options)
84
+ self.default_client = constructedClient
85
+ end
86
+
87
+ def initialize(options={})
88
+ TypeCheck.verifyObj(Hash, options, 'options')
89
+ @use_logging = options[:use_logging]
90
+
91
+ if options[:host]
92
+ @host = options[:host]
93
+ else
94
+ raise ClientInstantiationException.new "Failed to initialize BillForward API Client\n" +
95
+ "Required parameters: :host, and either [:api_token] or all of [:client_id, :client_secret, :username, :password].\n" +
96
+ "Supplied Parameters: #{options}"
97
+ end
98
+
99
+ if options[:use_proxy]
100
+ @use_proxy = options[:use_proxy]
101
+ @proxy_url = options[:proxy_url]
102
+ end
103
+
104
+ if options[:api_token]
105
+ @api_token = options[:api_token]
106
+ else
107
+ @api_token = nil
108
+ if options[:client_id] and options[:client_secret] and options[:username] and options[:password]
109
+ @client_id = options[:client_id]
110
+ @client_secret = options[:client_secret]
111
+ @username = options[:username]
112
+ @password = options[:password]
113
+ else
114
+ raise ClientException.new "Failed to initialize BillForward API Client\n"+
115
+ "Required parameters: :host and :use_logging, and either [:api_token] or all of [:client_id, :client_secret, :username, :password].\n" +
116
+ "Supplied Parameters: #{options}"
117
+ end
118
+
119
+ end
120
+
121
+ @authorization = nil
122
+ end
123
+
124
+ def execute_request(verb, url, token, payload=nil)
125
+ # Enable Fiddler:
126
+ if @use_proxy
127
+ RestClient.proxy = @proxy_url
128
+ end
129
+
130
+ # content_type seems to be broken on generic execute.
131
+ # darn.
132
+ # RestClient::Request.execute(options)
133
+ options = {
134
+ :Authorization => "Bearer #{token}",
135
+ :accept => 'application/json'
136
+ }
137
+
138
+ haspayload = @@payload_verbs.include?(verb)
139
+
140
+ if (haspayload)
141
+ options.update(:content_type => 'application/json')
142
+ end
143
+
144
+ args = [url, options]
145
+ args.insert(1, payload) if haspayload
146
+
147
+ RestClient.send(verb.intern, *args)
148
+ end
149
+
150
+ @@all_verbs.each do |action|
151
+ define_method(action.intern) do |*args|
152
+ verb = action
153
+ url = args.shift
154
+ payload = nil
155
+ if @@payload_verbs.include?(verb)
156
+ payload = args.shift
157
+ TypeCheck.verifyObj(String, payload, 'payload')
158
+ end
159
+
160
+ query_params = args.shift || {}
161
+ TypeCheck.verifyObj(Hash, query_params, 'query_params')
162
+
163
+ self.send(:request, *[verb, url, query_params, payload])
164
+ end
165
+ define_method("#{action}_many".intern) do |*args|
166
+ response = self.send(action.intern, *args)
167
+ results = response["results"]
168
+ if results.nil?
169
+ raise ApiUnexpectedResponseFormatError.new("Response did not contain a results array.")
170
+ end
171
+ results
172
+ end
173
+
174
+ define_method("#{action}_first".intern) do |*args|
175
+ results = self.send("#{action}_many".intern, *args)
176
+
177
+ if results.nil? or results.length == 0
178
+ raise IndexError.new("Cannot get first; request returned empty list of results.")
179
+ end
180
+
181
+ results.first
182
+ end
183
+ end
184
+
185
+ alias_method :retire, :delete
186
+ alias_method :retire_first, :delete_first
187
+ alias_method :get_results, :get_many
188
+
189
+ private
190
+ def uri_encode(params = {})
191
+ TypeCheck.verifyObj(Hash, params, 'params')
192
+
193
+ encoded_params = params.reduce([]) do |accumulator, (iterand_key, iterand_value)|
194
+ encoded_key = ERB::Util.url_encode iterand_key
195
+ encoded_value = ERB::Util.url_encode iterand_value
196
+ accumulator + ["#{encoded_key}=#{encoded_value}"]
197
+ end
198
+ query = encoded_params.join '&'
199
+
200
+ end
201
+
202
+ def request(verb, url, params={}, payload=nil)
203
+ qualified_url = "#{@host}#{url}"
204
+
205
+ split = qualified_url.split('?')
206
+ distilled_url = split.first
207
+ override_params = split.length > 1 \
208
+ ? split[1] \
209
+ : ''
210
+
211
+ param_string = override_params.empty? \
212
+ ? ((params && params.any?) \
213
+ ? uri_encode(params) \
214
+ : '') \
215
+ : override_params
216
+
217
+ # Make params into query parameters
218
+ full_url = param_string.empty? \
219
+ ? distilled_url \
220
+ : [distilled_url, param_string].join('?')
221
+
222
+ token = get_token
223
+
224
+ log "#{verb} #{url}"
225
+ log "token: #{token}"
226
+
227
+ begin
228
+ response = execute_request(verb, full_url, token, payload)
229
+
230
+ parsed = JSON.parse(response.to_str)
231
+ pretty = JSON.pretty_generate(parsed)
232
+ log "response: \n#{pretty}"
233
+
234
+ return parsed
235
+ rescue SocketError => e
236
+ handle_restclient_error(e)
237
+ rescue NoMethodError => e
238
+ # Work around RestClient bug
239
+ if e.message =~ /\WRequestFailed\W/
240
+ e = APIConnectionError.new('Unexpected HTTP response code')
241
+ handle_restclient_error(e)
242
+ else
243
+ raise
244
+ end
245
+ rescue RestClient::ExceptionWithResponse => e
246
+ if rcode = e.http_code and rbody = e.http_body
247
+ handle_api_error(rcode, rbody)
248
+ else
249
+ handle_restclient_error(e)
250
+ end
251
+ rescue RestClient::Exception, Errno::ECONNREFUSED => e
252
+ handle_restclient_error(e)
253
+ end
254
+ end
255
+
256
+ def handle_restclient_error(e)
257
+ connection_message = "Please check your internet connection and try again. "
258
+
259
+ case e
260
+ when RestClient::RequestTimeout
261
+ message = "Could not connect to BillForward (#{@host}). #{connection_message}"
262
+ when RestClient::ServerBrokeConnection
263
+ message = "The connection to the server (#{@host}) broke before the " \
264
+ "request completed. #{connection_message}"
265
+ when SocketError
266
+ message = "Unexpected error communicating when trying to connect to BillForward. " \
267
+ "Please confirm that (#{@host}) is a BillForward API URL. "
268
+ else
269
+ message = "Unexpected error communicating with BillForward. "
270
+ end
271
+
272
+ raise ClientException.new(message + "\n\n(Network error: #{e.message})")
273
+ end
274
+
275
+ def handle_api_error(rcode, rbody)
276
+ begin
277
+ # Example error JSON:
278
+ # {
279
+ # "errorType" : "ValidationError",
280
+ # "errorMessage" : "Validation Error - Entity: Subscription Field: type Value: null Message: may not be null\nValidation Error - Entity: Subscription Field: productID Value: null Message: may not be null\nValidation Error - Entity: Subscription Field: name Value: null Message: may not be null\n",
281
+ # "errorParameters" : [ "type", "productID", "name" ]
282
+ # }
283
+
284
+ error = JSON.parse(rbody)
285
+
286
+ errorType = error['errorType']
287
+ errorMessage = error['errorMessage']
288
+ if (error.key? 'errorParameters')
289
+ errorParameters = error['errorParameters']
290
+ raise_message = "\n====\n#{rcode} API Error.\nType: #{errorType}\nMessage: #{errorMessage}\nParameters: #{errorParameters}\n====\n"
291
+ else
292
+ if (errorType == 'Oauth')
293
+ split = errorMessage.split(', ')
294
+
295
+ error = split.first.split('=').last
296
+ description = split.last.split('=').last
297
+
298
+ raise_message = "\n====\n#{rcode} Authorization failed.\nType: #{type}\nError: #{error}\nDescription: #{description}\n====\n"
299
+
300
+ raise ApiAuthorizationError.new(error, rbody), raise_message
301
+ else
302
+ raise_message = "\n====\n#{rcode} API Error.\nType: #{errorType}\nMessage: #{errorMessage}\n====\n"
303
+ end
304
+ end
305
+
306
+ raise ApiError.new(error, rbody), raise_message
307
+ end
308
+
309
+ raise_message = "\n====\n#{rcode} API Error.\n Response body: #{rbody}\n====\n"
310
+ raise ApiError.new(nil, rbody), raise_message
311
+ end
312
+
313
+ def log(*args)
314
+ if @use_logging
315
+ puts *args
316
+ end
317
+ end
318
+
319
+ def get_token
320
+ if @api_token
321
+ @api_token
322
+ else
323
+ if @authorization and Time.now < @authorization["expires_at"]
324
+ return @authorization["access_token"]
325
+ end
326
+ begin
327
+ response = RestClient.get("#{@host}oauth/token", :params => {
328
+ :username => @username,
329
+ :password => @password,
330
+ :client_id => @client_id,
331
+ :client_secret => @client_secret,
332
+ :grant_type => "password"
333
+ }, :accept => :json)
334
+
335
+ @authorization = JSON.parse(response.to_str)
336
+ @authorization["expires_at"] = Time.now + @authorization["expires_in"]
337
+
338
+ @authorization["access_token"]
339
+ rescue => e
340
+ if e.respond_to? "response"
341
+ log "BILL FORWARD CLIENT ERROR", e.response
342
+ else
343
+ log "BILL FORWARD CLIENT ERROR", e, e.to_json
344
+ end
345
+ nil
346
+ end
347
+ end
348
+ end
349
+ end
350
+ end