asaas-sdk 0.1.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 (39) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +32 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +435 -0
  5. data/lib/asaas/asaas_object.rb +76 -0
  6. data/lib/asaas/client.rb +270 -0
  7. data/lib/asaas/configuration.rb +31 -0
  8. data/lib/asaas/errors.rb +83 -0
  9. data/lib/asaas/list_object.rb +52 -0
  10. data/lib/asaas/resources/anticipation.rb +14 -0
  11. data/lib/asaas/resources/base.rb +50 -0
  12. data/lib/asaas/resources/bill_payment.rb +9 -0
  13. data/lib/asaas/resources/chargeback.rb +14 -0
  14. data/lib/asaas/resources/checkout.rb +9 -0
  15. data/lib/asaas/resources/credit_card.rb +20 -0
  16. data/lib/asaas/resources/customer.rb +20 -0
  17. data/lib/asaas/resources/document.rb +35 -0
  18. data/lib/asaas/resources/dunning.rb +14 -0
  19. data/lib/asaas/resources/finance.rb +25 -0
  20. data/lib/asaas/resources/has_client.rb +15 -0
  21. data/lib/asaas/resources/installment.rb +14 -0
  22. data/lib/asaas/resources/invoice.rb +9 -0
  23. data/lib/asaas/resources/my_account.rb +34 -0
  24. data/lib/asaas/resources/notification.rb +19 -0
  25. data/lib/asaas/resources/payment.rb +46 -0
  26. data/lib/asaas/resources/payment_link.rb +14 -0
  27. data/lib/asaas/resources/pix.rb +56 -0
  28. data/lib/asaas/resources/pix_automatic.rb +9 -0
  29. data/lib/asaas/resources/sandbox.rb +24 -0
  30. data/lib/asaas/resources/split.rb +32 -0
  31. data/lib/asaas/resources/subaccount.rb +20 -0
  32. data/lib/asaas/resources/subscription.rb +15 -0
  33. data/lib/asaas/resources/transfer.rb +9 -0
  34. data/lib/asaas/resources/webhook.rb +14 -0
  35. data/lib/asaas/version.rb +5 -0
  36. data/lib/asaas/webhook_event.rb +31 -0
  37. data/lib/asaas-sdk.rb +5 -0
  38. data/lib/asaas.rb +62 -0
  39. metadata +152 -0
