abacatepay-ruby 0.1.0 → 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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +136 -0
  3. data/CHANGELOG.md +171 -1
  4. data/README.md +491 -87
  5. data/Rakefile +1 -1
  6. data/abacatepay-ruby.gemspec +17 -10
  7. data/lib/abacate_pay/clients/billing_client.rb +90 -0
  8. data/lib/abacate_pay/clients/checkout_client.rb +91 -0
  9. data/lib/abacate_pay/clients/client.rb +202 -0
  10. data/lib/abacate_pay/clients/coupon_client.rb +57 -0
  11. data/lib/abacate_pay/clients/customer_client.rb +58 -0
  12. data/lib/abacate_pay/clients/payment_link_client.rb +71 -0
  13. data/lib/abacate_pay/clients/payout_client.rb +41 -0
  14. data/lib/abacate_pay/clients/pix_client.rb +47 -0
  15. data/lib/abacate_pay/clients/product_client.rb +54 -0
  16. data/lib/abacate_pay/clients/store_client.rb +40 -0
  17. data/lib/abacate_pay/clients/subscription_client.rb +90 -0
  18. data/lib/abacate_pay/clients/transparent_client.rb +125 -0
  19. data/lib/abacate_pay/clients/webhook_client.rb +82 -0
  20. data/lib/abacate_pay/clients.rb +24 -0
  21. data/lib/abacate_pay/collection.rb +98 -0
  22. data/lib/abacate_pay/configuration.rb +83 -0
  23. data/lib/{abacatepay/enums/billing → abacate_pay/enums/billings}/frequencies.rb +9 -3
  24. data/lib/{abacatepay/enums/billing → abacate_pay/enums/billings}/methods.rb +12 -3
  25. data/lib/{abacatepay/enums/billing → abacate_pay/enums/billings}/statuses.rb +3 -2
  26. data/lib/abacate_pay/enums/checkouts/statuses.rb +29 -0
  27. data/lib/abacate_pay/enums/coupons/discount_kinds.rb +26 -0
  28. data/lib/abacate_pay/enums/coupons/statuses.rb +27 -0
  29. data/lib/abacate_pay/enums/payouts/statuses.rb +29 -0
  30. data/lib/abacate_pay/enums/pix/key_types.rb +30 -0
  31. data/lib/abacate_pay/enums/products/cycles.rb +28 -0
  32. data/lib/abacate_pay/enums/transfers/statuses.rb +30 -0
  33. data/lib/abacate_pay/enums/webhooks/event_types.rb +50 -0
  34. data/lib/abacate_pay/enums.rb +20 -0
  35. data/lib/{abacatepay/resources/billing → abacate_pay/resources/billings}/metadata.rb +2 -8
  36. data/lib/{abacatepay/resources/billing → abacate_pay/resources/billings}/product.rb +2 -8
  37. data/lib/{abacatepay/resources/billing.rb → abacate_pay/resources/billings.rb} +16 -16
  38. data/lib/abacate_pay/resources/checkouts.rb +84 -0
  39. data/lib/abacate_pay/resources/coupons.rb +41 -0
  40. data/lib/{abacatepay/resources/customer → abacate_pay/resources/customers}/metadata.rb +2 -8
  41. data/lib/{abacatepay/resources/customer.rb → abacate_pay/resources/customers.rb} +5 -5
  42. data/lib/abacate_pay/resources/payouts.rb +40 -0
  43. data/lib/abacate_pay/resources/pix_transfers.rb +41 -0
  44. data/lib/abacate_pay/resources/products.rb +40 -0
  45. data/lib/{abacatepay → abacate_pay}/resources/resource.rb +33 -21
  46. data/lib/abacate_pay/resources/store/balance.rb +20 -0
  47. data/lib/abacate_pay/resources/store.rb +36 -0
  48. data/lib/abacate_pay/resources/subscriptions.rb +72 -0
  49. data/lib/abacate_pay/resources/transparents.rb +53 -0
  50. data/lib/abacate_pay/resources/webhook_endpoints.rb +49 -0
  51. data/lib/abacate_pay/resources.rb +27 -0
  52. data/lib/{abacatepay → abacate_pay}/version.rb +2 -2
  53. data/lib/abacate_pay/webhooks/event.rb +20 -0
  54. data/lib/abacate_pay/webhooks.rb +100 -0
  55. data/lib/abacate_pay.rb +112 -4
  56. metadata +75 -47
  57. data/lib/abacatepay/clients/billing_client.rb +0 -60
  58. data/lib/abacatepay/clients/client.rb +0 -68
  59. data/lib/abacatepay/clients/customer_client.rb +0 -39
  60. data/lib/abacatepay/clients.rb +0 -12
  61. data/lib/abacatepay/configuration.rb +0 -56
  62. data/lib/abacatepay/enums.rb +0 -12
  63. data/lib/abacatepay/resources.rb +0 -15
  64. data/sig/abacatepay/rails.rbs +0 -6
