charging-client 0.0.2

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.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rdoc/task'
4
+ Rake::RDocTask.new do |rdoc|
5
+ rdoc.title = 'charging-client-ruby'
6
+ rdoc.main = "README.rdoc"
7
+ rdoc.rdoc_dir = 'doc'
8
+ rdoc.rdoc_files.include("README.rdoc","lib/**/*.rb")
9
+ # rdoc.generator = 'darkfish'
10
+ end
11
+
12
+ require 'rspec/core/rake_task'
13
+ RSpec::Core::RakeTask.new(:spec)
14
+
15
+ task :test => :spec
16
+ task :default => :spec
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ # Ensure we require the local version and not one we might have installed already
6
+ require File.join([File.dirname(__FILE__),'lib','charging','version.rb'])
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = "charging-client"
10
+ spec.version = Charging::VERSION
11
+ spec.authors = ["Rodrigo Tassinari de Oliveira", "Celestino Gomes"]
12
+ spec.email = ["rodrigo@pittlandia.net", "rodrigo.tassinari@myfreecomm.com.br", "tinorj@gmail.com", "celestino.gomes@myfreecomm.com.br"]
13
+ spec.description = %q{A Ruby client for the Charging REST API}
14
+ spec.summary = %q{A Ruby client for the Charging REST API}
15
+ spec.homepage = "https://github.com/myfreecomm/charging-client-ruby"
16
+ spec.license = "Apache-v2"
17
+ spec.has_rdoc = true
18
+
19
+ # VCR cassettes are too long for the gemspec, see http://stackoverflow.com/questions/14371686/building-rails-3-engine-throwing-gempackagetoolongfilename-error
20
+ # spec.files = `git ls-files`.split($/)
21
+ spec.files = `git ls-files`.split($/).reject { |f| f =~ %r{(vcr_cassettes)/} }
22
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_dependency "rest-client", "~> 1.6.7"
27
+ spec.add_dependency "multi_json", "~> 1.11"
28
+
29
+ spec.add_development_dependency "bundler", "> 1.3.2"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency 'rdoc', '~> 4.0'
32
+ spec.add_development_dependency "rspec", "~> 2.13"
33
+ spec.add_development_dependency "vcr", "~> 2.4"
34
+ spec.add_development_dependency "webmock", "~> 1.9.3"
35
+ spec.add_development_dependency "pry", "~> 0.9"
36
+ spec.add_development_dependency "pry-nav", "~> 0.2"
37
+ spec.add_development_dependency "awesome_print", "~> 1.1"
38
+ spec.add_development_dependency "simplecov", "~> 0.7"
39
+ spec.add_development_dependency "coveralls", "~> 0.6"
40
+ end
data/lib/charging.rb ADDED
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+
3
+ # http://www.ruby-doc.org/stdlib-2.0.0/libdoc/English/rdoc/English.html
4
+ require 'English'
5
+
6
+ require 'rest_client'
7
+ require 'multi_json'
8
+
9
+ require 'charging/version'
10
+ require 'charging/helpers'
11
+ require 'charging/configuration'
12
+ require 'charging/http'
13
+
14
+ require 'charging/collection'
15
+ require 'charging/base'
16
+ require 'charging/service_account'
17
+ require 'charging/domain'
18
+ require 'charging/charge_account'
19
+ require 'charging/invoice'
20
+
21
+ module Charging
22
+ def self.configuration
23
+ @configuration ||= Configuration.new
24
+ end
25
+
26
+ def self.configure
27
+ yield(configuration) if block_given?
28
+ end
29
+
30
+ def self.use_sandbox!(application_token = 'AwdhihciTgORGUjnkuk1vg==')
31
+ Charging.configure do |config|
32
+ config.url = 'https://sandbox.charging.financeconnect.com.br'
33
+ config.application_token = application_token
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,109 @@
1
+ # encoding: UTF-8
2
+
3
+ module Charging
4
+ class Base
5
+ DEFAULT_PAGE = 1
6
+ DEFAULT_LIMIT = 10
7
+
8
+ COMMON_ATTRIBUTES = [:uuid, :uri, :etag]
9
+
10
+ attr_reader :last_response, :errors
11
+ attr_reader(*COMMON_ATTRIBUTES)
12
+
13
+ def initialize(attributes, response)
14
+ Helpers.load_variables(self, get_attributes, attributes)
15
+
16
+ @last_response = response
17
+ @errors = []
18
+ @deleted = false
19
+
20
+ normalize_etag!
21
+ end
22
+
23
+ def create!(&block)
24
+ execute_and_capture_raises_at_errors(201) do
25
+ @last_response = block.call
26
+ end
27
+
28
+ self
29
+ end
30
+
31
+ def destroy!(&block)
32
+ execute_and_capture_raises_at_errors(204) do
33
+ @last_response = block.call
34
+ end
35
+
36
+ if errors.empty?
37
+ @deleted = true
38
+ @persisted = false
39
+ end
40
+
41
+ self
42
+ end
43
+
44
+ def normalize_etag!
45
+ if @etag.nil?
46
+ @etag = last_response.headers[:etag] if last_response && last_response.code === 200
47
+ else
48
+ @etag = @etag.inspect
49
+ end
50
+ end
51
+
52
+ # Returns true if the object exists on Charging service.
53
+ def persisted?
54
+ (uuid && etag && uri && !deleted?) || false
55
+ end
56
+
57
+ # Returns true if the object exists on Charging service.
58
+ def unpersisted?
59
+ !persisted?
60
+ end
61
+
62
+ # Returns true if object already deleted on API
63
+ def deleted?
64
+ @deleted || false
65
+ end
66
+
67
+ # Returns a hash with attributes
68
+ def attributes
69
+ Helpers.hashify(self, self.class::ATTRIBUTES)
70
+ end
71
+
72
+ def self.validate_attributes!(attributes) # :nodoc:
73
+ keys = attributes.keys.map(&:to_sym)
74
+ diff = keys - (const_get(:ATTRIBUTES) + const_get(:READ_ONLY_ATTRIBUTES) + COMMON_ATTRIBUTES)
75
+ raise ArgumentError, "Invalid attributes for #{self.name}: #{attributes.inspect}" if diff.any?
76
+ end
77
+
78
+ private
79
+
80
+ def self.raise_last_response_unless(status_code, response)
81
+ raise Http::LastResponseError.new(response) if response.code != status_code
82
+ end
83
+
84
+ def raise_last_response_unless(status_code)
85
+ self.class.raise_last_response_unless(status_code, last_response)
86
+ end
87
+
88
+ def execute_and_capture_raises_at_errors(success_code, &block)
89
+ reset_errors!
90
+
91
+ block.call
92
+
93
+ raise_last_response_unless success_code
94
+ ensure
95
+ if $ERROR_INFO
96
+ @last_response = $ERROR_INFO.last_response if $ERROR_INFO.kind_of?(Http::LastResponseError)
97
+ @errors = [$ERROR_INFO.message]
98
+ end
99
+ end
100
+
101
+ def reset_errors!
102
+ @errors = []
103
+ end
104
+
105
+ def get_attributes
106
+ ((self.class::ATTRIBUTES || []) + (self.class::READ_ONLY_ATTRIBUTES || []) + COMMON_ATTRIBUTES).flatten.uniq
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,169 @@
1
+ # encoding: UTF-8
2
+
3
+ module Charging
4
+ class ChargeAccount < Base
5
+ DEFAULT_PAGE = 1
6
+ DEFAULT_LIMIT = 10
7
+
8
+ READ_ONLY_ATTRIBUTES = [:national_identifier]
9
+
10
+ ATTRIBUTES = [
11
+ :account, :agency, :name, :portfolio_code, :address, :zipcode,
12
+ :sequence_numbers, :currency, :agreement_code, :supplier_name,
13
+ :advance_days, :bank, :our_number_range, :default_charging_features,
14
+ :city_state
15
+ ]
16
+
17
+ attr_accessor(*ATTRIBUTES)
18
+ attr_reader(*READ_ONLY_ATTRIBUTES, :domain)
19
+
20
+ def initialize(attributes, domain, response = nil)
21
+ super(attributes, response)
22
+ @domain = domain
23
+ end
24
+
25
+ # Creates current charge account at API.
26
+ #
27
+ # API method: <tt>POST /account/domains/</tt>
28
+ #
29
+ # API documentation: https://charging.financeconnect.com.br/static/docs/accounts_and_domains.html#post-account-domains
30
+ def create!
31
+ super do
32
+ raise 'can not create without a domain' if invalid_domain?
33
+
34
+ ChargeAccount.post_charge_accounts(domain, attributes)
35
+ end
36
+
37
+ reload_attributes!
38
+ end
39
+
40
+ # Deletes the charge account at API
41
+ #
42
+ # API method: <tt>DELETE /charge-accounts/:uuid/</tt>
43
+ #
44
+ # API documentation: https://charging.financeconnect.com.br/static/docs/charges.html#delete-charge-accounts-uuid
45
+ def destroy!
46
+ super do
47
+ Http.delete("/charge-accounts/#{uuid}/", domain.token, etag)
48
+ end
49
+ end
50
+
51
+ # Update an attribute on charge account at API.
52
+ #
53
+ # API method: <tt>PATCH /charge-accounts/:uuid/</tt>
54
+ #
55
+ # API documentation: https://charging.financeconnect.com.br/static/docs/charges.html#patch-charge-accounts-uuid
56
+ def update_attribute!(attribute, value, should_reload_attributes = true)
57
+ execute_and_capture_raises_at_errors(204) do
58
+ @last_response = Http.patch("/charge-accounts/#{uuid}/", domain.token, etag, attribute => value)
59
+ end
60
+
61
+ reload_attributes! if should_reload_attributes
62
+ end
63
+
64
+ # Update all attributes at charge_account. This method uses
65
+ # <tt>update_attribute!</tt> recurring for each attrubute.
66
+ # <tt>attrubutes_valies</tt> should be a hash with attribute and value to
67
+ # be updated.
68
+ def update_attributes!(attributes_values)
69
+ attributes_values.each do |attribute, value|
70
+ update_attribute! attribute, value, false
71
+ end
72
+ ensure
73
+ reload_attributes!
74
+ end
75
+
76
+ # Finds a charge account by uuid. It requites an <tt>domain</tt> and a
77
+ # <tt>uuid</tt>.
78
+ #
79
+ # Returns a ChargeAccount instance or raises a Http::LastResponseError if something
80
+ # went wrong, like unauthorized request, not found.
81
+ #
82
+ # API method: <tt>GET /charge-accounts/:uuid/</tt>
83
+ #
84
+ # API documentation: https://charging.financeconnect.com.br/static/docs/charges.html#get-charge-accounts-uuid
85
+ def self.find_by_uuid(domain, uuid)
86
+ Helpers.required_arguments!(domain: domain, uuid: uuid)
87
+
88
+ response = ChargeAccount.get_charge_account(domain, uuid)
89
+
90
+ raise_last_response_unless 200, response
91
+
92
+ load_persisted_charge_account(MultiJson.decode(response.body), response, domain)
93
+ end
94
+
95
+ # Finds all charge accounts for a domain. It requites an <tt>domain</tt>,
96
+ # and you should pass <tt>page</tt> and/or <tt>limit</tt> to apply on find.
97
+ #
98
+ # Returns a Collection (Array-like) of ChargeAccount
99
+ #
100
+ # API method: <tt>GET /charge-accounts/?page=:page&limit=:limit</tt>
101
+ #
102
+ # API documentation: https://charging.financeconnect.com.br/static/docs/charges.html#get-charge-accounts-limit-limit-page-page
103
+ def self.find_all(domain, page = DEFAULT_PAGE, limit = DEFAULT_LIMIT)
104
+ Helpers.required_arguments!(domain: domain)
105
+
106
+ response = get_charge_accounts(domain, page, limit)
107
+
108
+ Collection.new(domain, response)
109
+ end
110
+
111
+ # Finds a charge account by uri. It requites an <tt>domain</tt> and a
112
+ # <tt>String</tt>.
113
+ #
114
+ # Returns a ChargeAccount instance or raises a Http::LastResponseError if something
115
+ # went wrong, like unauthorized request, not found.
116
+ def self.find_by_uri(domain, uri)
117
+ Helpers.required_arguments!(domain: domain, uri: uri)
118
+
119
+ response = Http.get(uri, domain.token)
120
+
121
+ raise_last_response_unless 200, response
122
+
123
+ ChargeAccount.load_persisted_charge_account(MultiJson.decode(response.body), response, domain)
124
+ end
125
+
126
+ def self.load_persisted_charge_account(attributes, response, domain)
127
+ validate_attributes!(attributes)
128
+ ChargeAccount.new(attributes, domain, response)
129
+ end
130
+
131
+ private
132
+
133
+ def invalid_domain?
134
+ domain.nil?
135
+ end
136
+
137
+ def reload_attributes!
138
+ new_charge_account = self.class.find_by_uuid(domain, Helpers.extract_uuid(last_response.headers[:location]) || uuid)
139
+
140
+ (ATTRIBUTES + COMMON_ATTRIBUTES + READ_ONLY_ATTRIBUTES).each do |attribute|
141
+ instance_variable_set "@#{attribute}", new_charge_account.send(attribute)
142
+ end
143
+
144
+ self
145
+ end
146
+
147
+ def self.get_charge_accounts(domain, page, limit)
148
+ Http.get("/charge-accounts/?page=#{page}&limit=#{limit}", domain.token)
149
+ end
150
+
151
+ def self.get_charge_account(domain, uuid)
152
+ Http.get("/charge-accounts/#{uuid}/", domain.token)
153
+ end
154
+
155
+ def self.post_charge_accounts(domain, attributes)
156
+ Http.post('/charge-accounts/', domain.token, MultiJson.encode(attributes))
157
+ end
158
+
159
+ class Collection < Charging::Collection
160
+ def initialize(domain, response)
161
+ super(response, domain: domain)
162
+ end
163
+
164
+ def load_object_with(attributes)
165
+ ChargeAccount.load_persisted_charge_account(attributes, last_response, @domain)
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ module Charging
4
+ # Represents a object collection result. It is a delegator for an array of object.
5
+ class Collection < SimpleDelegator
6
+
7
+ # Responds the last http response from the API.
8
+ attr_reader :last_response
9
+
10
+ def initialize(response, attributes) # :nodoc:
11
+ Helpers.required_arguments!(attributes.merge('response' => response))
12
+
13
+ @last_response = response
14
+
15
+ attributes.each do |attribute, value|
16
+ instance_variable_set("@#{attribute}", value)
17
+ end
18
+
19
+ super(load_data_with_response!)
20
+ end
21
+
22
+ def load_data_with_response! # :nodoc:
23
+ return [] if last_response.code != 200
24
+
25
+ raw_lines = MultiJson.decode(last_response.body)
26
+ raw_lines.map { |raw_line| load_object_with(raw_line) }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ require 'base64'
3
+
4
+ module Charging
5
+ class Configuration
6
+ attr_accessor :application_token, :url, :user_agent
7
+
8
+ def initialize(application_token = nil)
9
+ @application_token = application_token
10
+ @url = 'https://charging.financeconnect.com.br'
11
+ @user_agent = "Charging Ruby Client v#{Charging::VERSION}"
12
+ end
13
+
14
+ def credentials_for(token = application_token)
15
+ check_valid_token!(token)
16
+ encrypted_token = ::Base64.strict_encode64(":#{token}")
17
+ "Basic #{encrypted_token}"
18
+ end
19
+
20
+ private
21
+
22
+ def check_valid_token!(token = application_token)
23
+ invalid = token.nil? || token.to_s.strip.empty?
24
+ raise(ArgumentError, "#{token.inspect} is not a valid token") if invalid
25
+ true
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,164 @@
1
+ # encoding: utf-8
2
+
3
+ module Charging
4
+ # Represents a Charging domain.
5
+ class Domain < Base
6
+ READ_ONLY_ATTRIBUTES = [:token]
7
+
8
+ ATTRIBUTES = [ :supplier_name, :address, :city_state, :zipcode, :national_identifier, :description ]
9
+
10
+ attr_accessor(*ATTRIBUTES)
11
+ attr_reader(*READ_ONLY_ATTRIBUTES, :account)
12
+
13
+ # Initializes a domain instance
14
+ def initialize(attributes, account, response = nil)
15
+ super(attributes, response)
16
+ @account = account
17
+ end
18
+
19
+ # Creates current domain at API.
20
+ #
21
+ # API method: <tt>POST /account/domains/</tt>
22
+ #
23
+ # API documentation: https://charging.financeconnect.com.br/static/docs/accounts_and_domains.html#post-account-domains
24
+ def create!
25
+ super do
26
+ raise 'can not create without a service account' if invalid_account?
27
+
28
+ Domain.post_account_domains(account.application_token, attributes)
29
+ end
30
+
31
+ reload_attributes!
32
+ end
33
+
34
+ # Destroys current domain at API.
35
+ #
36
+ # API method: <tt>DELETE /account/domains/:uuid/</tt>
37
+ #
38
+ # API documentation: https://charging.financeconnect.com.br/static/docs/accounts_and_domains.html#delete-account-domains-uuid
39
+ def destroy!
40
+ super do
41
+ raise 'can not destroy without a service account' if invalid_account?
42
+ raise 'can not destroy a not persisted domain' unless persisted?
43
+
44
+ Domain.delete_account_domains(self)
45
+ end
46
+ end
47
+
48
+ # Finds all domains for a specified account. It requites an ServiceAccount
49
+ # instance, and you should pass <tt>page</tt> and/or <tt>limit</tt> to
50
+ # apply on find.
51
+ #
52
+ # Returns a Collection (Array-like) of Domain
53
+ #
54
+ # API method: <tt>GET /account/domains/</tt>
55
+ #
56
+ # API documentation: https://charging.financeconnect.com.br/static/docs/accounts_and_domains.html#get-account-domains-limit-limit-page-page
57
+ def self.find_all(account, page = DEFAULT_PAGE, limit = DEFAULT_LIMIT)
58
+ Helpers.required_arguments!('service account' => account)
59
+
60
+ response = get_account_domains(account, page, limit)
61
+
62
+ Collection.new(account, response)
63
+ end
64
+
65
+ # Finds a domain by your uuid. It requites an ServiceAccount instance and a
66
+ # String <tt>uuid</tt>.
67
+ #
68
+ # Returns a Domain instance or raises a Http::LastResponseError if something
69
+ # went wrong, like unauthorized request, not found.
70
+ #
71
+ # API method: <tt>GET /account/domains/:uuid/</tt>
72
+ #
73
+ # API documentation: https://charging.financeconnect.com.br/static/docs/accounts_and_domains.html#get-account-domains-uuid
74
+ def self.find_by_uuid(account, uuid)
75
+ Helpers.required_arguments!('service account' => account, uuid: uuid)
76
+
77
+ response = get_account_domain(account, uuid)
78
+
79
+ raise_last_response_unless 200, response
80
+
81
+ load_persisted_domain(MultiJson.decode(response.body), response, account)
82
+ end
83
+
84
+ # Finds a domain by its authentication token. It requites an <tt>token</tt>.
85
+ #
86
+ # Returns a Domain instance or raises a Http::LastResponseError if something
87
+ # went wrong, like unauthorized request, not found.
88
+ #
89
+ # API method: <tt>GET /domain/</tt>
90
+ #
91
+ # API documentation: https://charging.financeconnect.com.br/static/docs/accounts_and_domains.html#get-subuser-domain
92
+ def self.find_by_token(token)
93
+ Helpers.required_arguments!('token' => token)
94
+
95
+ response = get_domain(token)
96
+
97
+ raise_last_response_unless 200, response
98
+
99
+ load_persisted_domain(MultiJson.decode(response.body), response)
100
+ end
101
+
102
+ def self.load_persisted_domain(attributes, response, account = nil) # :nodoc:
103
+ validate_attributes!(attributes)
104
+ Domain.new(attributes, account, response)
105
+ end
106
+
107
+ private
108
+
109
+ def reload_attributes!
110
+ new_domain = self.class.find_by_uuid(account, Helpers.extract_uuid(last_response.headers[:location]) || uuid)
111
+
112
+ (COMMON_ATTRIBUTES + READ_ONLY_ATTRIBUTES).each do |attribute|
113
+ instance_variable_set "@#{attribute}", new_domain.send(attribute)
114
+ end
115
+
116
+ self
117
+ end
118
+
119
+ def load_errors(*error_messages)
120
+ @errors = error_messages.flatten
121
+ end
122
+
123
+ def invalid_account?
124
+ account.nil?
125
+ end
126
+
127
+ def self.delete_account_domains(domain)
128
+ token = domain.account.application_token
129
+ Http.delete("/account/domains/#{domain.uuid}/", token, domain.etag)
130
+ end
131
+
132
+ def self.post_account_domains(token, attributes)
133
+ Http.post('/account/domains/', token, MultiJson.encode(attributes))
134
+ end
135
+
136
+ def self.get_domain(token) # :nodoc:
137
+ Http.get('/domain/', token)
138
+ end
139
+
140
+ def self.get_account_domain(account, uuid) # :nodoc:
141
+ Http.get("/account/domains/#{uuid}/", account.application_token)
142
+ end
143
+
144
+ def self.get_account_domains(account, page, limit) # :nodoc:
145
+ Http.get("/account/domains/?page=#{page}&limit=#{limit}", account.application_token)
146
+ end
147
+
148
+ def self.create_domain_collection_for(response) # :nodoc:
149
+ data = response.code === 200 ? MultiJson.decode(response.body) : []
150
+
151
+ Collection.new(data, response)
152
+ end
153
+
154
+ class Collection < Charging::Collection
155
+ def initialize(account, response)
156
+ super(response, account: account)
157
+ end
158
+
159
+ def load_object_with(attributes)
160
+ Domain.load_persisted_domain(attributes, last_response, @account)
161
+ end
162
+ end
163
+ end
164
+ end