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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d420f112f43923771b7d6df6feb5eb99f42774d8903338614995bdc7be3af2f1
4
+ data.tar.gz: 7dd8d9109d82e5daa3455775711f974078a758ca0273d13f241511f07b8a474c
5
+ SHA512:
6
+ metadata.gz: 47faba89b760d0dd6bd587b04be2cd0919f3f220377a142abc34e5f6465803a9392f949362f71a5df930f3953ff3366def7b2d119de302fa9853d5d54c018560
7
+ data.tar.gz: 39e16dc9fc2d333f992223bc3e9970f0b06d8243abe1987a87bedb1035a286153d0ec14054e5d0e65ba39f40f722b46f1e3e3e6ca4b47e1f5823758731325c88
data/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.0] - 2026-11-11
11
+
12
+ First public release, published as `asaas-sdk`. The Ruby namespace is `Asaas`;
13
+ both `require "asaas-sdk"` and `require "asaas"` load the SDK.
14
+
15
+ ### Added
16
+ - `AsaasObject` with dot-access and recursive hash conversion
17
+ - `ListObject` with `auto_paging_each` for transparent pagination
18
+ - `Resources::Base` with generic CRUD (`create`, `retrieve`, `update`, `delete`, `list`)
19
+ - `HasClient` module for shared HTTP client access
20
+ - Resources: Customer, Payment, Subscription, Webhook
21
+ - Resources: Finance (`balance`, `statistics`, `extract`), Pix, PaymentLink, Transfer, Installment
22
+ - Resources: Checkout, Invoice, Split, Anticipation, Notification
23
+ - Resources: Dunning, Chargeback, Subaccount, BillPayment, PixAutomatic
24
+ - Resources: Document (KYC `/myAccount/documents` — `pending`, `send_document`, `delete_file`), MyAccount (`status`)
25
+ - Per-call options (`api_key`, retry, timeout, `idempotency_key`), including on `Payment.refund`
26
+ - Error hierarchy with HTTP status mapping (`AuthenticationError`, `NotFoundError`, `RateLimitError`, `ServerError`, `ConnectionError`)
27
+ - Automatic retry with exponential backoff on 5xx and network errors
28
+ - Idempotency key on mutating requests (POST, PUT, PATCH)
29
+ - Optional logger support
30
+
31
+ [Unreleased]: https://github.com/JoaoPierre/asaas-ruby/compare/v0.1.0...HEAD
32
+ [0.1.0]: https://github.com/JoaoPierre/asaas-ruby/releases/tag/v0.1.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 JoaoPierre
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,435 @@
1
+ # asaas-sdk
2
+
3
+ Ruby SDK for the [Asaas](https://www.asaas.com) payment gateway API.
4
+
5
+ ## Installation
6
+
7
+ Add to your Gemfile:
8
+
9
+ ```ruby
10
+ gem "asaas-sdk"
11
+ ```
12
+
13
+ Or install directly:
14
+
15
+ ```bash
16
+ gem install asaas-sdk
17
+ ```
18
+
19
+ Until the first release is on RubyGems, install from GitHub:
20
+
21
+ ```ruby
22
+ gem "asaas-sdk", github: "JoaoPierre/asaas-ruby"
23
+ ```
24
+
25
+ The gem is published as `asaas-sdk`, but the namespace is `Asaas`
26
+ (`Asaas.configure`, `Asaas::Resources::Payment`, ...).
27
+
28
+ ## Configuration
29
+
30
+ ```ruby
31
+ Asaas.configure do |c|
32
+ c.api_key = "your_api_key"
33
+ c.sandbox = true # false in production
34
+ end
35
+ ```
36
+
37
+ | Option | Default | Description |
38
+ |---|---|---|
39
+ | `api_key` | `nil` | Your Asaas API key |
40
+ | `sandbox` | `false` | Use sandbox environment |
41
+ | `timeout` | `30` | HTTP timeout in seconds |
42
+ | `max_retries` | `2` | Retries on 5xx / network errors |
43
+ | `retry_delay` | `0.5` | Base delay in seconds (exponential backoff) |
44
+ | `logger` | `nil` | Any Logger-compatible object |
45
+
46
+ ### Per-call API key
47
+
48
+ Every resource method accepts a final positional options hash that overrides the global `api_key` — useful for multi-tenant apps and subaccount keys. When passing options, the `params` hash must be explicit.
49
+
50
+ ```ruby
51
+ Asaas::Resources::Customer.retrieve("cus_123", api_key: "aact_subaccount_key")
52
+ Asaas::Resources::Customer.list({ name: "João" }, api_key: "aact_subaccount_key")
53
+ Asaas::Resources::Payment.create(
54
+ { customer: "cus_123", value: 10.0, billingType: "PIX", dueDate: "2025-12-31" },
55
+ api_key: "aact_subaccount_key"
56
+ )
57
+ Asaas::Resources::Finance.balance(api_key: "aact_subaccount_key")
58
+ ```
59
+
60
+ ### Stable idempotency keys for creates
61
+
62
+ `Base.create` resources accept `idempotency_key:` in the final options hash.
63
+ It is sent only as the `Idempotency-Key` HTTP header, and the same value is
64
+ preserved if the request is retried. The key must be a non-blank `String`.
65
+
66
+ ```ruby
67
+ Asaas::Resources::Customer.create(
68
+ { name: "João Silva", email: "joao@example.com" },
69
+ idempotency_key: "scoby-customer-42"
70
+ )
71
+ ```
72
+
73
+ `ListObject` pages returned with an override keep that override when paginated:
74
+
75
+ ```ruby
76
+ list = Asaas::Resources::Customer.list({ limit: 50 }, api_key: "aact_subaccount_key")
77
+ list.next_page # also uses aact_subaccount_key
78
+ ```
79
+
80
+ For direct `Client` usage:
81
+
82
+ ```ruby
83
+ client = Asaas::Client.new(api_key: "aact_subaccount_key")
84
+ client.request(:get, "/customers")
85
+ ```
86
+
87
+ ## Usage
88
+
89
+ All resources return `AsaasObject` instances with dot-access to attributes. List endpoints return `ListObject`, which supports pagination.
90
+
91
+ ### Customers
92
+
93
+ ```ruby
94
+ customer = Asaas::Customer.create(name: "João Silva", cpfCnpj: "000.000.000-00", email: "joao@example.com")
95
+ customer.id # => "cus_..."
96
+ customer.name # => "João Silva"
97
+
98
+ Asaas::Customer.retrieve("cus_123")
99
+ Asaas::Customer.update("cus_123", name: "João Atualizado")
100
+ Asaas::Customer.delete("cus_123")
101
+ Asaas::Customer.restore("cus_123")
102
+ Asaas::Customer.list(name: "João")
103
+ Asaas::Customer.notifications("cus_123")
104
+ ```
105
+
106
+ ### Payments
107
+
108
+ ```ruby
109
+ payment = Asaas::Payment.create(
110
+ customer: "cus_123",
111
+ billingType: "PIX",
112
+ value: 150.0,
113
+ dueDate: "2025-12-31"
114
+ )
115
+
116
+ Asaas::Payment.retrieve("pay_123")
117
+ Asaas::Payment.update("pay_123", value: 200.0)
118
+ Asaas::Payment.delete("pay_123")
119
+ Asaas::Payment.restore("pay_123")
120
+ Asaas::Payment.list(status: "PENDING")
121
+ Asaas::Payment.refund("pay_123", value: 150.0)
122
+ Asaas::Payment.capture("pay_123")
123
+ Asaas::Payment.confirm_cash_receipt("pay_123", paymentDate: "2025-01-15")
124
+ Asaas::Payment.payment_info("pay_123")
125
+ ```
126
+
127
+ `Payment.refund(id, params, opts)` also accepts per-call `retryable`, `timeout`
128
+ and `idempotency_key` options, alongside `api_key`. Existing calls keep the
129
+ configured retry policy by default. For a financial operation that must not be
130
+ automatically repeated after an ambiguous response, set `retryable: false` and
131
+ reconcile the payment before any new refund submission. Passing an idempotency
132
+ key forwards the header; it does not by itself prove provider-side deduplication.
133
+
134
+ ### Subscriptions
135
+
136
+ ```ruby
137
+ subscription = Asaas::Subscription.create(
138
+ customer: "cus_123",
139
+ billingType: "PIX",
140
+ value: 49.90,
141
+ cycle: "MONTHLY",
142
+ nextDueDate: "2025-02-01"
143
+ )
144
+
145
+ Asaas::Subscription.retrieve("sub_123")
146
+ Asaas::Subscription.update("sub_123", value: 59.90)
147
+ Asaas::Subscription.delete("sub_123")
148
+ Asaas::Subscription.list
149
+ Asaas::Subscription.payments("sub_123")
150
+ ```
151
+
152
+ ### Pix
153
+
154
+ ```ruby
155
+ Asaas::Pix.create_key(type: "EMAIL", key: "joao@example.com")
156
+ Asaas::Pix.list_keys
157
+ Asaas::Pix.delete_key("key_123")
158
+
159
+ qr = Asaas::Pix.create_qr_code(addressKey: "joao@example.com", value: 50.0)
160
+ qr.payload # => "00020101..."
161
+
162
+ Asaas::Pix.transactions(type: "CREDIT")
163
+ Asaas::Pix.decode_qr_code(payload: "00020101...")
164
+ ```
165
+
166
+ ### Finance
167
+
168
+ ```ruby
169
+ balance = Asaas::Finance.balance
170
+ balance.balance # => 1500.0
171
+
172
+ Asaas::Finance.statistics(billingType: "PIX")
173
+
174
+ # Account statement (GET /financialTransactions), paginated
175
+ Asaas::Finance.extract(startDate: "2026-11-01", finishDate: "2026-11-30", order: "desc")
176
+ ```
177
+
178
+ ### Payment Links
179
+
180
+ ```ruby
181
+ link = Asaas::PaymentLink.create(name: "Produto X", value: 99.90, billingType: "UNDEFINED")
182
+ Asaas::PaymentLink.retrieve("lnk_123")
183
+ Asaas::PaymentLink.update("lnk_123", value: 149.90)
184
+ Asaas::PaymentLink.delete("lnk_123")
185
+ Asaas::PaymentLink.list
186
+ Asaas::PaymentLink.add_image("lnk_123", image: "base64data")
187
+ ```
188
+
189
+ ### Transfers
190
+
191
+ ```ruby
192
+ Asaas::Transfer.create(value: 500.0, walletId: "wal_abc")
193
+ Asaas::Transfer.retrieve("tra_123")
194
+ Asaas::Transfer.list
195
+ Asaas::Transfer.delete("tra_123") # cancel
196
+ ```
197
+
198
+ ### Installments
199
+
200
+ ```ruby
201
+ Asaas::Installment.create(customer: "cus_123", value: 1200.0, installmentCount: 12, billingType: "CREDIT_CARD")
202
+ Asaas::Installment.retrieve("ins_123")
203
+ Asaas::Installment.list
204
+ Asaas::Installment.delete("ins_123")
205
+ Asaas::Installment.update_splits("ins_123", splits: [{ walletId: "wal_1", percentualValue: 10.0 }])
206
+ ```
207
+
208
+ ### Webhooks
209
+
210
+ ```ruby
211
+ Asaas::Webhook.create(url: "https://example.com/webhook", email: "dev@example.com")
212
+ Asaas::Webhook.retrieve("web_123")
213
+ Asaas::Webhook.update("web_123", url: "https://example.com/new")
214
+ Asaas::Webhook.delete("web_123")
215
+ Asaas::Webhook.list
216
+ Asaas::Webhook.remove_penalty("web_123")
217
+ ```
218
+
219
+ ### Notifications
220
+
221
+ ```ruby
222
+ Asaas::Notification.update("not_123", enabled: false)
223
+ Asaas::Notification.update_batch(notifications: [{ id: "not_123", enabled: true }])
224
+ ```
225
+
226
+ ### Invoices
227
+
228
+ ```ruby
229
+ Asaas::Invoice.create(payment: "pay_123", serviceDescription: "Consultoria")
230
+ Asaas::Invoice.retrieve("inv_123")
231
+ Asaas::Invoice.update("inv_123", serviceDescription: "Desenvolvimento")
232
+ Asaas::Invoice.delete("inv_123")
233
+ Asaas::Invoice.list
234
+ ```
235
+
236
+ ### Anticipations
237
+
238
+ ```ruby
239
+ Asaas::Anticipation.simulate(payment: "pay_123")
240
+ Asaas::Anticipation.create(payment: "pay_123")
241
+ Asaas::Anticipation.retrieve("ant_123")
242
+ Asaas::Anticipation.list
243
+ Asaas::Anticipation.delete("ant_123") # cancel
244
+ ```
245
+
246
+ ### Splits
247
+
248
+ ```ruby
249
+ Asaas::Split.paid
250
+ Asaas::Split.received
251
+ Asaas::Split.retrieve_paid("spl_123")
252
+ Asaas::Split.retrieve_received("spl_123")
253
+ ```
254
+
255
+ ### Checkouts
256
+
257
+ ```ruby
258
+ Asaas::Checkout.create(billingType: "UNDEFINED", totalValue: 150.0)
259
+ Asaas::Checkout.retrieve("chk_123")
260
+ Asaas::Checkout.delete("chk_123") # cancel
261
+ ```
262
+
263
+ ### Dunning
264
+
265
+ ```ruby
266
+ Asaas::Dunning.create(payment: "pay_123", type: "CREDIT_BUREAU")
267
+ Asaas::Dunning.retrieve("dun_123")
268
+ Asaas::Dunning.list
269
+ Asaas::Dunning.delete("dun_123") # cancel
270
+ Asaas::Dunning.resend_documents("dun_123")
271
+ ```
272
+
273
+ ### Chargebacks
274
+
275
+ ```ruby
276
+ Asaas::Chargeback.list
277
+ Asaas::Chargeback.retrieve("cbk_123")
278
+ Asaas::Chargeback.dispute("cbk_123", description: "Item entregue conforme pedido")
279
+ ```
280
+
281
+ ### Subaccounts
282
+
283
+ ```ruby
284
+ Asaas::Subaccount.create(name: "Loja Parceira", email: "loja@example.com", cpfCnpj: "000.000.000-00")
285
+ Asaas::Subaccount.retrieve("sub_123")
286
+ Asaas::Subaccount.list
287
+ Asaas::Subaccount.create_api_key("sub_123")
288
+ Asaas::Subaccount.api_keys("sub_123")
289
+ ```
290
+
291
+ ### Account Status and Commercial Info
292
+
293
+ Authoritative KYC registration status for the authenticated account. Acts on
294
+ `/myAccount`, so pass the subaccount's own key per call:
295
+
296
+ ```ruby
297
+ status = Asaas::MyAccount.status(api_key: "aact_subaccount_key")
298
+ status.commercialInfo # => "APPROVED"
299
+ status.documentation # => "AWAITING_APPROVAL"
300
+ status.bankAccountInfo # => "PENDING"
301
+ status.general # => "PENDING"
302
+
303
+ commercial_info = Asaas::MyAccount.commercial_info(api_key: "aact_subaccount_key")
304
+ commercial_info.incomeValue
305
+
306
+ # POST replaces the full applicable profile; include every applicable field.
307
+ profile = {
308
+ personType: "JURIDICA",
309
+ cpfCnpj: "35381637000150",
310
+ companyType: "LIMITED",
311
+ companyName: "Loja Parceira Ltda",
312
+ incomeValue: 50_000.0,
313
+ email: "financeiro@lojapareceira.example",
314
+ mobilePhone: "11999999999",
315
+ postalCode: "01310100",
316
+ address: "Avenida Paulista",
317
+ addressNumber: "1000",
318
+ province: "Bela Vista",
319
+ site: "https://lojapareceira.example"
320
+ }
321
+ Asaas::MyAccount.update_commercial_info(profile, api_key: "aact_subaccount_key")
322
+
323
+ # Disable automatic retries for this POST if needed
324
+ Asaas::MyAccount.update_commercial_info(
325
+ profile,
326
+ api_key: "aact_subaccount_key",
327
+ retryable: false
328
+ )
329
+ ```
330
+
331
+ `incomeValue` is required when resubmitting commercial info. These methods act
332
+ on the authenticated account, so use that subaccount's API key.
333
+
334
+ ### Documents (KYC)
335
+
336
+ KYC documents for the authenticated account (`/myAccount/documents`). Pass the
337
+ subaccount's own key per call:
338
+
339
+ ```ruby
340
+ # List pending document groups (each with its onboardingUrl and sent files)
341
+ groups = Asaas::Document.pending(api_key: "aact_subaccount_key")
342
+ group = groups.data.first
343
+ group.onboardingUrl
344
+
345
+ # Upload a file to a group (multipart). `file` is any IO that responds to #read.
346
+ Asaas::Document.send_document(
347
+ group.id,
348
+ file: File.open("identity.png"),
349
+ type: "IDENTIFICATION",
350
+ api_key: "aact_subaccount_key"
351
+ )
352
+
353
+ # Remove a previously sent file
354
+ Asaas::Document.delete_file("file_123", api_key: "aact_subaccount_key")
355
+ ```
356
+
357
+ > Asaas recommends waiting ~15s after subaccount creation before listing
358
+ > documents, so Receita Federal validation can finish.
359
+
360
+ ### Bill Payments
361
+
362
+ ```ruby
363
+ Asaas::BillPayment.create(identificationField: "123456789", value: 150.0)
364
+ Asaas::BillPayment.retrieve("bil_123")
365
+ Asaas::BillPayment.list
366
+ Asaas::BillPayment.delete("bil_123") # cancel
367
+ ```
368
+
369
+ ### Pix Automatic
370
+
371
+ ```ruby
372
+ Asaas::PixAutomatic.create(customer: "cus_123", value: 99.90)
373
+ Asaas::PixAutomatic.list
374
+ Asaas::PixAutomatic.delete("aut_123") # cancel
375
+ ```
376
+
377
+ ## Pagination
378
+
379
+ List endpoints return a `ListObject`. You can iterate the current page or all pages automatically:
380
+
381
+ ```ruby
382
+ # Current page only
383
+ payments = Asaas::Payment.list(limit: 10)
384
+ payments.each { |p| puts p.status }
385
+ payments.total_count # => 312
386
+ payments.has_more # => true
387
+
388
+ # All pages — fetches next automatically
389
+ Asaas::Payment.list.auto_paging_each do |payment|
390
+ puts payment.id
391
+ end
392
+
393
+ # As an Enumerator (lazy)
394
+ Asaas::Payment.list.auto_paging_each.lazy.first(50)
395
+ ```
396
+
397
+ ## Error Handling
398
+
399
+ ```ruby
400
+ begin
401
+ Asaas::Customer.retrieve("invalid_id")
402
+ rescue Asaas::NotFoundError => e
403
+ puts e.message
404
+ puts e.http_status # => 404
405
+ puts e.request_id
406
+ rescue Asaas::AuthenticationError
407
+ puts "Invalid API key"
408
+ rescue Asaas::RateLimitError
409
+ puts "Too many requests"
410
+ rescue Asaas::AsaasError => e
411
+ puts "Unexpected error: #{e.message}"
412
+ end
413
+ ```
414
+
415
+ | Exception | HTTP Status |
416
+ |---|---|
417
+ | `AuthenticationError` | 401 |
418
+ | `PermissionError` | 403 |
419
+ | `NotFoundError` | 404 |
420
+ | `RateLimitError` | 429 |
421
+ | `ServerError` | 5xx |
422
+ | `ConnectionError` | network failure |
423
+
424
+ ## Development
425
+
426
+ ```bash
427
+ bundle install
428
+ bundle exec rspec # run tests
429
+ bundle exec rubocop # lint
430
+ bundle exec rake # tests + lint
431
+ ```
432
+
433
+ ## License
434
+
435
+ MIT License. See [LICENSE.txt](LICENSE.txt).
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Asaas
4
+ class AsaasObject
5
+ def initialize(attrs = {})
6
+ @attrs = {}
7
+ attrs.to_h.each { |k, v| @attrs[k.to_s] = convert(v) }
8
+ end
9
+
10
+ def [](key)
11
+ @attrs[key.to_s]
12
+ end
13
+
14
+ def []=(key, value)
15
+ @attrs[key.to_s] = value
16
+ end
17
+
18
+ def to_h
19
+ serialize(@attrs)
20
+ end
21
+
22
+ def respond_to_missing?(name, include_private = false)
23
+ @attrs.key?(name.to_s.delete_suffix("?")) || super
24
+ end
25
+
26
+ def method_missing(name, *args)
27
+ key = name.to_s
28
+ if key.end_with?("?")
29
+ !!@attrs[key.delete_suffix("?")]
30
+ elsif @attrs.key?(key)
31
+ @attrs[key]
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ def ==(other)
38
+ case other
39
+ when AsaasObject then to_h == other.to_h
40
+ when Hash then to_h == other.transform_keys(&:to_s)
41
+ else false
42
+ end
43
+ end
44
+
45
+ def inspect
46
+ "#<#{self.class} #{@attrs.inspect}>"
47
+ end
48
+
49
+ def self.construct_from(attrs)
50
+ new(attrs)
51
+ end
52
+
53
+ private
54
+
55
+ def serialize(value)
56
+ case value
57
+ when AsaasObject
58
+ value.to_h
59
+ when Array
60
+ value.map { serialize(_1) }
61
+ when Hash
62
+ value.transform_values { serialize(_1) }
63
+ else
64
+ value
65
+ end
66
+ end
67
+
68
+ def convert(value)
69
+ case value
70
+ when Hash then self.class.new(value)
71
+ when Array then value.map { convert(_1) }
72
+ else value
73
+ end
74
+ end
75
+ end
76
+ end