data/README.md CHANGED
@@ -1,121 +1,258 @@
1
- # AbacatePay SDK for Ruby on Rails
1
+ <div align="center">
2
2
 
3
- ## 💻 Installation
3
+ # AbacatePay Ruby SDK
4
4
 
5
- Add this line to your application's Gemfile:
5
+ SDK oficial da **AbacatePay** para integrar pagamentos via **PIX** de forma simples, segura e idiomática em Ruby.
6
6
 
7
- ```ruby
8
- gem 'abacatepay-ruby'
9
- ```
7
+ O [`abacatepay-ruby`](https://rubygems.org/gems/abacatepay-ruby) é um **wrapper versionado de alto nível** sobre a API da AbacatePay, focado em **DX**, **verificação segura de webhooks** e **erros tipados**.
10
8
 
11
- And then execute:
9
+ <img src="https://res.cloudinary.com/dkok1obj5/image/upload/v1767631413/avo_clhmaf.png" width="100%" alt="AbacatePay Open Source"/>
12
10
 
13
- ```bash
14
- bundle install
15
- ```
11
+ Funciona em qualquer aplicação Ruby — Rails, Sinatra, Hanami ou Ruby puro.
12
+
13
+ Referência completa da API [aqui](https://abacatepay.readme.io/reference).
16
14
 
17
- Or install it yourself as:
15
+ ## Requisitos
16
+
17
+ Ruby **3.2 ou superior**. Testado em 3.2, 3.3, 3.4 e 4.0.
18
+
19
+ ## Instalação
20
+
21
+ </div>
18
22
 
19
23
  ```bash
20
- gem install abacatepay-ruby
24
+ bundle add abacatepay-ruby
21
25
  ```
22
26
 
23
- ## 🔧 Configuration
27
+ <div align="center">
24
28
 
25
- Configure your API token and environment in an initializer:
29
+ Ou adicione ao seu `Gemfile`:
30
+
31
+ </div>
26
32
 
27
33
  ```ruby
28
- # config/initializers/abacatepay.rb
29
- AbacatePay.configure do |config|
30
- config.api_token = ENV['ABACATEPAY_TOKEN']
31
- config.environment = :sandbox # or :production
32
- end
34
+ gem 'abacatepay-ruby'
33
35
  ```
34
36
 
35
- ## 🌟 Resources
37
+ <div align="center">
36
38
 
37
- ### Billing
39
+ ## Uso básico
38
40
 
39
- **Initialize the Billing Client**
41
+ </div>
40
42
 
41
43
  ```ruby
42
- billing_client = AbacatePay::Clients::BillingClient.new
44
+ AbacatePay.configure do |config|
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
49
+ end
43
50
  ```
44
51
 
45
- **List Billings**
52
+ <div align="center">
46
53
 
47
- Retrieve a list of all billings:
54
+ Nunca utilize sua API key diretamente no código.
55
+ **Sempre use variáveis de ambiente**.
48
56
 
49
- ```ruby
50
- billing_client.list
51
- ```
57
+ Em Rails, coloque isso em `config/initializers/abacatepay.rb`.
58
+
59
+ Trocar o token em runtime tem efeito imediato — os clients são reconstruídos a cada `configure`.
52
60
 
53
- **Create a Billing**
61
+ ### Criando uma cobrança
54
62
 
55
- To create a billing, use the following code:
63
+ </div>
56
64
 
57
65
  ```ruby
58
- billing_client.create(
59
- AbacatePay::Resources::Billing.new(
60
- frequency: AbacatePay::Enums::Billing::Frequencies::ONE_TIME,
61
- methods: [AbacatePay::Enums::Billing::Methods::PIX],
66
+ checkout = AbacatePay.checkouts.create(
67
+ AbacatePay::Resources::Checkouts.new(
68
+ frequency: 'ONE_TIME',
69
+ methods: ['PIX'],
62
70
  products: [
63
- AbacatePay::Resources::Billing::Product.new(
64
- external_id: 'abc_123',
71
+ AbacatePay::Resources::Billings::Product.new(
72
+ external_id: 'prod_123',
65
73
  name: 'Product A',
66
- description: 'Description of product A',
67
74
  quantity: 1,
68
- price: 100 # Price in cents
75
+ price: 100
69
76
  )
70
77
  ],
71
- metadata: AbacatePay::Resources::Billing::Metadata.new(
72
- return_url: 'https://www.abacatepay.com',
73
- completion_url: 'https://www.abacatepay.com'
74
- ),
75
- customer: AbacatePay::Resources::Customer.new(
76
- metadata: AbacatePay::Resources::Customer::Metadata.new(
77
- name: 'Abacate Lover',
78
- cellphone: '01912341234',
79
- email: 'lover@abacate.com',
80
- tax_id: '13827826837'
81
- )
82
- )
78
+ customer: AbacatePay::Resources::Customers.new(id: 'cust_123')
83
79
  )
84
80
  )
85
81
  ```
86
82
 
87
- Alternatively, you can use a previously created customer by specifying their ID:
83
+ <div align="center">
84
+
85
+ ### Procure por alguns clientes
86
+
87
+ </div>
88
88
 
89
89
  ```ruby
90
- AbacatePay::Resources::Customer.new(
91
- id: 'cust_DEbpqcN...'
92
- )
90
+ customers = AbacatePay.customers.list(limit: 25)
91
+ ```
92
+
93
+ <div align="center">
94
+
95
+ Todos os métodos `list` aceitam parâmetros de paginação e filtro opcionais:
96
+
97
+ </div>
98
+
99
+ ```ruby
100
+ AbacatePay.customers.list(limit: 10, after: 'cursor_abc')
101
+ AbacatePay.checkouts.list(status: 'PAID', email: 'user@example.com')
102
+ ```
103
+
104
+ <div align="center">
105
+
106
+ Listas retornam no máximo 100 itens. O resultado é uma `Collection` — funciona como Array, e ainda carrega o cursor:
107
+
108
+ </div>
109
+
110
+ ```ruby
111
+ page = AbacatePay.customers.list
112
+ page.first.id # funciona como Array
113
+ page.has_more? # => true
114
+ page.next_cursor # => "cust_abc123"
115
+
116
+ # Para percorrer tudo sem lidar com cursor:
117
+ AbacatePay.customers.auto_paging_each { |customer| puts customer.id }
118
+ AbacatePay.customers.each_page { |page| puts page.size }
119
+ ```
120
+
121
+ <div align="center">
122
+
123
+ ## Versionamento
124
+
125
+ O SDK fala **exclusivamente a v2** — `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.
126
+
127
+ 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.
128
+
129
+ O `BillingClient` também está descontinuado, substituído pelo `CheckoutClient`. Ele emite um aviso ao ser instanciado, e seus endpoints `/billings/*` não existem na v2:
130
+
131
+ </div>
132
+
133
+ ```
134
+ [DEPRECATION] BillingClient is deprecated. Use CheckoutClient instead.
135
+ ```
136
+
137
+ <div align="center">
138
+
139
+ ## Tratamento de erros
140
+
141
+ 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.
142
+
143
+ </div>
144
+
145
+ ```ruby
146
+ begin
147
+ checkout = AbacatePay.checkouts.create(data)
148
+ rescue AbacatePay::ConfigurationError => e
149
+ # token ausente ou vazio
150
+ rescue AbacatePay::ApiError => e
151
+ # a API recusou a chamada, ou houve falha de rede/timeout
152
+ Rails.logger.error(e.message)
153
+ end
93
154
  ```
94
155
 
95
- ### Customer
156
+ <div align="center">
157
+
158
+ | Exceção | Quando acontece |
159
+ |---|---|
160
+ | `AbacatePay::ConfigurationError` | Token ausente ou vazio |
161
+ | `AbacatePay::ApiError` | Erro da API, falha de rede ou timeout |
162
+ | `AbacatePay::Webhooks::SignatureError` | Assinatura de webhook ausente, vazia ou inválida |
163
+ | `AbacatePay::Webhooks::PayloadError` | Corpo do webhook malformado ou que não é um objeto JSON |
164
+
165
+ Erros de rede e timeout são normalizados para `ApiError`, com a mensagem da API preservada quando ela envia uma.
166
+
167
+ ## Webhooks
96
168
 
97
- **Initialize the Customer Client**
169
+ Endpoints de webhook são públicos e não autenticados. Use `construct_event`, que **verifica a assinatura antes de fazer o parse** — é o único ponto de entrada que não permite agir sobre um payload não verificado.
170
+
171
+ </div>
98
172
 
99
173
  ```ruby
100
- customer_client = AbacatePay::Clients::CustomerClient.new
174
+ payload = request.body.read
175
+ signature = request.headers['X-Webhook-Signature']
176
+ secret = ENV['ABACATEPAY_WEBHOOK_SECRET']
177
+
178
+ begin
179
+ event = AbacatePay::Webhooks.construct_event(
180
+ payload: payload, signature: signature, secret: secret
181
+ )
182
+ rescue AbacatePay::Webhooks::SignatureError
183
+ return head :unauthorized
184
+ rescue AbacatePay::Webhooks::PayloadError
185
+ return head :bad_request
186
+ end
187
+
188
+ case event.type
189
+ when 'checkout.completed' then handle_payment(event.data)
190
+ when 'checkout.refunded' then handle_refund(event.data)
191
+ when 'subscription.renewed' then handle_renewal(event.data)
192
+ end
101
193
  ```
102
194
 
103
- **List Customers**
195
+ <div align="center">
104
196
 
105
- Retrieve a list of all customers:
197
+ 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.
198
+
199
+ Os métodos de baixo nível continuam disponíveis:
200
+
201
+ </div>
106
202
 
107
203
  ```ruby
108
- customer_client.list
204
+ # Levanta SignatureError se a assinatura estiver ausente ou inválida
205
+ AbacatePay::Webhooks.verify!(payload: payload, signature: signature, secret: secret)
206
+
207
+ # Contraparte booleana — nunca levanta exceção
208
+ AbacatePay::Webhooks.valid?(payload: payload, signature: signature, secret: secret)
209
+
210
+ # Faz parse de um corpo já verificado
211
+ AbacatePay::Webhooks.parse(payload)
109
212
  ```
110
213
 
111
- **Create a Customer**
214
+ <div align="center">
215
+
216
+ ### Eventos disponíveis
217
+
218
+ | Categoria | Eventos |
219
+ |---|---|
220
+ | Checkout | `checkout.completed`, `checkout.refunded`, `checkout.disputed` |
221
+ | Transparent | `transparent.completed`, `transparent.refunded`, `transparent.disputed` |
222
+ | Subscription | `subscription.completed`, `subscription.renewed`, `subscription.cancelled` |
223
+ | Transfer | `transfer.completed`, `transfer.failed` |
224
+ | Payout | `payout.completed`, `payout.failed` |
112
225
 
113
- To create a customer, use the following code:
226
+ ## Recursos
227
+
228
+ Todos os recursos são acessíveis pela fachada `AbacatePay.<recurso>`.
229
+
230
+ | Recurso | Métodos |
231
+ |---|---|
232
+ | `customers` | `list` `get` `create` `delete` |
233
+ | `products` | `list` `get` `create` `delete` |
234
+ | `coupons` | `list` `get` `create` `delete` `toggle` |
235
+ | `checkouts` | `list` `get` `create` `refund` |
236
+ | `subscriptions` | `list` `create` `cancel` `change_plan` `record_usage` |
237
+ | `transparents` | `list` `create` `check` `simulate_payment` `refund` |
238
+ | `pix` | `list` `get` `send_pix` |
239
+ | `payouts` | `list` `get` `create` |
240
+ | `store` | `get` `merchant_info` `mrr` `revenue` |
241
+ | `payment_links` | `list` `get` `create` `refund` |
242
+ | `webhook_endpoints` | `list` `get` `create` `delete` |
243
+
244
+ ### Clientes
245
+
246
+ </div>
114
247
 
115
248
  ```ruby
116
- customer_client.create(
117
- AbacatePay::Resources::Customer.new(
118
- metadata: AbacatePay::Resources::Customer::Metadata.new(
249
+ AbacatePay.customers.list
250
+ AbacatePay.customers.get('cust_123')
251
+ AbacatePay.customers.delete('cust_123')
252
+
253
+ AbacatePay.customers.create(
254
+ AbacatePay::Resources::Customers.new(
255
+ metadata: AbacatePay::Resources::Customers::Metadata.new(
119
256
  name: 'Abacate Lover',
120
257
  cellphone: '01912341234',
121
258
  email: 'lover@abacate.com',
@@ -125,42 +262,309 @@ customer_client.create(
125
262
  )
126
263
  ```
127
264
 
128
- ## 📚 Documentation
265
+ <div align="center">
129
266
 
130
- For detailed information about the API and SDK, refer to the official documentation:
131
- https://abacatepay.readme.io/reference
267
+ ### Produtos
132
268
 
133
- ## 🤝 Contribution
269
+ </div>
134
270
 
135
- Contributions are welcome! If you wish to contribute:
271
+ ```ruby
272
+ AbacatePay.products.create(
273
+ AbacatePay::Resources::Products.new(
274
+ external_id: 'my-product-1',
275
+ name: 'Monthly Plan',
276
+ price: 2990, # R$ 29,90 em centavos
277
+ currency: 'BRL',
278
+ description: 'Acesso a todos os recursos',
279
+ cycle: 'MONTHLY' # ou nil para pagamento único
280
+ )
281
+ )
282
+ ```
136
283
 
137
- 1. Fork the repository
138
- 2. Create a new branch for your feature or fix:
284
+ <div align="center">
139
285
 
140
- ```bash
141
- git checkout -b feature/your-feature-name
286
+ ### Cupons
287
+
288
+ </div>
289
+
290
+ ```ruby
291
+ AbacatePay.coupons.create(
292
+ AbacatePay::Resources::Coupons.new(
293
+ code: 'SAVE20',
294
+ discount: 20,
295
+ discount_kind: 'PERCENTAGE', # ou 'FIXED'
296
+ max_redeems: 100
297
+ )
298
+ )
299
+
300
+ AbacatePay.coupons.toggle('coup_123')
142
301
  ```
143
302
 
144
- 3. Make your changes and commit them:
303
+ <div align="center">
145
304
 
146
- ```bash
147
- git commit -m "Add your detailed commit message here"
305
+ ### Assinaturas
306
+
307
+ Exigem exatamente um produto com `cycle` definido.
308
+
309
+ </div>
310
+
311
+ ```ruby
312
+ AbacatePay.subscriptions.create(
313
+ AbacatePay::Resources::Subscriptions.new(
314
+ methods: ['PIX'],
315
+ customer: AbacatePay::Resources::Customers.new(id: 'cust_123'),
316
+ products: [
317
+ AbacatePay::Resources::Billings::Product.new(
318
+ external_id: 'plan-monthly',
319
+ name: 'Monthly Plan',
320
+ price: 2990,
321
+ quantity: 1
322
+ )
323
+ ]
324
+ )
325
+ )
326
+ ```
327
+
328
+ <div align="center">
329
+
330
+ ### PIX transparente (QR Code)
331
+
332
+ </div>
333
+
334
+ ```ruby
335
+ AbacatePay.transparents.create(
336
+ AbacatePay::Resources::Transparents.new(
337
+ amount: 1000,
338
+ description: 'Pedido #123',
339
+ expires_in: 3600
340
+ )
341
+ )
342
+
343
+ AbacatePay.transparents.check('tr_123')
344
+ AbacatePay.transparents.simulate_payment('tr_123') # apenas em dev mode
345
+ ```
346
+
347
+ <div align="center">
348
+
349
+ ### Transferências PIX
350
+
351
+ </div>
352
+
353
+ ```ruby
354
+ AbacatePay.pix.send_pix(
355
+ AbacatePay::Resources::PixTransfers.new(
356
+ amount: 500,
357
+ external_id: 'transfer-001',
358
+ description: 'Pagamento ao fornecedor',
359
+ key: '12345678900',
360
+ key_type: 'CPF' # CPF, CNPJ, PHONE, EMAIL, RANDOM, BR_CODE
361
+ )
362
+ )
363
+ ```
364
+
365
+ <div align="center">
366
+
367
+ ### Saques
368
+
369
+ Valor mínimo de R$ 3,50.
370
+
371
+ </div>
372
+
373
+ ```ruby
374
+ AbacatePay.payouts.create(
375
+ AbacatePay::Resources::Payouts.new(
376
+ amount: 5000,
377
+ external_id: 'withdrawal-001',
378
+ description: 'Saque mensal'
379
+ )
380
+ )
381
+ ```
382
+
383
+ <div align="center">
384
+
385
+ ### Boleto
386
+
387
+ Boleto tem vencimento, juros e multa próprios. Todos os valores em centavos.
388
+
389
+ </div>
390
+
391
+ ```ruby
392
+ AbacatePay.checkouts.create(
393
+ AbacatePay::Resources::Checkouts.new(
394
+ methods: ['BOLETO'],
395
+ due_date: '2026-08-15', # opcional; default 3 dias úteis
396
+ interest: { value: 100 }, # juros ao mês
397
+ fine: { value: 200, type: 'PERCENTAGE' }, # ou type: 'FIXED'
398
+ products: [
399
+ AbacatePay::Resources::Billings::Product.new(external_id: 'prod_123', quantity: 1)
400
+ ]
401
+ )
402
+ )
403
+ ```
404
+
405
+ <div align="center">
406
+
407
+ No checkout transparente, o boleto exige nome e CPF/CNPJ do pagador — o SDK valida antes de chamar a API:
408
+
409
+ </div>
410
+
411
+ ```ruby
412
+ charge = AbacatePay::Resources::Transparents.new(amount: 25_000, due_date: '2026-08-15')
413
+ # charge.customer precisa ter metadata.name e metadata.tax_id
414
+
415
+ boleto = AbacatePay.transparents.create(charge, method: 'BOLETO')
416
+ boleto.bar_code # linha digitável
417
+ boleto.url # PDF para impressão
418
+ boleto.br_code # PIX alternativo da mesma cobrança
419
+ ```
420
+
421
+ <div align="center">
422
+
423
+ ### Parcelamento e order bump
424
+
425
+ </div>
426
+
427
+ ```ruby
428
+ AbacatePay.checkouts.create(
429
+ AbacatePay::Resources::Checkouts.new(
430
+ methods: ['CARD'],
431
+ max_installments: 12,
432
+ up_sell_product_id: 'prod_bump',
433
+ custom_metadata: { origem: 'app-mobile' },
434
+ products: [
435
+ AbacatePay::Resources::Billings::Product.new(external_id: 'prod_123', quantity: 1)
436
+ ]
437
+ )
438
+ )
439
+ ```
440
+
441
+ <div align="center">
442
+
443
+ ### Links de pagamento
444
+
445
+ 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`.
446
+
447
+ </div>
448
+
449
+ ```ruby
450
+ link = AbacatePay.payment_links.create(
451
+ AbacatePay::Resources::Checkouts.new(
452
+ methods: ['PIX', 'CARD'],
453
+ external_id: 'campanha-black-friday',
454
+ products: [
455
+ AbacatePay::Resources::Billings::Product.new(external_id: 'prod_123', quantity: 1)
456
+ ]
457
+ )
458
+ )
459
+
460
+ puts link.url # compartilhe esta URL
461
+ ```
462
+
463
+ <div align="center">
464
+
465
+ ### Estornos
466
+
467
+ O estorno é sempre integral — a AbacatePay não faz estorno parcial.
468
+
469
+ </div>
470
+
471
+ ```ruby
472
+ AbacatePay.checkouts.refund('bill_abc123xyz')
473
+ AbacatePay.transparents.refund('pix_char_abc123xyz')
474
+ AbacatePay.payment_links.refund('char_abc123xyz')
475
+ ```
476
+
477
+ <div align="center">
478
+
479
+ ### Cancelar assinatura
480
+
481
+ Cancela imediatamente; parcelas futuras pendentes são canceladas junto.
482
+
483
+ </div>
484
+
485
+ ```ruby
486
+ AbacatePay.subscriptions.cancel('subs_abc123xyz')
487
+
488
+ # Upgrade/downgrade — vale a partir do próximo ciclo
489
+ AbacatePay.subscriptions.change_plan('subs_abc123xyz', product_id: 'prod_pro', quantity: 1)
490
+
491
+ # Cobrança por uso — produto sem ciclo
492
+ AbacatePay.subscriptions.record_usage('subs_abc123xyz', product_id: 'prod_api', units: 50)
148
493
  ```
149
494
 
150
- 4. Push to your branch:
495
+ <div align="center">
496
+
497
+ ### Registro de webhooks
498
+
499
+ Isto gerencia **para onde** a AbacatePay entrega os eventos. Para verificar uma entrega recebida, use `Webhooks.construct_event`.
500
+
501
+ </div>
502
+
503
+ ```ruby
504
+ AbacatePay.webhook_endpoints.create(
505
+ name: 'Pagamentos',
506
+ endpoint: 'https://meusite.com/webhooks/abacatepay', # precisa ser HTTPS
507
+ secret: ENV['ABACATEPAY_WEBHOOK_SECRET'],
508
+ events: ['checkout.completed', 'subscription.renewed']
509
+ )
510
+
511
+ AbacatePay.webhook_endpoints.list
512
+ AbacatePay.webhook_endpoints.delete('wh_123')
513
+ ```
514
+
515
+ <div align="center">
516
+
517
+ ### Loja
518
+
519
+ </div>
520
+
521
+ ```ruby
522
+ store = AbacatePay.store.get
523
+ store.balance.available # => 10000
524
+ store.balance.pending # => 500
525
+ store.balance.blocked # => 0
526
+
527
+ AbacatePay.store.revenue(start_date: '2026-01-01', end_date: '2026-03-30')
528
+ ```
529
+
530
+ <div align="center">
531
+
532
+ ## Enums
533
+
534
+ Os valores são validados na construção do recurso — um valor inválido levanta `ArgumentError` antes de qualquer chamada de rede.
535
+
536
+ | Enum | Valores |
537
+ |---|---|
538
+ | `Billings::Methods` | `PIX`, `CARD` |
539
+ | `Billings::Frequencies` | `ONE_TIME`, `WEEKLY`, `MONTHLY`, `SEMIANNUALLY`, `ANNUALLY`, `MULTIPLE_PAYMENTS` |
540
+ | `Billings::Statuses` | `PENDING`, `EXPIRED`, `CANCELLED`, `PAID`, `REFUNDED` |
541
+ | `Products::Cycles` | `WEEKLY`, `MONTHLY`, `SEMIANNUALLY`, `ANNUALLY` |
542
+ | `Coupons::Statuses` | `ACTIVE`, `INACTIVE`, `EXPIRED` |
543
+ | `Coupons::DiscountKinds` | `PERCENTAGE`, `FIXED` |
544
+ | `Pix::KeyTypes` | `CPF`, `CNPJ`, `PHONE`, `EMAIL`, `RANDOM`, `BR_CODE` |
545
+ | `Transfers::Statuses` | `PENDING`, `COMPLETE`, `CANCELLED`, `EXPIRED`, `REFUNDED`, `FAILED` |
546
+ | `Payouts::Statuses` | `PENDING`, `COMPLETE`, `CANCELLED`, `EXPIRED`, `REFUNDED` |
547
+
548
+ ## Contribuindo
549
+
550
+ </div>
151
551
 
152
552
  ```bash
153
- git push origin feature/your-feature-name
553
+ git clone https://github.com/AbacatePay/abacatepay-ruby-sdk.git
554
+ cd abacatepay-ruby-sdk
555
+ bundle install
556
+ bundle exec rake # specs + rubocop
154
557
  ```
155
558
 
156
- 5. Open a pull request with a clear description of your changes
559
+ <div align="center">
560
+
561
+ 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.
562
+
563
+ ## Licença
157
564
 
158
- Please ensure your code:
565
+ MIT
159
566
 
160
- - Includes proper documentation
161
- - Follows Ruby style guidelines
162
- - Includes appropriate tests
163
- - Passes all existing tests (bundle exec rspec)
164
- - Passes code style checks (bundle exec rubocop)
567
+ Feito com 🥑 pela equipe AbacatePay</br>
568
+ Open source, de verdade.
165
569
 
166
- ### Happy coding! 🚀
570
+ </div>
data/Rakefile CHANGED
@@ -7,4 +7,4 @@ require "rubocop/rake_task"
7
7
  RSpec::Core::RakeTask.new(:spec)
8
8
  RuboCop::RakeTask.new
9
9
 
10
- task default: %i[spec rubocop]
10
+ task default: %i[spec rubocop]