dinie-sdk-sandbox 1.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 (114) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +40 -0
  3. data/LICENSE +21 -0
  4. data/README.md +280 -0
  5. data/lib/dinie/generated/api_version.rb +8 -0
  6. data/lib/dinie/generated/client.rb +96 -0
  7. data/lib/dinie/generated/errors/registry.rb +40 -0
  8. data/lib/dinie/generated/events/base.rb +11 -0
  9. data/lib/dinie/generated/events/credit_offer.rb +56 -0
  10. data/lib/dinie/generated/events/customer_created.rb +42 -0
  11. data/lib/dinie/generated/events/customer_denied.rb +39 -0
  12. data/lib/dinie/generated/events/customer_kyc_updated.rb +36 -0
  13. data/lib/dinie/generated/events/customer_status.rb +48 -0
  14. data/lib/dinie/generated/events/deserializers.rb +35 -0
  15. data/lib/dinie/generated/events/loan_active.rb +35 -0
  16. data/lib/dinie/generated/events/loan_created.rb +38 -0
  17. data/lib/dinie/generated/events/loan_payment_received.rb +46 -0
  18. data/lib/dinie/generated/events/loan_processing.rb +37 -0
  19. data/lib/dinie/generated/events/loan_signature_received.rb +48 -0
  20. data/lib/dinie/generated/events/loan_status.rb +73 -0
  21. data/lib/dinie/generated/events.rb +4 -0
  22. data/lib/dinie/generated/resources/banks.rb +25 -0
  23. data/lib/dinie/generated/resources/biometrics.rb +27 -0
  24. data/lib/dinie/generated/resources/credentials.rb +56 -0
  25. data/lib/dinie/generated/resources/credit_offers.rb +59 -0
  26. data/lib/dinie/generated/resources/customers.rb +200 -0
  27. data/lib/dinie/generated/resources/loans.rb +70 -0
  28. data/lib/dinie/generated/resources/webhook_endpoints.rb +97 -0
  29. data/lib/dinie/generated/resources.rb +9 -0
  30. data/lib/dinie/generated/types/bank.rb +17 -0
  31. data/lib/dinie/generated/types/biometrics_session.rb +16 -0
  32. data/lib/dinie/generated/types/biometrics_session_exchange_response.rb +23 -0
  33. data/lib/dinie/generated/types/credential.rb +52 -0
  34. data/lib/dinie/generated/types/credit_offer.rb +62 -0
  35. data/lib/dinie/generated/types/customer.rb +46 -0
  36. data/lib/dinie/generated/types/customer_bank_account.rb +33 -0
  37. data/lib/dinie/generated/types/ids.rb +18 -0
  38. data/lib/dinie/generated/types/kyc.rb +458 -0
  39. data/lib/dinie/generated/types/kyc_attachment_response.rb +16 -0
  40. data/lib/dinie/generated/types/loan.rb +51 -0
  41. data/lib/dinie/generated/types/money.rb +4 -0
  42. data/lib/dinie/generated/types/simulation.rb +35 -0
  43. data/lib/dinie/generated/types/transaction.rb +43 -0
  44. data/lib/dinie/generated/types/webhook_endpoint.rb +52 -0
  45. data/lib/dinie/generated/types/webhook_secret_rotation.rb +17 -0
  46. data/lib/dinie/generated/types.rb +18 -0
  47. data/lib/dinie/runtime/errors.rb +295 -0
  48. data/lib/dinie/runtime/http.rb +327 -0
  49. data/lib/dinie/runtime/idempotency.rb +34 -0
  50. data/lib/dinie/runtime/logger.rb +326 -0
  51. data/lib/dinie/runtime/model.rb +162 -0
  52. data/lib/dinie/runtime/multipart.rb +77 -0
  53. data/lib/dinie/runtime/paginator.rb +164 -0
  54. data/lib/dinie/runtime/rate_limit.rb +150 -0
  55. data/lib/dinie/runtime/request_options.rb +112 -0
  56. data/lib/dinie/runtime/retry.rb +74 -0
  57. data/lib/dinie/runtime/token_manager.rb +341 -0
  58. data/lib/dinie/runtime/webhooks.rb +194 -0
  59. data/lib/dinie/version.rb +7 -0
  60. data/lib/dinie.rb +37 -0
  61. data/sig/_external/faraday.rbs +44 -0
  62. data/sig/dinie/generated/client.rbs +45 -0
  63. data/sig/dinie/generated/errors/registry.rbs +40 -0
  64. data/sig/dinie/generated/events/base.rbs +17 -0
  65. data/sig/dinie/generated/events/credit_offer.rbs +33 -0
  66. data/sig/dinie/generated/events/customer_created.rbs +27 -0
  67. data/sig/dinie/generated/events/customer_denied.rbs +25 -0
  68. data/sig/dinie/generated/events/customer_kyc_updated.rbs +21 -0
  69. data/sig/dinie/generated/events/customer_status.rbs +26 -0
  70. data/sig/dinie/generated/events/deserializers.rbs +9 -0
  71. data/sig/dinie/generated/events/loan_active.rbs +20 -0
  72. data/sig/dinie/generated/events/loan_created.rbs +23 -0
  73. data/sig/dinie/generated/events/loan_payment_received.rbs +28 -0
  74. data/sig/dinie/generated/events/loan_processing.rbs +23 -0
  75. data/sig/dinie/generated/events/loan_signature_received.rbs +30 -0
  76. data/sig/dinie/generated/events/loan_status.rbs +40 -0
  77. data/sig/dinie/generated/resources/banks.rbs +15 -0
  78. data/sig/dinie/generated/resources/credentials.rbs +21 -0
  79. data/sig/dinie/generated/resources/credit_offers.rbs +19 -0
  80. data/sig/dinie/generated/resources/customers.rbs +58 -0
  81. data/sig/dinie/generated/resources/loans.rbs +26 -0
  82. data/sig/dinie/generated/resources/webhook_endpoints.rbs +35 -0
  83. data/sig/dinie/generated/types/bank.rbs +12 -0
  84. data/sig/dinie/generated/types/biometrics_session.rbs +11 -0
  85. data/sig/dinie/generated/types/credential.rbs +26 -0
  86. data/sig/dinie/generated/types/credit_offer.rbs +24 -0
  87. data/sig/dinie/generated/types/customer.rbs +25 -0
  88. data/sig/dinie/generated/types/customer_bank_account.rbs +26 -0
  89. data/sig/dinie/generated/types/enums.rbs +66 -0
  90. data/sig/dinie/generated/types/ids.rbs +21 -0
  91. data/sig/dinie/generated/types/kyc/attachment.rbs +14 -0
  92. data/sig/dinie/generated/types/kyc/common.rbs +42 -0
  93. data/sig/dinie/generated/types/kyc/requirements.rbs +117 -0
  94. data/sig/dinie/generated/types/kyc/submitted.rbs +21 -0
  95. data/sig/dinie/generated/types/kyc/uploads.rbs +24 -0
  96. data/sig/dinie/generated/types/loan.rbs +32 -0
  97. data/sig/dinie/generated/types/money.rbs +6 -0
  98. data/sig/dinie/generated/types/simulation.rbs +28 -0
  99. data/sig/dinie/generated/types/transaction.rbs +24 -0
  100. data/sig/dinie/generated/types/webhook_endpoint.rbs +38 -0
  101. data/sig/dinie/runtime/errors.rbs +106 -0
  102. data/sig/dinie/runtime/http.rbs +59 -0
  103. data/sig/dinie/runtime/idempotency.rbs +15 -0
  104. data/sig/dinie/runtime/logger.rbs +89 -0
  105. data/sig/dinie/runtime/model.rbs +51 -0
  106. data/sig/dinie/runtime/multipart.rbs +25 -0
  107. data/sig/dinie/runtime/paginator.rbs +50 -0
  108. data/sig/dinie/runtime/rate_limit.rbs +46 -0
  109. data/sig/dinie/runtime/request_options.rbs +35 -0
  110. data/sig/dinie/runtime/retry.rbs +29 -0
  111. data/sig/dinie/runtime/token_manager.rbs +51 -0
  112. data/sig/dinie/runtime/webhooks.rbs +31 -0
  113. data/sig/dinie/version.rbs +7 -0
  114. metadata +316 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2a373cf8978cc140407d0d105f1c2028474ed59774e29b425c8f32428f8a4beb
