charging-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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