abacatepay-ruby 1.0.0 → 1.2.1
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +18 -0
- data/CHANGELOG.md +132 -12
- data/README.md +122 -20
- data/abacatepay-ruby.gemspec +2 -1
- data/lib/abacate_pay/clients/billing_client.rb +13 -3
- data/lib/abacate_pay/clients/checkout_client.rb +25 -4
- data/lib/abacate_pay/clients/client.rb +171 -16
- data/lib/abacate_pay/clients/coupon_client.rb +1 -1
- data/lib/abacate_pay/clients/customer_client.rb +1 -1
- data/lib/abacate_pay/clients/payment_link_client.rb +3 -3
- data/lib/abacate_pay/clients/payout_client.rb +1 -1
- data/lib/abacate_pay/clients/pix_client.rb +1 -1
- data/lib/abacate_pay/clients/product_client.rb +1 -1
- data/lib/abacate_pay/clients/store_client.rb +3 -1
- data/lib/abacate_pay/clients/subscription_client.rb +36 -1
- data/lib/abacate_pay/clients/transparent_client.rb +78 -24
- data/lib/abacate_pay/clients/webhook_client.rb +2 -2
- data/lib/abacate_pay/clients.rb +1 -1
- data/lib/abacate_pay/collection.rb +98 -0
- data/lib/abacate_pay/configuration.rb +20 -4
- data/lib/abacate_pay/enums/billings/methods.rb +8 -1
- data/lib/abacate_pay/enums/webhooks/event_types.rb +7 -0
- data/lib/abacate_pay/resources/checkouts.rb +10 -2
- data/lib/abacate_pay/resources/customers.rb +62 -21
- data/lib/abacate_pay/resources/resource.rb +1 -1
- data/lib/abacate_pay/resources/transparents.rb +29 -5
- data/lib/abacate_pay/resources/webhook_endpoints.rb +1 -1
- data/lib/abacate_pay/resources.rb +1 -1
- data/lib/abacate_pay/version.rb +1 -1
- data/lib/abacate_pay/webhooks.rb +53 -7
- data/lib/abacate_pay.rb +1 -0
- data/lib/abacatepay-ruby.rb +14 -0
- metadata +19 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 65f1214bed1683aa366b68ef9055d85a318ca11901f90c36669fb3202c0b9be5
|
|
4
|
+
data.tar.gz: 0f1374d3702f53204228345237a8072a23918151cb3e2b9d13bd374b558cb04a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: de8e7e809c16a249b698f02ed6d7f5eb0d1df24e6e2cab086f32a49fe90c818665a31c1cd642fae49f9a57174482470d64ec681ee99a55cb35e400d2d9fa6188
|
|
7
|
+
data.tar.gz: 22fd62f1e9e223d5d2e956c466b1d9de1d6128930d3b8651f4c72369e5827596e3a46c4c30db5171673eee95dd1bce49ef16a1739a351c64f1c73babeb37ff8a
|
data/.rubocop.yml
CHANGED
|
@@ -58,6 +58,7 @@ Naming/MethodParameterName:
|
|
|
58
58
|
Naming/PredicateMethod:
|
|
59
59
|
AllowedMethods:
|
|
60
60
|
- verify!
|
|
61
|
+
- verify_secret!
|
|
61
62
|
|
|
62
63
|
# Faraday errors carry a response payload, not just a message; the compact
|
|
63
64
|
# form is how Faraday itself documents constructing them.
|
|
@@ -119,7 +120,24 @@ RSpec/SpecFilePathFormat:
|
|
|
119
120
|
|
|
120
121
|
# These specs deliberately cut across the whole library rather than describing
|
|
121
122
|
# a single class.
|
|
123
|
+
# The file name must match the gem name so Bundler's default require works.
|
|
124
|
+
Naming/FileName:
|
|
125
|
+
Exclude:
|
|
126
|
+
- "lib/abacatepay-ruby.rb"
|
|
127
|
+
|
|
122
128
|
RSpec/DescribeClass:
|
|
123
129
|
Exclude:
|
|
130
|
+
- "spec/load_spec.rb"
|
|
124
131
|
- "spec/clients/request_contract_spec.rb"
|
|
132
|
+
- "spec/clients/pagination_spec.rb"
|
|
133
|
+
- "spec/clients/resilience_spec.rb"
|
|
134
|
+
- "spec/clients/logging_spec.rb"
|
|
135
|
+
- "spec/clients/boleto_spec.rb"
|
|
125
136
|
- "spec/packaging_spec.rb"
|
|
137
|
+
|
|
138
|
+
# `has_more?` mirrors the API's own `hasMore` field and matches the convention
|
|
139
|
+
# other payment SDKs use for cursor pagination. `more?` would read as a
|
|
140
|
+
# different question.
|
|
141
|
+
Naming/PredicatePrefix:
|
|
142
|
+
AllowedMethods:
|
|
143
|
+
- has_more?
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,124 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.2.1] - 2026-08-06
|
|
11
|
+
|
|
12
|
+
Inclui tudo o que estava previsto para a 1.2.0, que nunca chegou a ser
|
|
13
|
+
publicada. Todas as correções abaixo foram encontradas exercitando o SDK
|
|
14
|
+
contra o sandbox da AbacatePay e contra uma aplicação Rails real, e não contra
|
|
15
|
+
a própria suíte de testes.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- **Webhook signature verification rejected every genuine delivery.** The SDK
|
|
20
|
+
compared against `OpenSSL::HMAC.hexdigest`, but AbacatePay sends the HMAC
|
|
21
|
+
base64-encoded. The [security
|
|
22
|
+
spec](https://docs.abacatepay.com/pages/webhooks/security) and its Node,
|
|
23
|
+
Python and Go samples all use base64. `verify!` therefore failed every real
|
|
24
|
+
webhook while accepting a hex signature nobody sends, making webhook
|
|
25
|
+
verification inoperative since it was introduced. Reported by @danieldenis01
|
|
26
|
+
in #6, found while building a `pay-rails/pay` adapter against the sandbox and
|
|
27
|
+
production, along with the three fixes above.
|
|
28
|
+
- **`qr_code` and `qr_code_image` always returned nil for transparent PIX.** The
|
|
29
|
+
API sends the copy-and-paste payload as `brCode` and the image as
|
|
30
|
+
`brCodeBase64`; the readers looked for the older `qrCode`/`qrCodeImage`
|
|
31
|
+
spelling. Both are accepted now, preferring `qr*` when present. `platform_fee`
|
|
32
|
+
and `receipt_url` were missing entirely.
|
|
33
|
+
- **`simulate_payment` sent the id in the body.** The API reads it from the
|
|
34
|
+
query string on this endpoint, like `check`, and body-only requests fail with
|
|
35
|
+
"Expected property 'id'".
|
|
36
|
+
- **Optional fields were sent as explicit nulls.** The API rejects
|
|
37
|
+
`{"cellphone": null}` with HTTP 400 `Expected property 'cellphone' to be
|
|
38
|
+
string but found: null`, and payload builders emit nils for anything the
|
|
39
|
+
caller left unset. Payloads are now compacted recursively at the request
|
|
40
|
+
boundary, so this cannot be forgotten by a future endpoint. Also reported in
|
|
41
|
+
#6; the fix covers `products`, `coupons`, `payouts`, `pix` and
|
|
42
|
+
`subscriptions`, which had the same defect.
|
|
43
|
+
|
|
44
|
+
- **`gem "abacatepay-ruby"` did not load the SDK.** Bundler requires a gem by
|
|
45
|
+
its own name, so Rails called `require "abacatepay-ruby"`, and the entry
|
|
46
|
+
point was `lib/abacate_pay.rb`. The require failed, but the gemspec had
|
|
47
|
+
already defined `AbacatePay` with nothing but `VERSION` in it, so the first
|
|
48
|
+
call raised `undefined method 'configure' for module AbacatePay` instead of a
|
|
49
|
+
missing-constant error. Every Rails user had to discover `require:
|
|
50
|
+
"abacate_pay"` on their own. There is now an entry point matching the gem
|
|
51
|
+
name.
|
|
52
|
+
- **Customer responses dropped every field except the id.** The API returns
|
|
53
|
+
`name`, `email`, `cellphone` and `taxId` at the top level of the customer
|
|
54
|
+
object with an empty `metadata`; the resource only mapped a nested
|
|
55
|
+
`metadata`, so `customers.list` and `customers.get` returned objects with
|
|
56
|
+
nothing usable and `customer.metadata.name` was always nil. The fields are
|
|
57
|
+
exposed directly now, and `metadata` keeps answering for code written against
|
|
58
|
+
the previous interface.
|
|
59
|
+
- **`AbacatePay.store.get` always failed.** The API serves this as
|
|
60
|
+
`stores/get`, plural. The singular path documented in the reference answers
|
|
61
|
+
HTTP 400.
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
|
|
65
|
+
- `AbacatePay::Webhooks::PUBLIC_KEY`, the fixed key AbacatePay signs with, now
|
|
66
|
+
the default for `secret:`. It is public and global, so it proves body
|
|
67
|
+
integrity only: anyone can compute a valid signature with it.
|
|
68
|
+
- `AbacatePay::Webhooks.verify_secret!(received:, expected:)` for the
|
|
69
|
+
`webhookSecret` query parameter, which is what actually authenticates the
|
|
70
|
+
origin. The docs instruct using both mechanisms together; the SDK previously
|
|
71
|
+
offered only the HMAC half.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- The README no longer implies `has_more?` is always available. List responses
|
|
76
|
+
only carry pagination metadata when the API sends it; otherwise they are
|
|
77
|
+
plain Arrays, exactly as before.
|
|
78
|
+
|
|
79
|
+
## [1.1.0] - 2026-08-05
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
- **BOLETO support.** The method was rejected outright by the `Billings::Methods`
|
|
84
|
+
enum even though it is a first-class payment method in the v2 API. Adds the
|
|
85
|
+
enum value, the boleto-only `due_date`/`interest`/`fine` fields on checkouts,
|
|
86
|
+
and a `method:` argument on `TransparentClient#create` (which previously
|
|
87
|
+
hard-coded PIX). Boleto responses now expose `bar_code`, `url`, `br_code`,
|
|
88
|
+
`br_code_base64` and `expires_at`.
|
|
89
|
+
- **Cursor pagination.** List endpoints cap at 100 items and report `hasMore`
|
|
90
|
+
plus a cursor; the SDK discarded that metadata, making record 101
|
|
91
|
+
unreachable. `list` now returns an `AbacatePay::Collection`. It is Enumerable and
|
|
92
|
+
Array-compatible, so existing code is unaffected, and it carries `has_more?`
|
|
93
|
+
and `next_cursor`. `each_page` and `auto_paging_each` walk every page.
|
|
94
|
+
- **Retries with exponential backoff and jitter** on 429 and 5xx, configurable
|
|
95
|
+
via `config.max_retries` (default 2). Only idempotent methods are retried;
|
|
96
|
+
POST never is, because repeating `checkouts/create` after a timeout could
|
|
97
|
+
charge a customer twice and the API exposes no idempotency key.
|
|
98
|
+
- **Optional request logging** via `config.logger`, with the bearer token
|
|
99
|
+
redacted and bodies never logged.
|
|
100
|
+
- `SubscriptionClient#change_plan` and `#record_usage`: the last two of the 45
|
|
101
|
+
documented v2 endpoints. All 45 are now covered.
|
|
102
|
+
- `subscription.payment_failed` and `subscription.trial_started` webhook event
|
|
103
|
+
types. `payment_failed` is the dunning signal.
|
|
104
|
+
- Checkout fields the v2 API accepts but the SDK never sent: `max_installments`
|
|
105
|
+
(nested under `card`), `up_sell_product_id` and `custom_metadata`.
|
|
106
|
+
- A `User-Agent` identifying the SDK and Ruby version.
|
|
107
|
+
|
|
108
|
+
### Fixed
|
|
109
|
+
|
|
110
|
+
- `Webhooks.parse` aside, malformed API responses raised `JSON::ParserError`
|
|
111
|
+
and a missing `data` field raised `KeyError`; both are now `ApiError`.
|
|
112
|
+
|
|
113
|
+
### Changed
|
|
114
|
+
|
|
115
|
+
- `BillingClient`'s deprecation warning now states that its `/billings/*`
|
|
116
|
+
endpoints do not exist on either API version, so every call fails. It will be
|
|
117
|
+
removed in 2.0.0.
|
|
118
|
+
|
|
119
|
+
### Corrected
|
|
120
|
+
|
|
121
|
+
- The 1.0.0 notes stated that the v1 API "has been retired and answers Not found
|
|
122
|
+
for every path". That is wrong: v1 is still served, under a different dialect.
|
|
123
|
+
It uses singular paths (`/v1/billing/`, `/v1/customer/`) and different
|
|
124
|
+
resource names (`pixQrCode`). The original diagnosis tested v2-shaped paths against
|
|
125
|
+
`/v1`. The fix itself stands: this SDK only ever spoke v2's dialect, so 10 of
|
|
126
|
+
the 12 paths it calls do not exist on v1 and routing there produced 404s.
|
|
127
|
+
|
|
10
128
|
## [1.0.0] - 2026-08-05
|
|
11
129
|
|
|
12
130
|
First stable release. The public surface is now covered by CI on four Ruby
|
|
@@ -16,15 +134,15 @@ versions and will not change without a major bump.
|
|
|
16
134
|
|
|
17
135
|
- Full API coverage: checkouts, coupons, customers, payouts, PIX transfers,
|
|
18
136
|
products, store, subscriptions and transparent checkout.
|
|
19
|
-
- `AbacatePay::Webhooks.construct_event
|
|
137
|
+
- `AbacatePay::Webhooks.construct_event`, which verifies the signature and parses the
|
|
20
138
|
body in a single call, so an unverified payload cannot be acted on.
|
|
21
139
|
- `AbacatePay::Webhooks::PayloadError` for malformed or non-object webhook bodies.
|
|
22
|
-
- `PaymentLinkClient` (`AbacatePay.payment_links`)
|
|
23
|
-
- `WebhookClient` (`AbacatePay.webhook_endpoints`)
|
|
140
|
+
- `PaymentLinkClient` (`AbacatePay.payment_links`) for reusable multi-payment links.
|
|
141
|
+
- `WebhookClient` (`AbacatePay.webhook_endpoints`) for webhook endpoint registration,
|
|
24
142
|
with local HTTPS validation so a bad endpoint fails before the round trip.
|
|
25
|
-
- `CheckoutClient#refund`, `TransparentClient#refund`, `PaymentLinkClient#refund
|
|
26
|
-
|
|
27
|
-
- `SubscriptionClient#cancel
|
|
143
|
+
- `CheckoutClient#refund`, `TransparentClient#refund`, `PaymentLinkClient#refund`.
|
|
144
|
+
Refunds were previously impossible through the SDK.
|
|
145
|
+
- `SubscriptionClient#cancel`. A subscription created through the SDK could not
|
|
28
146
|
be cancelled through it.
|
|
29
147
|
- CI workflow running the suite on Ruby 3.2, 3.3, 3.4 and 4.0, plus RuboCop, a
|
|
30
148
|
dependency audit and a gem build check on every pull request.
|
|
@@ -62,7 +180,7 @@ versions and will not change without a major bump.
|
|
|
62
180
|
without `X-Webhook-Signature` reached `secure_compare` as `nil` and raised
|
|
63
181
|
`NoMethodError`, so `valid?` returned neither `true` nor `false` and the
|
|
64
182
|
endpoint returned a server error instead of rejecting the request. Missing and
|
|
65
|
-
empty signatures
|
|
183
|
+
empty signatures, plus a missing secret, are now `SignatureError`.
|
|
66
184
|
- **`Webhooks.parse` leaked parser internals.** Malformed JSON raised
|
|
67
185
|
`JSON::ParserError` and a non-object JSON body raised `TypeError`; both now
|
|
68
186
|
raise `PayloadError`.
|
|
@@ -72,7 +190,7 @@ versions and will not change without a major bump.
|
|
|
72
190
|
- **Changing the token at runtime had no effect.** Clients were memoized on first
|
|
73
191
|
use and never rebuilt, so `AbacatePay.configure` after a first API call kept
|
|
74
192
|
sending the previous bearer token. `configure` now discards memoized clients.
|
|
75
|
-
- `Configuration#api_url` was declared twice
|
|
193
|
+
- `Configuration#api_url` was declared twice, as an `attr_reader` and as a
|
|
76
194
|
method. The dead reader has been removed.
|
|
77
195
|
- `CustomerClient#get` and `#delete` had no test coverage at all.
|
|
78
196
|
- The `customer` and `billing` specs stubbed singular endpoint paths while the
|
|
@@ -93,13 +211,13 @@ versions and will not change without a major bump.
|
|
|
93
211
|
### Changed
|
|
94
212
|
|
|
95
213
|
- **BREAKING: `required_ruby_version` is now `>= 3.2.0`** (was `>= 2.6.0`). The
|
|
96
|
-
old floor was never installable
|
|
214
|
+
old floor was never installable: faraday 2.x requires Ruby 3.0 or newer, so
|
|
97
215
|
no working installation is losing support, but a `bundle update` on Ruby 2.6
|
|
98
216
|
or 3.1 will now refuse to resolve instead of failing later.
|
|
99
217
|
- **`config.environment` is deprecated and now warns.** It was declared,
|
|
100
218
|
defaulted, and validated, but read by nothing. Per AbacatePay's own
|
|
101
|
-
documentation the environment is decided by the API key
|
|
102
|
-
simulate transactions
|
|
219
|
+
documentation the environment is decided by the API key (Dev mode keys
|
|
220
|
+
simulate transactions), so the setting could never have worked. It is kept as
|
|
103
221
|
an accepted no-op so existing initializers keep loading.
|
|
104
222
|
- `validate!` no longer rejects unknown `environment` values, and now rejects an
|
|
105
223
|
empty or whitespace-only token.
|
|
@@ -114,12 +232,14 @@ versions and will not change without a major bump.
|
|
|
114
232
|
- Publishing to GitHub Packages. The step never executed successfully in any run
|
|
115
233
|
and nothing consumed the gem from that registry.
|
|
116
234
|
- `sig/abacatepay/rails.rbs`. Leftover `bundle gem` scaffolding declaring an
|
|
117
|
-
`Abacatepay::Rails` module that does not exist in this codebase
|
|
235
|
+
`Abacatepay::Rails` module that does not exist in this codebase, and that was
|
|
118
236
|
shipping inside the published gem, where a type checker would read it.
|
|
119
237
|
|
|
120
238
|
## [0.1.0] - 2024-12-13
|
|
121
239
|
|
|
122
240
|
- Initial release
|
|
123
241
|
|
|
242
|
+
[1.2.1]: https://github.com/AbacatePay/abacatepay-ruby-sdk/releases/tag/v1.2.1
|
|
243
|
+
[1.1.0]: https://github.com/AbacatePay/abacatepay-ruby-sdk/releases/tag/v1.1.0
|
|
124
244
|
[1.0.0]: https://github.com/AbacatePay/abacatepay-ruby-sdk/releases/tag/v1.0.0
|
|
125
245
|
[0.1.0]: https://github.com/AbacatePay/abacatepay-ruby-sdk/releases/tag/v0.1.0
|
data/README.md
CHANGED
|
@@ -8,7 +8,7 @@ O [`abacatepay-ruby`](https://rubygems.org/gems/abacatepay-ruby) é um **wrapper
|
|
|
8
8
|
|
|
9
9
|
<img src="https://res.cloudinary.com/dkok1obj5/image/upload/v1767631413/avo_clhmaf.png" width="100%" alt="AbacatePay Open Source"/>
|
|
10
10
|
|
|
11
|
-
Funciona em qualquer aplicação Ruby
|
|
11
|
+
Funciona em qualquer aplicação Ruby: Rails, Sinatra, Hanami ou Ruby puro.
|
|
12
12
|
|
|
13
13
|
Referência completa da API [aqui](https://abacatepay.readme.io/reference).
|
|
14
14
|
|
|
@@ -42,8 +42,10 @@ gem 'abacatepay-ruby'
|
|
|
42
42
|
|
|
43
43
|
```ruby
|
|
44
44
|
AbacatePay.configure do |config|
|
|
45
|
-
config.api_token
|
|
46
|
-
config.timeout
|
|
45
|
+
config.api_token = ENV['ABACATEPAY_TOKEN']
|
|
46
|
+
config.timeout = 30 # opcional, segundos (default 30)
|
|
47
|
+
config.max_retries = 2 # opcional, retry em 429/5xx (default 2, 0 desliga)
|
|
48
|
+
config.logger = Rails.logger # opcional, token é redigido
|
|
47
49
|
end
|
|
48
50
|
```
|
|
49
51
|
|
|
@@ -54,7 +56,7 @@ Nunca utilize sua API key diretamente no código.
|
|
|
54
56
|
|
|
55
57
|
Em Rails, coloque isso em `config/initializers/abacatepay.rb`.
|
|
56
58
|
|
|
57
|
-
Trocar o token em runtime tem efeito imediato
|
|
59
|
+
Trocar o token em runtime tem efeito imediato: os clients são reconstruídos a cada `configure`.
|
|
58
60
|
|
|
59
61
|
### Criando uma cobrança
|
|
60
62
|
|
|
@@ -101,25 +103,53 @@ AbacatePay.checkouts.list(status: 'PAID', email: 'user@example.com')
|
|
|
101
103
|
|
|
102
104
|
<div align="center">
|
|
103
105
|
|
|
106
|
+
Listas retornam no máximo 100 itens.
|
|
107
|
+
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
page = AbacatePay.customers.list
|
|
112
|
+
page.first.id # funciona como Array
|
|
113
|
+
|
|
114
|
+
# Para percorrer tudo sem lidar com cursor:
|
|
115
|
+
AbacatePay.customers.auto_paging_each { |customer| puts customer.id }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
<div align="center">
|
|
119
|
+
|
|
120
|
+
Quando a API envia metadados de paginação, o resultado é uma `Collection` que carrega o cursor. Quando não envia, é um Array puro, então cheque antes de usar:
|
|
121
|
+
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
if page.respond_to?(:has_more?) && page.has_more?
|
|
126
|
+
proxima = AbacatePay.customers.list(after: page.next_cursor)
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
<div align="center">
|
|
131
|
+
|
|
104
132
|
## Versionamento
|
|
105
133
|
|
|
106
|
-
O SDK fala **exclusivamente a v2
|
|
134
|
+
O SDK fala **exclusivamente a v2**, em `https://api.abacatepay.com/v2`. A v1 ainda existe para integrações legadas, mas usa outro dialeto (caminhos no singular como `/v1/billing/`, `/v1/customer/`) que este SDK nunca implementou. Se você precisa da v1, chame a API diretamente.
|
|
107
135
|
|
|
108
|
-
O ambiente (dev mode x produção) é definido **pela chave de API**, não por configuração: chaves de Dev mode geram transações simuladas. Por isso `config.environment` não faz nada
|
|
136
|
+
O ambiente (dev mode x produção) é definido **pela chave de API**, não por configuração: chaves de Dev mode geram transações simuladas. Por isso `config.environment` não faz nada. Ela continua aceita para não quebrar initializers existentes, mas emite aviso de depreciação.
|
|
109
137
|
|
|
110
|
-
O `BillingClient` também está descontinuado, substituído pelo `CheckoutClient`.
|
|
138
|
+
O `BillingClient` também está descontinuado, substituído pelo `CheckoutClient`. Seus endpoints `/billings/*` não existem em nenhuma versão da API: toda chamada falha. Será removido na 2.0.0:
|
|
111
139
|
|
|
112
140
|
</div>
|
|
113
141
|
|
|
114
142
|
```
|
|
115
|
-
[DEPRECATION] BillingClient
|
|
143
|
+
[DEPRECATION] BillingClient calls /billings/* endpoints that do not exist on the
|
|
144
|
+
AbacatePay API, every request will fail. Use AbacatePay.checkouts instead.
|
|
145
|
+
This class will be removed in 2.0.0.
|
|
116
146
|
```
|
|
117
147
|
|
|
118
148
|
<div align="center">
|
|
119
149
|
|
|
120
150
|
## Tratamento de erros
|
|
121
151
|
|
|
122
|
-
Diferente do SDK de Node, **este SDK levanta exceções
|
|
152
|
+
Diferente do SDK de Node, **este SDK levanta exceções**. Ele não retorna `{ data, error, success }`. Toda falha vira uma exceção tipada que herda de `AbacatePay::Error`, então você pode capturar tudo de uma vez ou tratar caso a caso.
|
|
123
153
|
|
|
124
154
|
</div>
|
|
125
155
|
|
|
@@ -147,18 +177,26 @@ Erros de rede e timeout são normalizados para `ApiError`, com a mensagem da API
|
|
|
147
177
|
|
|
148
178
|
## Webhooks
|
|
149
179
|
|
|
150
|
-
Endpoints de webhook são públicos e
|
|
180
|
+
Endpoints de webhook são públicos. A AbacatePay usa **dois mecanismos**, e a documentação orienta usar os dois: o `webhookSecret` na query autentica a origem, e a assinatura HMAC garante que o corpo não foi alterado. A chave HMAC é pública e global: ela sozinha não prova origem.
|
|
151
181
|
|
|
152
182
|
</div>
|
|
153
183
|
|
|
154
184
|
```ruby
|
|
155
185
|
payload = request.body.read
|
|
156
186
|
signature = request.headers['X-Webhook-Signature']
|
|
157
|
-
secret = ENV['ABACATEPAY_WEBHOOK_SECRET']
|
|
158
187
|
|
|
159
188
|
begin
|
|
189
|
+
# 1. Autentica a origem com o secret que você definiu ao criar o webhook,
|
|
190
|
+
# enviado pela AbacatePay como query parameter.
|
|
191
|
+
AbacatePay::Webhooks.verify_secret!(
|
|
192
|
+
received: params[:webhookSecret],
|
|
193
|
+
expected: ENV['ABACATEPAY_WEBHOOK_SECRET']
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# 2. Verifica a integridade do corpo. A chave HMAC é pública, então este
|
|
197
|
+
# passo sozinho não prova origem. Por isso os dois juntos.
|
|
160
198
|
event = AbacatePay::Webhooks.construct_event(
|
|
161
|
-
payload: payload, signature: signature
|
|
199
|
+
payload: payload, signature: signature
|
|
162
200
|
)
|
|
163
201
|
rescue AbacatePay::Webhooks::SignatureError
|
|
164
202
|
return head :unauthorized
|
|
@@ -175,7 +213,7 @@ end
|
|
|
175
213
|
|
|
176
214
|
<div align="center">
|
|
177
215
|
|
|
178
|
-
Header ausente, secret vazio, assinatura forjada e corpo malformado são todos tratados como casos esperados
|
|
216
|
+
Header ausente, secret vazio, assinatura forjada e corpo malformado são todos tratados como casos esperados: levantam erro tipado em vez de derrubar o endpoint. A comparação de assinatura é feita em tempo constante.
|
|
179
217
|
|
|
180
218
|
Os métodos de baixo nível continuam disponíveis:
|
|
181
219
|
|
|
@@ -185,7 +223,7 @@ Os métodos de baixo nível continuam disponíveis:
|
|
|
185
223
|
# Levanta SignatureError se a assinatura estiver ausente ou inválida
|
|
186
224
|
AbacatePay::Webhooks.verify!(payload: payload, signature: signature, secret: secret)
|
|
187
225
|
|
|
188
|
-
# Contraparte booleana
|
|
226
|
+
# Contraparte booleana. Nunca levanta exceção
|
|
189
227
|
AbacatePay::Webhooks.valid?(payload: payload, signature: signature, secret: secret)
|
|
190
228
|
|
|
191
229
|
# Faz parse de um corpo já verificado
|
|
@@ -200,7 +238,7 @@ AbacatePay::Webhooks.parse(payload)
|
|
|
200
238
|
|---|---|
|
|
201
239
|
| Checkout | `checkout.completed`, `checkout.refunded`, `checkout.disputed` |
|
|
202
240
|
| Transparent | `transparent.completed`, `transparent.refunded`, `transparent.disputed` |
|
|
203
|
-
| Subscription | `subscription.completed`, `subscription.renewed`, `subscription.cancelled` |
|
|
241
|
+
| Subscription | `subscription.completed`, `subscription.renewed`, `subscription.cancelled`, `subscription.payment_failed`, `subscription.trial_started` |
|
|
204
242
|
| Transfer | `transfer.completed`, `transfer.failed` |
|
|
205
243
|
| Payout | `payout.completed`, `payout.failed` |
|
|
206
244
|
|
|
@@ -214,7 +252,7 @@ Todos os recursos são acessíveis pela fachada `AbacatePay.<recurso>`.
|
|
|
214
252
|
| `products` | `list` `get` `create` `delete` |
|
|
215
253
|
| `coupons` | `list` `get` `create` `delete` `toggle` |
|
|
216
254
|
| `checkouts` | `list` `get` `create` `refund` |
|
|
217
|
-
| `subscriptions` | `list` `create` `cancel` |
|
|
255
|
+
| `subscriptions` | `list` `create` `cancel` `change_plan` `record_usage` |
|
|
218
256
|
| `transparents` | `list` `create` `check` `simulate_payment` `refund` |
|
|
219
257
|
| `pix` | `list` `get` `send_pix` |
|
|
220
258
|
| `payouts` | `list` `get` `create` |
|
|
@@ -363,9 +401,67 @@ AbacatePay.payouts.create(
|
|
|
363
401
|
|
|
364
402
|
<div align="center">
|
|
365
403
|
|
|
404
|
+
### Boleto
|
|
405
|
+
|
|
406
|
+
Boleto tem vencimento, juros e multa próprios. Todos os valores em centavos.
|
|
407
|
+
|
|
408
|
+
</div>
|
|
409
|
+
|
|
410
|
+
```ruby
|
|
411
|
+
AbacatePay.checkouts.create(
|
|
412
|
+
AbacatePay::Resources::Checkouts.new(
|
|
413
|
+
methods: ['BOLETO'],
|
|
414
|
+
due_date: '2026-08-15', # opcional; default 3 dias úteis
|
|
415
|
+
interest: { value: 100 }, # juros ao mês
|
|
416
|
+
fine: { value: 200, type: 'PERCENTAGE' }, # ou type: 'FIXED'
|
|
417
|
+
products: [
|
|
418
|
+
AbacatePay::Resources::Billings::Product.new(external_id: 'prod_123', quantity: 1)
|
|
419
|
+
]
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
<div align="center">
|
|
425
|
+
|
|
426
|
+
No checkout transparente, o boleto exige nome e CPF/CNPJ do pagador, o SDK valida antes de chamar a API:
|
|
427
|
+
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
```ruby
|
|
431
|
+
charge = AbacatePay::Resources::Transparents.new(amount: 25_000, due_date: '2026-08-15')
|
|
432
|
+
# charge.customer precisa ter metadata.name e metadata.tax_id
|
|
433
|
+
|
|
434
|
+
boleto = AbacatePay.transparents.create(charge, method: 'BOLETO')
|
|
435
|
+
boleto.bar_code # linha digitável
|
|
436
|
+
boleto.url # PDF para impressão
|
|
437
|
+
boleto.br_code # PIX alternativo da mesma cobrança
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
<div align="center">
|
|
441
|
+
|
|
442
|
+
### Parcelamento e order bump
|
|
443
|
+
|
|
444
|
+
</div>
|
|
445
|
+
|
|
446
|
+
```ruby
|
|
447
|
+
AbacatePay.checkouts.create(
|
|
448
|
+
AbacatePay::Resources::Checkouts.new(
|
|
449
|
+
methods: ['CARD'],
|
|
450
|
+
max_installments: 12,
|
|
451
|
+
up_sell_product_id: 'prod_bump',
|
|
452
|
+
custom_metadata: { origem: 'app-mobile' },
|
|
453
|
+
products: [
|
|
454
|
+
AbacatePay::Resources::Billings::Product.new(external_id: 'prod_123', quantity: 1)
|
|
455
|
+
]
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
<div align="center">
|
|
461
|
+
|
|
366
462
|
### Links de pagamento
|
|
367
463
|
|
|
368
|
-
Um link reutilizável, pago por vários clientes de forma independente
|
|
464
|
+
Um link reutilizável, pago por vários clientes de forma independente, vendas em massa, rifas, formulários de inscrição. Para uma cobrança por cliente, use `checkouts`.
|
|
369
465
|
|
|
370
466
|
</div>
|
|
371
467
|
|
|
@@ -387,7 +483,7 @@ puts link.url # compartilhe esta URL
|
|
|
387
483
|
|
|
388
484
|
### Estornos
|
|
389
485
|
|
|
390
|
-
O estorno é sempre integral
|
|
486
|
+
O estorno é sempre integral, a AbacatePay não faz estorno parcial.
|
|
391
487
|
|
|
392
488
|
</div>
|
|
393
489
|
|
|
@@ -407,6 +503,12 @@ Cancela imediatamente; parcelas futuras pendentes são canceladas junto.
|
|
|
407
503
|
|
|
408
504
|
```ruby
|
|
409
505
|
AbacatePay.subscriptions.cancel('subs_abc123xyz')
|
|
506
|
+
|
|
507
|
+
# Upgrade/downgrade, vale a partir do próximo ciclo
|
|
508
|
+
AbacatePay.subscriptions.change_plan('subs_abc123xyz', product_id: 'prod_pro', quantity: 1)
|
|
509
|
+
|
|
510
|
+
# Cobrança por uso, produto sem ciclo
|
|
511
|
+
AbacatePay.subscriptions.record_usage('subs_abc123xyz', product_id: 'prod_api', units: 50)
|
|
410
512
|
```
|
|
411
513
|
|
|
412
514
|
<div align="center">
|
|
@@ -448,7 +550,7 @@ AbacatePay.store.revenue(start_date: '2026-01-01', end_date: '2026-03-30')
|
|
|
448
550
|
|
|
449
551
|
## Enums
|
|
450
552
|
|
|
451
|
-
Os valores são validados na construção do recurso
|
|
553
|
+
Os valores são validados na construção do recurso, um valor inválido levanta `ArgumentError` antes de qualquer chamada de rede.
|
|
452
554
|
|
|
453
555
|
| Enum | Valores |
|
|
454
556
|
|---|---|
|
|
@@ -475,7 +577,7 @@ bundle exec rake # specs + rubocop
|
|
|
475
577
|
|
|
476
578
|
<div align="center">
|
|
477
579
|
|
|
478
|
-
Antes de abrir um PR, garanta que `bundle exec rake` passa e que a cobertura não caiu
|
|
580
|
+
Antes de abrir um PR, garanta que `bundle exec rake` passa e que a cobertura não caiu, o CI roda os specs em Ruby 3.2, 3.3, 3.4 e 4.0, mais RuboCop, auditoria de dependências e build do gem.
|
|
479
581
|
|
|
480
582
|
## Licença
|
|
481
583
|
|
data/abacatepay-ruby.gemspec
CHANGED
|
@@ -41,13 +41,14 @@ Gem::Specification.new do |spec|
|
|
|
41
41
|
# NestedParamsEncoder). The lockfile only protects this repo — consumers are
|
|
42
42
|
# protected by the constraint here.
|
|
43
43
|
spec.add_dependency "faraday", "~> 2.14", ">= 2.14.3"
|
|
44
|
+
spec.add_dependency "faraday-retry", "~> 2.3"
|
|
44
45
|
|
|
45
46
|
# Development dependencies
|
|
46
47
|
spec.add_development_dependency "bundler-audit", "~> 0.9"
|
|
47
48
|
spec.add_development_dependency "rspec", "~> 3.12"
|
|
48
49
|
spec.add_development_dependency "rubocop", "~> 1.57"
|
|
49
50
|
spec.add_development_dependency "rubocop-rspec", "~> 3.0"
|
|
50
|
-
spec.add_development_dependency "simplecov", "~> 0
|
|
51
|
+
spec.add_development_dependency "simplecov", "~> 1.0"
|
|
51
52
|
|
|
52
53
|
# For more information and examples about making a new gem, check out our
|
|
53
54
|
# guide at: https://bundler.io/guides/creating_gem.html
|
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module AbacatePay
|
|
4
4
|
module Clients
|
|
5
|
-
#
|
|
5
|
+
# Deprecated client for the v1 billing endpoints.
|
|
6
|
+
#
|
|
7
|
+
# The endpoints this class calls (`/billings/create`, `/billings/list`) do
|
|
8
|
+
# not exist on either API version, v2 replaced them with `/checkouts/*`,
|
|
9
|
+
# and v1 uses the singular `/billing/*`. Every call raises ApiError.
|
|
10
|
+
#
|
|
11
|
+
# Kept only so existing code keeps loading; it will be removed in 2.0.0.
|
|
12
|
+
#
|
|
13
|
+
# @deprecated Use {CheckoutClient} instead.
|
|
6
14
|
class BillingClient < Client
|
|
7
15
|
# API endpoint for billing-related operations
|
|
8
16
|
URI = "billings"
|
|
@@ -10,7 +18,9 @@ module AbacatePay
|
|
|
10
18
|
# @param client [Faraday::Connection, nil] Optional Faraday client for custom configurations
|
|
11
19
|
# @deprecated Use {CheckoutClient} instead
|
|
12
20
|
def initialize(client = nil)
|
|
13
|
-
warn "[DEPRECATION] BillingClient
|
|
21
|
+
warn "[DEPRECATION] BillingClient calls /billings/* endpoints that do not exist on the " \
|
|
22
|
+
"AbacatePay API, so every request fails. Use AbacatePay.checkouts instead. " \
|
|
23
|
+
"This class will be removed in 2.0.0."
|
|
14
24
|
super(URI, client)
|
|
15
25
|
end
|
|
16
26
|
|
|
@@ -19,7 +29,7 @@ module AbacatePay
|
|
|
19
29
|
# @return [Array<Resources::Billing>] Array of Billing objects
|
|
20
30
|
def list
|
|
21
31
|
response = request("GET", "list")
|
|
22
|
-
|
|
32
|
+
build_list(response, Resources::Billings)
|
|
23
33
|
end
|
|
24
34
|
|
|
25
35
|
# Creates a new billing
|
|
@@ -17,7 +17,7 @@ module AbacatePay
|
|
|
17
17
|
# @return [Array<Resources::Checkouts>]
|
|
18
18
|
def list(**params)
|
|
19
19
|
response = request("GET", "list", params: params.empty? ? nil : params)
|
|
20
|
-
|
|
20
|
+
build_list(response, Resources::Checkouts)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
# @param id [String] Checkout ID
|
|
@@ -35,7 +35,7 @@ module AbacatePay
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# Refunds a paid checkout in full. AbacatePay does not support partial
|
|
38
|
-
# refunds
|
|
38
|
+
# refunds, the original amount is always returned.
|
|
39
39
|
#
|
|
40
40
|
# @param id [String] Public checkout ID (`bill_...`) or charge ID
|
|
41
41
|
# (`char_...`, `pix_char_...`, `card_...`)
|
|
@@ -62,8 +62,29 @@ module AbacatePay
|
|
|
62
62
|
items: data.products&.map { |product| { id: product.external_id, quantity: product.quantity } },
|
|
63
63
|
externalId: data.external_id,
|
|
64
64
|
coupons: data.coupons,
|
|
65
|
-
customerId: customer_id.to_s.empty? ? nil : customer_id
|
|
66
|
-
|
|
65
|
+
customerId: customer_id.to_s.empty? ? nil : customer_id,
|
|
66
|
+
upSellProductId: data.up_sell_product_id,
|
|
67
|
+
metadata: data.custom_metadata
|
|
68
|
+
}.merge(boleto_options(data)).merge(card_options(data)).compact
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# BOLETO-only fields. The API rejects them for other methods, so they are
|
|
72
|
+
# only sent when the caller actually set them.
|
|
73
|
+
#
|
|
74
|
+
# @param data [Resources::Checkouts] The checkout to serialize
|
|
75
|
+
# @return [Hash] The boleto payload fragment
|
|
76
|
+
def boleto_options(data)
|
|
77
|
+
{ dueDate: data.due_date, interest: data.interest, fine: data.fine }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# CARD-only instalment cap, which the API nests under `card`.
|
|
81
|
+
#
|
|
82
|
+
# @param data [Resources::Checkouts] The checkout to serialize
|
|
83
|
+
# @return [Hash] The card payload fragment
|
|
84
|
+
def card_options(data)
|
|
85
|
+
return {} unless data.max_installments
|
|
86
|
+
|
|
87
|
+
{ card: { maxInstallments: data.max_installments } }
|
|
67
88
|
end
|
|
68
89
|
end
|
|
69
90
|
end
|