AbacatePay Ruby SDK
SDK oficial da AbacatePay para integrar pagamentos via PIX de forma simples, segura e idiomática em Ruby.
O 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.
Funciona em qualquer aplicação Ruby: Rails, Sinatra, Hanami ou Ruby puro.
Referência completa da API aqui.
Requisitos
Ruby 3.2 ou superior. Testado em 3.2, 3.3, 3.4 e 4.0.
Instalação
bundle add abacatepay-ruby
Ou adicione ao seu Gemfile:
gem 'abacatepay-ruby'
Uso básico
AbacatePay.configure do |config|
config.api_token = ENV['ABACATEPAY_TOKEN']
config.timeout = 30 # opcional, segundos (default 30)
config.max_retries = 2 # opcional, retry em 429/5xx (default 2, 0 desliga)
config.logger = Rails.logger # opcional, token é redigido
end
Nunca utilize sua API key diretamente no código. Sempre use variáveis de ambiente.
Em Rails, coloque isso em config/initializers/abacatepay.rb.
Trocar o token em runtime tem efeito imediato: os clients são reconstruídos a cada configure.
Criando uma cobrança
checkout = AbacatePay.checkouts.create(
AbacatePay::Resources::Checkouts.new(
frequency: 'ONE_TIME',
methods: ['PIX'],
products: [
AbacatePay::Resources::Billings::Product.new(
external_id: 'prod_123',
name: 'Product A',
quantity: 1,
price: 100
)
],
customer: AbacatePay::Resources::Customers.new(id: 'cust_123')
)
)
Procure por alguns clientes
customers = AbacatePay.customers.list(limit: 25)
Todos os métodos list aceitam parâmetros de paginação e filtro opcionais:
AbacatePay.customers.list(limit: 10, after: 'cursor_abc')
AbacatePay.checkouts.list(status: 'PAID', email: 'user@example.com')
Listas retornam no máximo 100 itens.
page = AbacatePay.customers.list
page.first.id # funciona como Array
# Para percorrer tudo sem lidar com cursor:
AbacatePay.customers.auto_paging_each { |customer| puts customer.id }
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:
if page.respond_to?(:has_more?) && page.has_more?
proxima = AbacatePay.customers.list(after: page.next_cursor)
end
Versionamento
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.
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.
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:
[DEPRECATION] BillingClient calls /billings/* endpoints that do not exist on the
AbacatePay API, every request will fail. Use AbacatePay.checkouts instead.
This class will be removed in 2.0.0.
Tratamento de erros
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.
begin
checkout = AbacatePay.checkouts.create(data)
rescue AbacatePay::ConfigurationError => e
# token ausente ou vazio
rescue AbacatePay::ApiError => e
# a API recusou a chamada, ou houve falha de rede/timeout
Rails.logger.error(e.)
end
| Exceção | Quando acontece |
|---|---|
AbacatePay::ConfigurationError |
Token ausente ou vazio |
AbacatePay::ApiError |
Erro da API, falha de rede ou timeout |
AbacatePay::Webhooks::SignatureError |
Assinatura de webhook ausente, vazia ou inválida |
AbacatePay::Webhooks::PayloadError |
Corpo do webhook malformado ou que não é um objeto JSON |
Erros de rede e timeout são normalizados para ApiError, com a mensagem da API preservada quando ela envia uma.
Webhooks
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.
payload = request.body.read
signature = request.headers['X-Webhook-Signature']
begin
# 1. Autentica a origem com o secret que você definiu ao criar o webhook,
# enviado pela AbacatePay como query parameter.
AbacatePay::Webhooks.verify_secret!(
received: params[:webhookSecret],
expected: ENV['ABACATEPAY_WEBHOOK_SECRET']
)
# 2. Verifica a integridade do corpo. A chave HMAC é pública, então este
# passo sozinho não prova origem. Por isso os dois juntos.
event = AbacatePay::Webhooks.construct_event(
payload: payload, signature: signature
)
rescue AbacatePay::Webhooks::SignatureError
return head :unauthorized
rescue AbacatePay::Webhooks::PayloadError
return head :bad_request
end
case event.type
when 'checkout.completed' then handle_payment(event.data)
when 'checkout.refunded' then handle_refund(event.data)
when 'subscription.renewed' then handle_renewal(event.data)
end
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.
Os métodos de baixo nível continuam disponíveis:
# Levanta SignatureError se a assinatura estiver ausente ou inválida
AbacatePay::Webhooks.verify!(payload: payload, signature: signature, secret: secret)
# Contraparte booleana. Nunca levanta exceção
AbacatePay::Webhooks.valid?(payload: payload, signature: signature, secret: secret)
# Faz parse de um corpo já verificado
AbacatePay::Webhooks.parse(payload)
Eventos disponíveis
| Categoria | Eventos |
|---|---|
| Checkout | checkout.completed, checkout.refunded, checkout.disputed |
| Transparent | transparent.completed, transparent.refunded, transparent.disputed |
| Subscription | subscription.completed, subscription.renewed, subscription.cancelled, subscription.payment_failed, subscription.trial_started |
| Transfer | transfer.completed, transfer.failed |
| Payout | payout.completed, payout.failed |
Recursos
Todos os recursos são acessíveis pela fachada AbacatePay.<recurso>.
| Recurso | Métodos |
|---|---|
customers |
list get create delete |
products |
list get create delete |
coupons |
list get create delete toggle |
checkouts |
list get create refund |
subscriptions |
list create cancel change_plan record_usage |
transparents |
list create check simulate_payment refund |
pix |
list get send_pix |
payouts |
list get create |
store |
get merchant_info mrr revenue |
payment_links |
list get create refund |
webhook_endpoints |
list get create delete |
Clientes
AbacatePay.customers.list
AbacatePay.customers.get('cust_123')
AbacatePay.customers.delete('cust_123')
AbacatePay.customers.create(
AbacatePay::Resources::Customers.new(
metadata: AbacatePay::Resources::Customers::Metadata.new(
name: 'Abacate Lover',
cellphone: '01912341234',
email: 'lover@abacate.com',
tax_id: '13827826837'
)
)
)
Produtos
AbacatePay.products.create(
AbacatePay::Resources::Products.new(
external_id: 'my-product-1',
name: 'Monthly Plan',
price: 2990, # R$ 29,90 em centavos
currency: 'BRL',
description: 'Acesso a todos os recursos',
cycle: 'MONTHLY' # ou nil para pagamento único
)
)
Cupons
AbacatePay.coupons.create(
AbacatePay::Resources::Coupons.new(
code: 'SAVE20',
discount: 20,
discount_kind: 'PERCENTAGE', # ou 'FIXED'
max_redeems: 100
)
)
AbacatePay.coupons.toggle('coup_123')
Assinaturas
Exigem exatamente um produto com cycle definido.
AbacatePay.subscriptions.create(
AbacatePay::Resources::Subscriptions.new(
methods: ['PIX'],
customer: AbacatePay::Resources::Customers.new(id: 'cust_123'),
products: [
AbacatePay::Resources::Billings::Product.new(
external_id: 'plan-monthly',
name: 'Monthly Plan',
price: 2990,
quantity: 1
)
]
)
)
PIX transparente (QR Code)
AbacatePay.transparents.create(
AbacatePay::Resources::Transparents.new(
amount: 1000,
description: 'Pedido #123',
expires_in: 3600
)
)
AbacatePay.transparents.check('tr_123')
AbacatePay.transparents.simulate_payment('tr_123') # apenas em dev mode
Transferências PIX
AbacatePay.pix.send_pix(
AbacatePay::Resources::PixTransfers.new(
amount: 500,
external_id: 'transfer-001',
description: 'Pagamento ao fornecedor',
key: '12345678900',
key_type: 'CPF' # CPF, CNPJ, PHONE, EMAIL, RANDOM, BR_CODE
)
)
Saques
Valor mínimo de R$ 3,50.
AbacatePay.payouts.create(
AbacatePay::Resources::Payouts.new(
amount: 5000,
external_id: 'withdrawal-001',
description: 'Saque mensal'
)
)
Boleto
Boleto tem vencimento, juros e multa próprios. Todos os valores em centavos.
AbacatePay.checkouts.create(
AbacatePay::Resources::Checkouts.new(
methods: ['BOLETO'],
due_date: '2026-08-15', # opcional; default 3 dias úteis
interest: { value: 100 }, # juros ao mês
fine: { value: 200, type: 'PERCENTAGE' }, # ou type: 'FIXED'
products: [
AbacatePay::Resources::Billings::Product.new(external_id: 'prod_123', quantity: 1)
]
)
)
No checkout transparente, o boleto exige nome e CPF/CNPJ do pagador, o SDK valida antes de chamar a API:
charge = AbacatePay::Resources::Transparents.new(amount: 25_000, due_date: '2026-08-15')
# charge.customer precisa ter metadata.name e metadata.tax_id
boleto = AbacatePay.transparents.create(charge, method: 'BOLETO')
boleto. # linha digitável
boleto.url # PDF para impressão
boleto.br_code # PIX alternativo da mesma cobrança
Parcelamento e order bump
AbacatePay.checkouts.create(
AbacatePay::Resources::Checkouts.new(
methods: ['CARD'],
max_installments: 12,
up_sell_product_id: 'prod_bump',
custom_metadata: { origem: 'app-mobile' },
products: [
AbacatePay::Resources::Billings::Product.new(external_id: 'prod_123', quantity: 1)
]
)
)
Links de pagamento
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.
link = AbacatePay.payment_links.create(
AbacatePay::Resources::Checkouts.new(
methods: ['PIX', 'CARD'],
external_id: 'campanha-black-friday',
products: [
AbacatePay::Resources::Billings::Product.new(external_id: 'prod_123', quantity: 1)
]
)
)
puts link.url # compartilhe esta URL
Estornos
O estorno é sempre integral, a AbacatePay não faz estorno parcial.
AbacatePay.checkouts.refund('bill_abc123xyz')
AbacatePay.transparents.refund('pix_char_abc123xyz')
AbacatePay.payment_links.refund('char_abc123xyz')
Cancelar assinatura
Cancela imediatamente; parcelas futuras pendentes são canceladas junto.
AbacatePay.subscriptions.cancel('subs_abc123xyz')
# Upgrade/downgrade, vale a partir do próximo ciclo
AbacatePay.subscriptions.change_plan('subs_abc123xyz', product_id: 'prod_pro', quantity: 1)
# Cobrança por uso, produto sem ciclo
AbacatePay.subscriptions.record_usage('subs_abc123xyz', product_id: 'prod_api', units: 50)
Registro de webhooks
Isto gerencia para onde a AbacatePay entrega os eventos. Para verificar uma entrega recebida, use Webhooks.construct_event.
AbacatePay.webhook_endpoints.create(
name: 'Pagamentos',
endpoint: 'https://meusite.com/webhooks/abacatepay', # precisa ser HTTPS
secret: ENV['ABACATEPAY_WEBHOOK_SECRET'],
events: ['checkout.completed', 'subscription.renewed']
)
AbacatePay.webhook_endpoints.list
AbacatePay.webhook_endpoints.delete('wh_123')
Loja
store = AbacatePay.store.get
store.balance.available # => 10000
store.balance.pending # => 500
store.balance.blocked # => 0
AbacatePay.store.revenue(start_date: '2026-01-01', end_date: '2026-03-30')
Enums
Os valores são validados na construção do recurso, um valor inválido levanta ArgumentError antes de qualquer chamada de rede.
| Enum | Valores |
|---|---|
Billings::Methods |
PIX, CARD |
Billings::Frequencies |
ONE_TIME, WEEKLY, MONTHLY, SEMIANNUALLY, ANNUALLY, MULTIPLE_PAYMENTS |
Billings::Statuses |
PENDING, EXPIRED, CANCELLED, PAID, REFUNDED |
Products::Cycles |
WEEKLY, MONTHLY, SEMIANNUALLY, ANNUALLY |
Coupons::Statuses |
ACTIVE, INACTIVE, EXPIRED |
Coupons::DiscountKinds |
PERCENTAGE, FIXED |
Pix::KeyTypes |
CPF, CNPJ, PHONE, EMAIL, RANDOM, BR_CODE |
Transfers::Statuses |
PENDING, COMPLETE, CANCELLED, EXPIRED, REFUNDED, FAILED |
Payouts::Statuses |
PENDING, COMPLETE, CANCELLED, EXPIRED, REFUNDED |
Contribuindo
git clone https://github.com/AbacatePay/abacatepay-ruby-sdk.git
cd abacatepay-ruby-sdk
bundle install
bundle exec rake # specs + rubocop
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.
Licença
MIT
Feito com 🥑 pela equipe AbacatePay Open source, de verdade.