4
+ data.tar.gz: ae691f9d8933e27c5a9c197564226372d6169c571d4460477ffd689a1160dab2
5
+ SHA512:
6
+ metadata.gz: 81da4d452ff69a12f68f0054d5d681018c47871a3a35427fdc9d2d4ef50fb6c16e97ba93a38e053486671748c116cce0d3e3bd79dc57e135c940aa4c28c2d98e
7
+ data.tar.gz: f75696efcbb3c2d616ee42d4be9d863ad317c4e2acfe1b415c702a79f6fc14946ffdd97b930a22cefa9ccbaa0165e12b785e948f63b2cf6949011a1002502bd4
data/CHANGELOG.md ADDED
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+
3
+ All notable changes to the `dinie` gem are 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
+ ## [1.1.0] - 2026-06-09
11
+
12
+ ## [1.0.0] — 2026-06-08
13
+
14
+ ### Added
15
+
16
+ - **Publishable gem** (`dinie-sdk-sandbox` on RubyGems.org): `allowed_push_host` wired;
17
+ `api_version` gemspec metadata (`2026-03-01`); `rubygems_mfa_required` satisfied by OIDC
18
+ Trusted Publishing.
19
+ - **`Dinie::VERSION = "1.0.0"`** — first stable public release.
20
+ - **api_version constant** (`lib/dinie/generated/api_version.rb`): `Dinie::Generated::API_VERSION`
21
+ sourced from `openapi.info.version`; User-Agent now reads it (no more `2026-05-10` literal).
22
+ - **YARD documentation** on every public method and class in `lib/dinie/generated/` (0 undocumented
23
+ methods — was 128/206 undocumented).
24
+ - **CI/CD workflows**: `publish.yml` (OIDC RubyGems via `rubygems/release-gem@v1`, keyless,
25
+ no `RUBYGEMS_API_KEY`); `tag-release.yml` (GitHub App token auto-tag on `generator-bump` PR merge).
26
+ - **Drift gate** in CI: hermetic `sdk-generator check --target ruby` — edit ⇒ red, revert ⇒ green.
27
+
28
+ ### Added
29
+
30
+ - Project scaffold: `dinie.gemspec` (private gem, Ruby >= 3.1; `faraday ~> 2.0` +
31
+ `net-http-persistent ~> 4.0`), `Gemfile`, `Rakefile`.
32
+ - `lib/dinie.rb` deterministic barrel (explicit `require_relative`, no Zeitwerk) and
33
+ `lib/dinie/version.rb` (`Dinie::VERSION`).
34
+ - `lib/dinie/runtime/` <-> `lib/dinie/generated/` directory boundary with `CODEOWNERS`
35
+ (human-owned runtime, bot-owned generated layer — architecture §4.2).
36
+ - RSpec + WebMock (zero network) test tree with a `Dinie::VERSION` smoke spec.
37
+ - RuboCop config, RBS `sig/` skeleton + `Steepfile`, YARD `.yardopts`.
38
+ - CI workflow: `bundle install` -> RuboCop -> RSpec -> Steep (informative) on Ruby
39
+ 3.1, 3.2, 3.3.
40
+ - `.env.example` for the deferred (non-gate) live smoke E2E.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dinie
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # dinie
2
+
3
+ Official Ruby SDK for the [Dinie](https://dinie.com.br) V3 API (backend-only).
4
+
5
+ A hand-written, synchronous Ruby client for the Dinie credit-as-a-service platform: OAuth2
6
+ client-credentials auth (handled for you), automatic retries with idempotency, cursor pagination,
7
+ typed errors, and webhook verification. Snake_case throughout — the wire and the Ruby surface match,
8
+ so there is no casing to translate.
9
+
10
+ > **Status — `1.0.0`, published to RubyGems.** The public surface is frozen against the Dinie V3
11
+ > contract (api-version `2026-03-01`).
12
+
13
+ ## Requirements
14
+
15
+ - Ruby >= 3.1
16
+ - A Dinie API credential (`client_id` + `client_secret`)
17
+
18
+ ## Installation
19
+
20
+ ```ruby
21
+ # Gemfile
22
+ gem "dinie-sdk-sandbox"
23
+ ```
24
+
25
+ ```bash
26
+ bundle install
27
+ ```
28
+
29
+ The SDK depends on `faraday` (with the `net-http-persistent` adapter for a real connection pool)
30
+ and `faraday-multipart`. Both are pulled in automatically.
31
+
32
+ ## Quickstart
33
+
34
+ ```ruby
35
+ require "dinie"
36
+
37
+ client = Dinie::Client.new(client_id: "dinie_ci_…", client_secret: "…")
38
+
39
+ customer = client.customers.create(
40
+ email: "ana@example.com",
41
+ phone: "+5511999999999",
42
+ cpf: "123.456.789-09",
43
+ cnpj: "12.345.678/0001-95"
44
+ )
45
+
46
+ customer.id # => "cust_…"
47
+ customer.status # => "pending_kyc"
48
+ ```
49
+
50
+ The client owns a connection pool and an in-memory OAuth2 token cache, so **construct it once and
51
+ reuse it**. Tokens are fetched and refreshed transparently — you never call the token endpoint
52
+ yourself.
53
+
54
+ ### Configuration
55
+
56
+ Pass options to the constructor, or let the SDK read them from the environment.
57
+
58
+ ```ruby
59
+ client = Dinie::Client.new(
60
+ client_id: "dinie_ci_…", # or ENV["DINIE_CLIENT_ID"]
61
+ client_secret: "…", # or ENV["DINIE_CLIENT_SECRET"]
62
+ base_url: nil, # or ENV["DINIE_BASE_URL"]; default https://api.dinie.com.br/api/v3
63
+ timeout: 30, # per-request timeout, in SECONDS
64
+ max_retries: 3, # retries after the first attempt
65
+ idempotency: true, # auto X-Idempotency-Key on POST/PATCH
66
+ log_level: :off # :off | :error | :warn | :info | :debug (or ENV["DINIE_LOG"])
67
+ )
68
+ ```
69
+
70
+ | Variable | Used for |
71
+ | -------------------- | -------------------------------------------------------------- |
72
+ | `DINIE_CLIENT_ID` | OAuth2 client id (when `client_id:` is omitted) |
73
+ | `DINIE_CLIENT_SECRET`| OAuth2 client secret (when `client_secret:` is omitted) |
74
+ | `DINIE_BASE_URL` | API base URL incl. `/api/v3` (when `base_url:` is omitted) |
75
+ | `DINIE_LOG` | log level (when `log_level:` is omitted) |
76
+
77
+ Need a one-off override (a tighter timeout for a single call path)? Clone the client — the clone
78
+ **shares the same token cache and connection pool**, so it never triggers a re-auth:
79
+
80
+ ```ruby
81
+ fast = client.with_options(timeout: 5)
82
+ ```
83
+
84
+ Logging is opt-in and redacts credentials and PII (`authorization`, `cpf`, `cnpj`, `client_secret`,
85
+ `phone`, …) before anything is written.
86
+
87
+ ## End-to-end flow: Customer → Credit Offer → Loan
88
+
89
+ The credit lifecycle is: register a customer, let Dinie run KYC and underwriting (you are notified by
90
+ webhook — see below), then read the resulting **credit offer**, **simulate** it, and **contract** a
91
+ loan from the accepted simulation.
92
+
93
+ ```ruby
94
+ client = Dinie::Client.new(client_id: "dinie_ci_test", client_secret: "dinie_secret_test")
95
+
96
+ # 1. Register the customer.
97
+ customer = client.customers.create(
98
+ email: "ana@example.com",
99
+ phone: "+5511999999999",
100
+ cpf: "123.456.789-09",
101
+ cnpj: "12.345.678/0001-95"
102
+ )
103
+
104
+ # 2. Dinie runs KYC + underwriting. When the customer is approved, a `credit_offer.available`
105
+ # webhook fires. Read the offers (they are NOT created by you):
106
+ offer = client.customers.credit_offers.list(customer.id).first
107
+ # …or fetch a known offer directly:
108
+ offer = client.credit_offers.retrieve("co_0550e8400e29b41d4a716446655440000")
109
+
110
+ offer.approved_amount # => 25000.0 (Money — a Float, BRL)
111
+ offer.monthly_interest_rate # => 4.5
112
+
113
+ # 3. Simulate the offer for the amount and term the customer wants.
114
+ simulation = client.credit_offers.create_simulation(
115
+ offer.id,
116
+ requested_amount: 25_000.0,
117
+ installment_count: 12
118
+ )
119
+
120
+ simulation.installment_amount # => 2_343.21
121
+ simulation.total_amount # => 28_118.52
122
+
123
+ # 4. Contract the loan from the accepted simulation.
124
+ loan = client.loans.create(
125
+ credit_offer_id: offer.id,
126
+ simulation_id: simulation.id,
127
+ installment_count: simulation.installment_count,
128
+ installment_amount: simulation.installment_amount,
129
+ first_due_date: "2026-07-10" # ISO-8601 date
130
+ )
131
+
132
+ loan.id # => "ln_…"
133
+ loan.status # => "awaiting_signatures"
134
+ loan.signing_url # => CCB signature URL (present while awaiting signatures)
135
+
136
+ # Later, inspect the amortization schedule:
137
+ client.loans.transactions.list(loan.id).each do |installment|
138
+ puts "#{installment.due_date}: #{installment.amount_due} (#{installment.status})"
139
+ end
140
+ ```
141
+
142
+ > **Note.** All monetary fields are `Money` — a plain `Float` in BRL. All timestamps
143
+ > (`created_at`, `valid_until`, …) are integer Unix epoch seconds; only `RateLimit#reset_at` is a
144
+ > `Time`. ID prefixes (`cust_`, `co_`, `sim_`, `ln_`, `tx_`) are documented on each field; the value
145
+ > itself is a `String`.
146
+
147
+ ## Pagination
148
+
149
+ List endpoints return a `Dinie::Page` that auto-paginates by cursor. It does **not** load every page
150
+ eagerly — `#each` walks pages on demand, following `has_more` (never the page size).
151
+
152
+ ```ruby
153
+ # Iterate every customer across all pages:
154
+ client.customers.list(limit: 50).each do |customer|
155
+ puts customer.id
156
+ end
157
+
158
+ # Page-by-page, for manual control:
159
+ client.customers.list.each_page do |page|
160
+ process(page.data) # Array<Dinie::Customer>
161
+ page.has_more # => true / false
162
+ end
163
+
164
+ # Just the first N, fetched lazily (only as many pages as needed):
165
+ top = client.customers.list.first(10)
166
+ ```
167
+
168
+ Without a block, `#each` and `#each_page` return an `Enumerator`, so `.map` / `.lazy` work too.
169
+
170
+ `client.banks.list` is the one exception: the bank directory is **not** paginated, so it returns a
171
+ flat `Array<Dinie::Bank>` in a single call.
172
+
173
+ ## Webhooks
174
+
175
+ Dinie delivers events (Standard Webhooks v1, HMAC-SHA256). `Dinie::Webhooks.extract` verifies the
176
+ signature **and** the timestamp, then returns the typed event — or raises. It never returns an
177
+ unverified payload, and it needs no client (verification is just your signing secret).
178
+
179
+ ```ruby
180
+ # In your Rack / Rails / Sinatra handler:
181
+ event = Dinie::Webhooks.extract(
182
+ headers: request.headers.to_h, # must include webhook-id / webhook-timestamp / webhook-signature
183
+ body: request.raw_post, # the RAW body, before JSON parsing
184
+ secret: ENV["DINIE_WEBHOOK_SECRET"] # "whsec_…" (or an Array of secrets during rotation)
185
+ )
186
+
187
+ case event
188
+ when Dinie::Events::CustomerActive
189
+ activate_customer(event.data.id)
190
+ when Dinie::Events::CreditOfferAvailable
191
+ notify_offer(event.data.customer_id, event.data.approved_amount)
192
+ when Dinie::Events::LoanActive
193
+ release_funds(event.data.id)
194
+ when Dinie::Events::LoanPaymentReceived
195
+ record_payment(event.data.id, event.data.payment.amount)
196
+ else
197
+ Rails.logger.info("Unhandled Dinie event: #{event.type}")
198
+ end
199
+ ```
200
+
201
+ Header lookup is case-insensitive and accepts string or symbol keys. The signed payload is
202
+ `"{webhook-id}.{webhook-timestamp}.{body}"`, the comparison is constant-time, and multiple
203
+ space-separated `v1,<sig>` signatures are accepted (so secret rotation works from either side).
204
+
205
+ Failures raise, so you can map them to HTTP responses:
206
+
207
+ | Raised | When |
208
+ | ------------------------------------- | --------------------------------------------------------------- |
209
+ | `Dinie::WebhookSignatureError` | a header is missing, no secret matched, or the HMAC differs |
210
+ | `Dinie::WebhookTimestampError` | the timestamp is malformed or outside the ±300s window |
211
+ | `Dinie::UnknownWebhookEventError` | the signature is valid but the `type` is not in the SDK's catalog |
212
+
213
+ ## Error handling
214
+
215
+ Every non-2xx response becomes a typed error. The hierarchy lets you rescue broadly or narrowly, and
216
+ each error carries the machine-readable `code` and the `request_id` for support tickets.
217
+
218
+ ```ruby
219
+ begin
220
+ client.credit_offers.retrieve("co_does_not_exist")
221
+ rescue Dinie::NotFoundError => e
222
+ warn "Not found (#{e.code}) — request #{e.request_id}"
223
+ rescue Dinie::ValidationError => e
224
+ warn "Invalid: #{e.detail}" # RFC 9457 Problem Details
225
+ rescue Dinie::RateLimitError
226
+ # transient — the SDK already retried with backoff; back off further if you see this
227
+ rescue Dinie::APIStatusError => e
228
+ warn "API error #{e.status}: #{e.title}"
229
+ rescue Dinie::APIConnectionError
230
+ # network/DNS/timeout — no response was received
231
+ end
232
+ ```
233
+
234
+ ```text
235
+ Dinie::Error
236
+ ├── Dinie::APIError
237
+ │ ├── Dinie::APIConnectionError → Dinie::APITimeoutError
238
+ │ └── Dinie::APIStatusError
239
+ │ ├── Dinie::BadRequestError (400) Dinie::ConflictError (409)
240
+ │ ├── Dinie::AuthError (401) Dinie::ValidationError (422)
241
+ │ ├── Dinie::PermissionError (403) Dinie::RateLimitError (429)
242
+ │ └── Dinie::NotFoundError (404) Dinie::ServerError (500 + ≥500 fallback)
243
+ ├── Dinie::OAuthError # token handshake failed
244
+ ├── Dinie::WebhookSignatureError
245
+ ├── Dinie::WebhookTimestampError
246
+ └── Dinie::UnknownWebhookEventError
247
+ ```
248
+
249
+ Retries are automatic for `408/429/500/502/503/504` and transport errors (exponential backoff with
250
+ jitter, honoring `Retry-After` up to 60s). `409`/`410` are never retried. A stable
251
+ `X-Idempotency-Key` is minted once per logical write and reused across retries, so a retry never
252
+ creates a duplicate resource.
253
+
254
+ After any call you can read the latest rate-limit snapshot:
255
+
256
+ ```ruby
257
+ rl = client.rate_limit # => Dinie::RateLimit or nil
258
+ rl&.remaining # => 87
259
+ rl&.reset_at # => 2026-06-02 12:00:30 UTC (a Time)
260
+ ```
261
+
262
+ ## Types
263
+
264
+ The full public surface is mirrored as RBS signatures under [`sig/`](sig/) — install the gem and
265
+ point [Steep](https://github.com/soutaro/steep) or Solargraph at it for static type checking and
266
+ editor hover. YARD documentation covers every public class and method.
267
+
268
+ ## Development
269
+
270
+ ```bash
271
+ bundle install
272
+ bundle exec rspec # tests — WebMock, zero network
273
+ bundle exec rubocop # lint
274
+ bundle exec steep check # type check (informative in v1 — see Steepfile)
275
+ bundle exec yard doc # build the API docs
276
+ ```
277
+
278
+ ## License
279
+
280
+ [MIT](LICENSE) © Dinie
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ # generated — do not edit
4
+ module Dinie
5
+ module Generated
6
+ API_VERSION = "2026-03-01"
7
+ end
8
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/multipart"
5
+ require "faraday/net_http_persistent"
6
+
7
+ module Dinie
8
+ class Client
9
+ DEFAULT_TIMEOUT_SECONDS = Internal::HttpClient::DEFAULT_TIMEOUT_SECONDS
10
+ DEFAULT_MAX_RETRIES = Internal::HttpClient::DEFAULT_MAX_RETRIES
11
+
12
+ def initialize(client_id: nil, client_secret: nil, code: nil, base_url: nil, timeout: DEFAULT_TIMEOUT_SECONDS,
13
+ max_retries: DEFAULT_MAX_RETRIES, idempotency: true, log_level: :off, logger: nil,
14
+ adapter: nil, token_manager: nil, connection: nil)
15
+ @base_url = first_present(base_url, ENV.fetch("DINIE_BASE_URL", nil)) || Internal::HttpClient::DEFAULT_BASE_URL
16
+ @options = {
17
+ timeout: timeout, max_retries: max_retries, idempotency: idempotency,
18
+ log_level: log_level, logger: logger, adapter: adapter
19
+ }
20
+
21
+ @connection = connection || build_connection(log_level: log_level, logger: logger, adapter: adapter)
22
+ @token_manager = token_manager || build_token_manager(client_id, client_secret, code)
23
+ @http = Internal::HttpClient.new(
24
+ token_manager: @token_manager, base_url: @base_url, timeout: timeout,
25
+ max_retries: max_retries, idempotency: idempotency, connection: @connection
26
+ )
27
+ end
28
+
29
+ def banks
30
+ @banks ||= Resources::Banks.new(@http)
31
+ end
32
+
33
+ def biometrics
34
+ @biometrics ||= Resources::Biometrics.new(@http)
35
+ end
36
+
37
+ def credentials
38
+ @credentials ||= Resources::Credentials.new(@http)
39
+ end
40
+
41
+ def credit_offers
42
+ @credit_offers ||= Resources::CreditOffers.new(@http)
43
+ end
44
+
45
+ def customers
46
+ @customers ||= Resources::Customers.new(@http)
47
+ end
48
+
49
+ def loans
50
+ @loans ||= Resources::Loans.new(@http)
51
+ end
52
+
53
+ def webhook_endpoints
54
+ @webhook_endpoints ||= Resources::WebhookEndpoints.new(@http)
55
+ end
56
+
57
+ def rate_limit
58
+ @http.rate_limit
59
+ end
60
+
61
+ def with_options(**overrides)
62
+ self.class.new(
63
+ base_url: @base_url, token_manager: @token_manager, connection: @connection,
64
+ **@options.merge(overrides)
65
+ )
66
+ end
67
+
68
+ private
69
+
70
+ def build_token_manager(client_id, client_secret, code = nil)
71
+ resolved_id = first_present(client_id, ENV.fetch("DINIE_CLIENT_ID", nil))
72
+ resolved_secret = first_present(client_secret, ENV.fetch("DINIE_CLIENT_SECRET", nil))
73
+ raise Dinie::Error, "Missing Dinie client id: pass `client_id:` or set DINIE_CLIENT_ID." if resolved_id.nil?
74
+ if resolved_secret.nil?
75
+ raise Dinie::Error, "Missing Dinie client secret: pass `client_secret:` or set DINIE_CLIENT_SECRET."
76
+ end
77
+
78
+ Internal::TokenManager.new(
79
+ client_id: resolved_id, client_secret: resolved_secret,
80
+ base_url: @base_url, connection: @connection, code: code
81
+ )
82
+ end
83
+
84
+ def build_connection(log_level:, logger:, adapter:)
85
+ Faraday.new(url: @base_url) do |faraday|
86
+ faraday.request :multipart
87
+ faraday.use Internal::Middleware::Logging, level: log_level, logger: logger
88
+ faraday.adapter(adapter || :net_http_persistent)
89
+ end
90
+ end
91
+
92
+ def first_present(*candidates)
93
+ candidates.find { |value| !value.nil? && value != "" }
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../runtime/errors"
4
+
5
+ module Dinie
6
+ class AuthError < APIStatusError; end
7
+ class BadRequestError < APIStatusError; end
8
+ class ConflictError < APIStatusError; end
9
+ class NotFoundError < APIStatusError; end
10
+ class PermissionDeniedError < APIStatusError; end
11
+ class RateLimitError < APIStatusError; end
12
+ class ServerError < APIStatusError; end
13
+ class ValidationError < APIStatusError; end
14
+
15
+ module Internal
16
+ ERROR_REGISTRY = {
17
+ by_type: {
18
+ "https://docs.dinie.com/errors/invalid-request" => Dinie::BadRequestError,
19
+ "https://docs.dinie.com/errors/authentication-failed" => Dinie::AuthError,
20
+ "https://docs.dinie.com/errors/forbidden" => Dinie::PermissionDeniedError,
21
+ "https://docs.dinie.com/errors/not-found" => Dinie::NotFoundError,
22
+ "https://docs.dinie.com/errors/conflict" => Dinie::ConflictError,
23
+ "https://docs.dinie.com/errors/validation-failed" => Dinie::ValidationError,
24
+ "https://docs.dinie.com/errors/rate-limit-exceeded" => Dinie::RateLimitError,
25
+ "https://docs.dinie.com/errors/internal" => Dinie::ServerError
26
+ }.freeze,
27
+ by_status: {
28
+ 400 => Dinie::BadRequestError,
29
+ 401 => Dinie::AuthError,
30
+ 403 => Dinie::PermissionDeniedError,
31
+ 404 => Dinie::NotFoundError,
32
+ 409 => Dinie::ConflictError,
33
+ 422 => Dinie::ValidationError,
34
+ 429 => Dinie::RateLimitError,
35
+ 500 => Dinie::ServerError
36
+ }.freeze,
37
+ fallback_5xx: Dinie::ServerError
38
+ }.freeze
39
+ end
40
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../runtime/model"
4
+
5
+ module Dinie
6
+ module Events
7
+ class WebhookEventBase < Internal::Model
8
+ attribute :api_version, :created_at, :data, :delivery_id, :id, :timestamp, :type
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../runtime/model"
4
+ require_relative "base"
5
+
6
+ module Dinie
7
+ module Events
8
+ class CreditOfferData < Internal::Model
9
+ attribute :approved_amount, :customer_id, :due_date_rule, :external_id, :id, :installments, :min_amount,
10
+ :monthly_interest_rate, :status, :valid_until
11
+
12
+ def self.deserialize(raw)
13
+ new(
14
+ approved_amount: raw[:approved_amount],
15
+ customer_id: raw[:customer_id],
16
+ due_date_rule: raw[:due_date_rule],
17
+ external_id: raw[:external_id],
18
+ id: raw[:id],
19
+ installments: raw[:installments],
20
+ min_amount: raw[:min_amount],
21
+ monthly_interest_rate: raw[:monthly_interest_rate],
22
+ status: raw[:status],
23
+ valid_until: raw[:valid_until]
24
+ )
25
+ end
26
+ end
27
+
28
+ class CreditOfferAvailable < WebhookEventBase
29
+ def self.deserialize(raw)
30
+ new(
31
+ api_version: raw[:api_version],
32
+ created_at: raw[:created_at],
33
+ data: CreditOfferData.deserialize(raw[:data]),
34
+ delivery_id: raw[:delivery_id],
35
+ id: raw[:id],
36
+ timestamp: raw[:timestamp],
37
+ type: "credit_offer.available"
38
+ )
39
+ end
40
+ end
41
+
42
+ class CreditOfferExpired < WebhookEventBase
43
+ def self.deserialize(raw)
44
+ new(
45
+ api_version: raw[:api_version],
46
+ created_at: raw[:created_at],
47
+ data: CreditOfferData.deserialize(raw[:data]),
48
+ delivery_id: raw[:delivery_id],
49
+ id: raw[:id],
50
+ timestamp: raw[:timestamp],
51
+ type: "credit_offer.expired"
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../runtime/model"
4
+ require_relative "../types/kyc"
5
+ require_relative "base"
6
+
7
+ module Dinie
8
+ module Events
9
+ class CustomerCreatedData < Internal::Model
10
+ attribute :cnpj, :cpf, :email, :external_id, :id, :kyc, :name, :phone, :status, :trading_name
11
+
12
+ def self.deserialize(raw)
13
+ new(
14
+ cnpj: raw[:cnpj],
15
+ cpf: raw[:cpf],
16
+ email: raw[:email],
17
+ external_id: raw[:external_id],
18
+ id: raw[:id],
19
+ kyc: raw[:kyc].map { |requirement| Dinie.deserialize_kyc_requirement(requirement) },
20
+ name: raw[:name],
21
+ phone: raw[:phone],
22
+ status: raw[:status],
23
+ trading_name: raw[:trading_name]
24
+ )
25
+ end
26
+ end
27
+
28
+ class CustomerCreated < WebhookEventBase
29
+ def self.deserialize(raw)
30
+ new(
31
+ api_version: raw[:api_version],
32
+ created_at: raw[:created_at],
33
+ data: CustomerCreatedData.deserialize(raw[:data]),
34
+ delivery_id: raw[:delivery_id],
35
+ id: raw[:id],
36
+ timestamp: raw[:timestamp],
37
+ type: "customer.created"
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../runtime/model"
4
+ require_relative "base"
5
+
6
+ module Dinie
7
+ module Events
8
+ class CustomerDeniedData < Internal::Model
9
+ attribute :cnpj, :cpf, :email, :external_id, :id, :name, :phone, :status
10
+
11
+ def self.deserialize(raw)
12
+ new(
13
+ cnpj: raw[:cnpj],
14
+ cpf: raw[:cpf],
15
+ email: raw[:email],
16
+ external_id: raw[:external_id],
17
+ id: raw[:id],
18
+ name: raw[:name],
19
+ phone: raw[:phone],
20
+ status: raw[:status]
21
+ )
22
+ end
23
+ end
24
+
25
+ class CustomerDenied < WebhookEventBase
26
+ def self.deserialize(raw)
27
+ new(
28
+ api_version: raw[:api_version],
29
+ created_at: raw[:created_at],
30
+ data: CustomerDeniedData.deserialize(raw[:data]),
31
+ delivery_id: raw[:delivery_id],
32
+ id: raw[:id],
33
+ timestamp: raw[:timestamp],
34
+ type: "customer.denied"
35
+ )
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../runtime/model"
4
+ require_relative "../types/kyc"
5
+ require_relative "base"
6
+
7
+ module Dinie
8
+ module Events
9
+ class CustomerKycUpdatedData < Internal::Model
10
+ attribute :external_id, :id, :kyc, :status
11
+
12
+ def self.deserialize(raw)
13
+ new(
14
+ external_id: raw[:external_id],
15
+ id: raw[:id],
16
+ kyc: raw[:kyc].map { |requirement| Dinie.deserialize_kyc_requirement(requirement) },
17
+ status: raw[:status]
18
+ )
19
+ end
20
+ end
21
+
22
+ class CustomerKycUpdated < WebhookEventBase
23
+ def self.deserialize(raw)
24
+ new(
25
+ api_version: raw[:api_version],
26
+ created_at: raw[:created_at],
27
+ data: CustomerKycUpdatedData.deserialize(raw[:data]),
28
+ delivery_id: raw[:delivery_id],
29
+ id: raw[:id],
30
+ timestamp: raw[:timestamp],
31
+ type: "customer.kyc_updated"
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end