@@ -0,0 +1,270 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "json"
6
+ require "securerandom"
7
+
8
+ module Asaas
9
+ class Client
10
+ RETRY_STATUSES = [429, 500, 502, 503, 504].freeze
11
+ IDEMPOTENT_METHODS = %i[post put patch].freeze
12
+ MAX_LOG_BODY_BYTES = 200
13
+ SENSITIVE_LOG_KEYS = %w[
14
+ accesskey
15
+ accesssecret
16
+ accesstoken
17
+ apikey
18
+ apisecret
19
+ authkey
20
+ authenticationtoken
21
+ authorization
22
+ authtoken
23
+ bearertoken
24
+ clientsecret
25
+ creditcard
26
+ creditcardholderinfo
27
+ creditcardtoken
28
+ idtoken
29
+ password
30
+ privatekey
31
+ refreshtoken
32
+ cardnumber
33
+ creditcardnumber
34
+ ccv
35
+ cvv
36
+ number
37
+ remoteip
38
+ secret
39
+ secretkey
40
+ sessiontoken
41
+ token
42
+ webhooktoken
43
+ ].freeze
44
+ HTTP_METHODS = {
45
+ get: Net::HTTP::Get,
46
+ post: Net::HTTP::Post,
47
+ put: Net::HTTP::Put,
48
+ patch: Net::HTTP::Patch,
49
+ delete: Net::HTTP::Delete
50
+ }.freeze
51
+
52
+ def initialize(config = Asaas.config, api_key: nil)
53
+ @config = config
54
+ @api_key = api_key || config.api_key
55
+ end
56
+
57
+ # @param method [:get, :post, :put, :patch, :delete]
58
+ # @param path [String]
59
+ # @param params [Hash]
60
+ # @param headers [Hash]
61
+ # @param retryable [Boolean]
62
+ # @return [Hash]
63
+ def request(method, path, params: {}, headers: {}, retryable: true, timeout: nil, idempotency_key: nil) # rubocop:disable Metrics/ParameterLists
64
+ validate_config!
65
+
66
+ uri = build_uri(path, method == :get ? params : {})
67
+ body = method == :get ? {} : params
68
+ request_idempotency_key = idempotency_key_for(method, idempotency_key)
69
+ request_headers = build_headers(headers, request_idempotency_key)
70
+ if explicit_idempotency_key?(method, idempotency_key)
71
+ replace_idempotency_key(request_headers, request_idempotency_key)
72
+ end
73
+ operation = -> { perform(method, uri, body, request_headers, timeout: timeout) }
74
+
75
+ retryable ? with_retries(&operation) : operation.call
76
+ end
77
+
78
+ private
79
+
80
+ def validate_config!
81
+ return unless @api_key.nil? || @api_key.empty?
82
+
83
+ raise ConfigurationError, "Asaas.api_key is not set. Call Asaas.configure { |c| c.api_key = '...' }"
84
+ end
85
+
86
+ def build_uri(path, query_params = {})
87
+ uri = URI.parse("#{@config.base_url}#{path}")
88
+ uri.query = URI.encode_www_form(flatten_params(query_params)) if query_params.any?
89
+ uri
90
+ end
91
+
92
+ def build_headers(extra = {}, idempotency_key = nil)
93
+ headers = {
94
+ "Content-Type" => "application/json",
95
+ "Accept" => "application/json",
96
+ "access_token" => @api_key,
97
+ "User-Agent" => "AsaasRuby/#{Asaas::VERSION}"
98
+ }
99
+ headers["Idempotency-Key"] = idempotency_key if idempotency_key
100
+ headers.merge(extra)
101
+ end
102
+
103
+ def idempotency_key_for(method, explicit_key)
104
+ return unless IDEMPOTENT_METHODS.include?(method)
105
+ return SecureRandom.uuid if explicit_key.nil?
106
+ return explicit_key.dup.freeze if explicit_key.is_a?(String) && !explicit_key.strip.empty?
107
+
108
+ raise ArgumentError, "idempotency_key must be a non-blank String"
109
+ end
110
+
111
+ def explicit_idempotency_key?(method, key)
112
+ IDEMPOTENT_METHODS.include?(method) && !key.nil?
113
+ end
114
+
115
+ def replace_idempotency_key(headers, key)
116
+ headers.delete_if { |name, _| name.to_s.casecmp?("Idempotency-Key") }
117
+ headers["Idempotency-Key"] = key
118
+ end
119
+
120
+ def perform(method, uri, body, headers, timeout: nil)
121
+ http = Net::HTTP.new(uri.host, uri.port)
122
+ http.use_ssl = uri.scheme == "https"
123
+ http.read_timeout = timeout || @config.timeout
124
+ http.open_timeout = timeout || @config.timeout
125
+
126
+ req = build_request(method, uri, headers, body)
127
+
128
+ log_request(method, uri, body)
129
+
130
+ res = begin
131
+ http.request(req)
132
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT,
133
+ Net::OpenTimeout, Net::ReadTimeout, SocketError => e
134
+ raise ConnectionError, "Network error: #{e.message}"
135
+ end
136
+
137
+ log_response(res)
138
+ parse_response(res)
139
+ end
140
+
141
+ def build_request(method, uri, headers, body)
142
+ req = HTTP_METHODS.fetch(method).new(uri.request_uri, headers)
143
+ encode_body(req, body) if body.any?
144
+ req
145
+ end
146
+
147
+ def encode_body(req, body)
148
+ if multipart?(body)
149
+ req.delete("Content-Type")
150
+ req.set_form(to_multipart_parts(body), "multipart/form-data")
151
+ else
152
+ req.body = JSON.generate(body)
153
+ end
154
+ end
155
+
156
+ def multipart?(params)
157
+ params.values.any? { |v| v.respond_to?(:read) }
158
+ end
159
+
160
+ def to_multipart_parts(params)
161
+ params.map do |k, v|
162
+ if v.respond_to?(:read)
163
+ filename = v.respond_to?(:path) ? File.basename(v.path) : k.to_s
164
+ [k.to_s, v, { filename: filename }]
165
+ else
166
+ [k.to_s, v.to_s]
167
+ end
168
+ end
169
+ end
170
+
171
+ def parse_response(res)
172
+ status = res.code.to_i
173
+ request_id = res["X-Request-Id"]
174
+ body = parse_body(res.body)
175
+
176
+ raise Asaas.error_for_status(status, body, request_id) unless (200..299).cover?(status)
177
+
178
+ body
179
+ end
180
+
181
+ def parse_body(raw)
182
+ return {} if raw.nil? || raw.empty?
183
+
184
+ JSON.parse(raw)
185
+ rescue JSON::ParserError
186
+ {}
187
+ end
188
+
189
+ def with_retries
190
+ attempts = 0
191
+ begin
192
+ yield
193
+ rescue RateLimitError, ServerError, ConnectionError => e
194
+ attempts += 1
195
+ if attempts <= @config.max_retries && retryable?(e)
196
+ sleep(@config.retry_delay * (2**(attempts - 1)))
197
+ retry
198
+ end
199
+ raise
200
+ end
201
+ end
202
+
203
+ def retryable?(error)
204
+ case error
205
+ in ConnectionError | RateLimitError then true
206
+ in ServerError if RETRY_STATUSES.include?(error.http_status) then true
207
+ else false
208
+ end
209
+ end
210
+
211
+ def flatten_params(params, prefix = nil)
212
+ params.each_with_object({}) do |(k, v), result|
213
+ key = prefix ? "#{prefix}[#{k}]" : k.to_s
214
+ case v
215
+ when Hash then result.merge!(flatten_params(v, key))
216
+ when Array then result[key] = v.join(",")
217
+ else result[key] = v
218
+ end
219
+ end
220
+ end
221
+
222
+ def log_request(method, uri, body)
223
+ return unless @config.logger
224
+
225
+ body_log = if body.none?
226
+ ""
227
+ elsif multipart?(body)
228
+ " [multipart/form-data: #{body.keys.join(", ")}]"
229
+ else
230
+ " #{sanitize(body).to_json}"
231
+ end
232
+
233
+ @config.logger.debug("[Asaas] --> #{method.upcase} #{uri}#{body_log}")
234
+ end
235
+
236
+ def log_response(res)
237
+ return unless @config.logger
238
+
239
+ parsed_body = JSON.parse(res.body.to_s)
240
+ @config.logger.debug("[Asaas] <-- #{res.code} #{bounded_log_json(parsed_body)}")
241
+ rescue JSON::ParserError
242
+ @config.logger.debug("[Asaas] <-- #{res.code} #{res.body.to_s.bytesize} bytes")
243
+ end
244
+
245
+ def bounded_log_json(value)
246
+ sanitized_json = JSON.generate(sanitize(value))
247
+ return sanitized_json if sanitized_json.bytesize <= MAX_LOG_BODY_BYTES
248
+
249
+ JSON.generate("_truncated" => true, "_bytes" => sanitized_json.bytesize)
250
+ end
251
+
252
+ def sanitize(value)
253
+ case value
254
+ when Hash
255
+ value.each_with_object({}) do |(key, nested_value), sanitized|
256
+ sanitized[key] = sensitive_key?(key) ? "[FILTERED]" : sanitize(nested_value)
257
+ end
258
+ when Array
259
+ value.map { |nested_value| sanitize(nested_value) }
260
+ else
261
+ value
262
+ end
263
+ end
264
+
265
+ def sensitive_key?(key)
266
+ normalized_key = key.to_s.downcase.gsub(/[^a-z0-9]/, "")
267
+ SENSITIVE_LOG_KEYS.include?(normalized_key)
268
+ end
269
+ end
270
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ class Configuration
5
+ PRODUCTION_URL = "https://api.asaas.com/v3"
6
+ SANDBOX_URL = "https://sandbox.asaas.com/api/v3"
7
+
8
+ DEFAULT_TIMEOUT = 30
9
+ DEFAULT_MAX_RETRIES = 2
10
+ DEFAULT_RETRY_DELAY = 0.5
11
+
12
+ attr_accessor :api_key,
13
+ :sandbox,
14
+ :timeout,
15
+ :max_retries,
16
+ :retry_delay,
17
+ :logger
18
+
19
+ def initialize
20
+ @sandbox = false
21
+ @timeout = DEFAULT_TIMEOUT
22
+ @max_retries = DEFAULT_MAX_RETRIES
23
+ @retry_delay = DEFAULT_RETRY_DELAY
24
+ @logger = nil
25
+ end
26
+
27
+ def base_url
28
+ sandbox ? SANDBOX_URL : PRODUCTION_URL
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ class AsaasError < StandardError
5
+ attr_reader :http_status, :http_body, :request_id, :errors
6
+
7
+ def initialize(message = nil, http_status: nil, http_body: nil, request_id: nil, errors: nil)
8
+ super(message)
9
+ @http_status = http_status
10
+ @http_body = http_body
11
+ @request_id = request_id
12
+ @errors = errors || []
13
+ end
14
+
15
+ def to_s
16
+ msg = super
17
+ msg += " (status=#{http_status})" if http_status
18
+ msg += " [request_id=#{request_id}]" if request_id
19
+ msg
20
+ end
21
+ end
22
+
23
+ # 400
24
+ class InvalidRequestError < AsaasError; end
25
+ # 401
26
+ class AuthenticationError < AsaasError; end
27
+ # 403
28
+ class PermissionError < AsaasError; end
29
+ # 404
30
+ class NotFoundError < AsaasError; end
31
+ # 409
32
+ class ConflictError < AsaasError; end
33
+ # 422
34
+ class UnprocessableEntityError < AsaasError; end
35
+ # 429
36
+ class RateLimitError < AsaasError; end
37
+ # 5xx
38
+ class ServerError < AsaasError; end
39
+ # network
40
+ class ConnectionError < AsaasError; end
41
+ # sdk config
42
+ class ConfigurationError < AsaasError; end
43
+ # webhook token missing or mismatched
44
+ class WebhookVerificationError < AsaasError; end
45
+ # webhook payload is not valid JSON
46
+ class InvalidPayloadError < AsaasError; end
47
+
48
+ def self.error_for_status(http_status, http_body, request_id)
49
+ message = extract_message(http_body)
50
+ errors = extract_errors(http_body)
51
+ kwargs = { http_status:, http_body:, request_id:, errors: }
52
+
53
+ case http_status
54
+ when 400 then InvalidRequestError.new(message, **kwargs)
55
+ when 401 then AuthenticationError.new(message, **kwargs)
56
+ when 403 then PermissionError.new(message, **kwargs)
57
+ when 404 then NotFoundError.new(message, **kwargs)
58
+ when 409 then ConflictError.new(message, **kwargs)
59
+ when 422 then UnprocessableEntityError.new(message, **kwargs)
60
+ when 429 then RateLimitError.new(message, **kwargs)
61
+ when 500..599 then ServerError.new(message, **kwargs)
62
+ else AsaasError.new(message, **kwargs)
63
+ end
64
+ end
65
+
66
+ def self.extract_message(body)
67
+ return "Unknown error" unless body.is_a?(Hash)
68
+
69
+ if body["errors"].is_a?(Array) && body["errors"].any?
70
+ body["errors"].filter_map { _1["description"] }.join("; ")
71
+ else
72
+ body["message"] || "Unknown error"
73
+ end
74
+ end
75
+ private_class_method :extract_message
76
+
77
+ def self.extract_errors(body)
78
+ return [] unless body.is_a?(Hash) && body["errors"].is_a?(Array)
79
+
80
+ body["errors"]
81
+ end
82
+ private_class_method :extract_errors
83
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ class ListObject
5
+ include Enumerable
6
+
7
+ attr_reader :data, :has_more, :total_count, :limit, :offset
8
+
9
+ def initialize(attrs, client:, path:, params: {})
10
+ @data = Array(attrs["data"]).map { AsaasObject.construct_from(_1) }
11
+ @has_more = attrs["hasMore"] || false
12
+ @total_count = attrs["totalCount"]
13
+ @limit = attrs["limit"]
14
+ @offset = attrs["offset"] || 0
15
+ @client = client
16
+ @path = path
17
+ @params = params
18
+ end
19
+
20
+ def each(&block)
21
+ @data.each(&block)
22
+ end
23
+
24
+ def next_page
25
+ return nil unless @has_more
26
+
27
+ next_params = @params.merge("offset" => @offset + @limit, "limit" => @limit)
28
+ raw = @client.request(:get, @path, params: next_params)
29
+ self.class.new(raw, client: @client, path: @path, params: next_params)
30
+ end
31
+
32
+ def auto_paging_each(&block)
33
+ return enum_for(:auto_paging_each) unless block
34
+
35
+ page = self
36
+ loop do
37
+ page.each(&block)
38
+ break unless page.has_more
39
+
40
+ page = page.next_page
41
+ end
42
+ end
43
+
44
+ def self.construct_from(attrs, client:, path:, params: {})
45
+ new(attrs, client: client, path: path, params: params)
46
+ end
47
+
48
+ def inspect
49
+ "#<#{self.class} total_count=#{@total_count} has_more=#{@has_more} data=#{@data.inspect}>"
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class Anticipation < Base
6
+ def self.resource_path = "/anticipations"
7
+
8
+ def self.simulate(params = {}, opts = {})
9
+ response = client(opts).request(:post, "#{resource_path}/simulate", params: params)
10
+ AsaasObject.construct_from(response)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class Base
6
+ extend HasClient
7
+
8
+ def self.resource_path
9
+ raise NotImplementedError, "#{name} must define resource_path"
10
+ end
11
+
12
+ def self.create(params = {}, opts = {})
13
+ response = client(opts).request(
14
+ :post,
15
+ resource_path,
16
+ params: params,
17
+ retryable: opts.fetch(:retryable, true),
18
+ timeout: opts[:timeout],
19
+ idempotency_key: opts[:idempotency_key]
20
+ )
21
+ AsaasObject.construct_from(response)
22
+ end
23
+
24
+ def self.retrieve(id, opts = {})
25
+ response = client(opts).request(
26
+ :get,
27
+ "#{resource_path}/#{id}",
28
+ retryable: opts.fetch(:retryable, true),
29
+ timeout: opts[:timeout]
30
+ )
31
+ AsaasObject.construct_from(response)
32
+ end
33
+
34
+ def self.update(id, params = {}, opts = {})
35
+ response = client(opts).request(:put, "#{resource_path}/#{id}", params: params)
36
+ AsaasObject.construct_from(response)
37
+ end
38
+
39
+ def self.delete(id, opts = {})
40
+ response = client(opts).request(:delete, "#{resource_path}/#{id}")
41
+ AsaasObject.construct_from(response)
42
+ end
43
+
44
+ def self.list(params = {}, opts = {})
45
+ response = client(opts).request(:get, resource_path, params: params)
46
+ ListObject.construct_from(response, client: client(opts), path: resource_path, params: params)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class BillPayment < Base
6
+ def self.resource_path = "/bill"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class Chargeback < Base
6
+ def self.resource_path = "/chargebacks"
7
+
8
+ def self.dispute(id, params = {}, opts = {})
9
+ response = client(opts).request(:post, "#{resource_path}/#{id}/disputes", params: params)
10
+ AsaasObject.construct_from(response)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class Checkout < Base
6
+ def self.resource_path = "/checkouts"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class CreditCard < Base
6
+ def self.resource_path = "/creditCard"
7
+
8
+ def self.tokenize(params = {}, opts = {})
9
+ response = client(opts).request(
10
+ :post,
11
+ "#{resource_path}/tokenizeCreditCard",
12
+ params: params,
13
+ retryable: opts.fetch(:retryable, false),
14
+ timeout: opts[:timeout]
15
+ )
16
+ AsaasObject.construct_from(response)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class Customer < Base
6
+ def self.resource_path = "/customers"
7
+
8
+ def self.restore(id, opts = {})
9
+ response = client(opts).request(:post, "#{resource_path}/#{id}/restore")
10
+ AsaasObject.construct_from(response)
11
+ end
12
+
13
+ def self.notifications(id, params = {}, opts = {})
14
+ path = "#{resource_path}/#{id}/notifications"
15
+ response = client(opts).request(:get, path, params: params)
16
+ ListObject.construct_from(response, client: client(opts), path: path, params: params)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ # KYC documents for the authenticated (sub)account: /myAccount/documents.
6
+ # All calls act on the account that owns the api_key, so pass the
7
+ # subaccount's own key per call: Document.pending(api_key: "aact_sub_key").
8
+ class Document
9
+ extend HasClient
10
+
11
+ def self.resource_path = "/myAccount/documents"
12
+
13
+ # Lists pending document groups (each with its onboardingUrl and any
14
+ # files already sent). Returns an AsaasObject with a `data` array.
15
+ def self.pending(opts = {})
16
+ response = client(opts).request(:get, resource_path)
17
+ AsaasObject.construct_from(response)
18
+ end
19
+
20
+ # Uploads a file to a document group (multipart). `file` is any IO that
21
+ # responds to #read; `type` is the Asaas document type (e.g. "IDENTIFICATION").
22
+ def self.send_document(group_id, file:, type:, **opts)
23
+ params = { documentFile: file, type: type }
24
+ response = client(opts).request(:post, "#{resource_path}/#{group_id}", params: params)
25
+ AsaasObject.construct_from(response)
26
+ end
27
+
28
+ # Removes a previously sent file by its file id.
29
+ def self.delete_file(file_id, opts = {})
30
+ response = client(opts).request(:delete, "#{resource_path}/files/#{file_id}")
31
+ AsaasObject.construct_from(response)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class Dunning < Base
6
+ def self.resource_path = "/paymentDunnings"
7
+
8
+ def self.resend_documents(id, opts = {})
9
+ response = client(opts).request(:post, "#{resource_path}/#{id}/documents")
10
+ AsaasObject.construct_from(response)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class Finance
6
+ extend HasClient
7
+
8
+ def self.balance(opts = {})
9
+ response = client(opts).request(:get, "/finance/balance")
10
+ AsaasObject.construct_from(response)
11
+ end
12
+
13
+ def self.statistics(params = {}, opts = {})
14
+ response = client(opts).request(:get, "/finance/payment/statistics", params: params)
15
+ AsaasObject.construct_from(response)
16
+ end
17
+
18
+ def self.extract(params = {}, opts = {})
19
+ path = "/financialTransactions"
20
+ response = client(opts).request(:get, path, params: params)
21
+ ListObject.construct_from(response, client: client(opts), path: path, params: params)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ module HasClient
6
+ def self.extended(base)
7
+ base.private_class_method(:client)
8
+ end
9
+
10
+ def client(opts = {})
11
+ Client.new(api_key: opts[:api_key])
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ module Resources
5
+ class Installment < Base
6
+ def self.resource_path = "/installments"
7
+
8
+ def self.update_splits(id, params = {}, opts = {})
9
+ response = client(opts).request(:put, "#{resource_path}/#{id}/splits", params: params)
10
+ AsaasObject.construct_from(response)
11
+ end
12
+ end
13
+ end
14
+ end