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.

AbacatePay Open Source

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, em segundos
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')

Versionamento

O SDK fala exclusivamente a v2https://api.abacatepay.com/v2. A v1 foi desligada pela AbacatePay e responde {"error":"Not found"} em toda rota, então não há o que negociar.

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. Ele emite um aviso ao ser instanciado, e seus endpoints /billings/* não existem na v2:

[DEPRECATION] BillingClient is deprecated. Use CheckoutClient instead.

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.message)
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 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.

payload   = request.body.read
signature = request.headers['X-Webhook-Signature']
secret    = ENV['ABACATEPAY_WEBHOOK_SECRET']

begin
  event = AbacatePay::Webhooks.construct_event(
    payload: payload, signature: signature, secret: secret
  )
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
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
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'
  )
)

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')

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.