Bonaventure OgetoBy Bonaventure Ogeto|

Accept Payments with Paystack in Ruby on Rails

To accept Paystack payments in Rails, create a PaymentsController that uses Net::HTTP or the httparty gem to call the Paystack API. Initialize a transaction, redirect the customer to Paystack checkout, and verify the payment in a callback action.

Setup

Add your Paystack key to Rails credentials or .env:

# .env
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxx

Add the dotenv gem if using .env:

# Gemfile
gem 'dotenv-rails', groups: [:development, :test]

Paystack Service

# app/services/paystack_service.rb
require 'net/http'
require 'json'

class PaystackService
  BASE_URL = 'https://api.paystack.co'

  def initialize
    @secret_key = ENV['PAYSTACK_SECRET_KEY']
  end

  def initialize_transaction(email:, amount_kobo:, reference:, callback_url:)
    uri = URI(BASE_URL + '/transaction/initialize')
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    request = Net::HTTP::Post.new(uri)
    request['Authorization'] = 'Bearer ' + @secret_key
    request['Content-Type'] = 'application/json'
    request.body = {
      email: email,
      amount: amount_kobo,
      reference: reference,
      callback_url: callback_url
    }.to_json

    response = http.request(request)
    JSON.parse(response.body)
  end

  def verify_transaction(reference)
    uri = URI(BASE_URL + '/transaction/verify/' + reference)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true

    request = Net::HTTP::Get.new(uri)
    request['Authorization'] = 'Bearer ' + @secret_key

    response = http.request(request)
    JSON.parse(response.body)
  end
end

Payments Controller

# app/controllers/payments_controller.rb
class PaymentsController < ApplicationController
  def create
    email = params[:email]
    amount = params[:amount].to_f
    reference = "order_#{SecureRandom.hex(6)}"

    order = Order.create!(
      email: email,
      amount_kobo: (amount * 100).to_i,
      reference: reference
    )

    service = PaystackService.new
    result = service.initialize_transaction(
      email: email,
      amount_kobo: order.amount_kobo,
      reference: reference,
      callback_url: payment_callback_url
    )

    if result['status']
      redirect_to result['data']['authorization_url'], allow_other_host: true
    else
      flash[:error] = result['message'] || 'Failed'
      redirect_to checkout_path
    end
  end

  def callback
    reference = params[:reference] || params[:trxref]

    unless reference
      flash[:error] = 'Missing reference'
      return redirect_to root_path
    end

    service = PaystackService.new
    result = service.verify_transaction(reference)

    if result['status'] && result['data']['status'] == 'success'
      order = Order.find_by(reference: reference)

      if order && result['data']['amount'] == order.amount_kobo
        Order.where(reference: reference, paid: false).update_all(paid: true, paid_at: Time.current)
        render :success
      else
        render :failed
      end
    else
      render :failed
    end
  end
end

Routes

# config/routes.rb
Rails.application.routes.draw do
  post '/pay', to: 'payments#create'
  get '/payment/callback', to: 'payments#callback', as: :payment_callback
end

Order Model

rails generate model Order reference:string:uniq email:string amount_kobo:integer currency:string paid:boolean paid_at:datetime
rails db:migrate

Production Checklist

  1. Use Rails credentials. Store keys with rails credentials:edit instead of .env in production.
  2. Set up webhooks. See Handle Paystack Webhooks in Rails.
  3. HTTPS. Required for callback and webhook URLs.
  4. Validate amounts. Check against your database, not params.

Ship Payments Faster

Key Takeaways

  • Use Net::HTTP (stdlib) or the httparty gem for Paystack API calls. No Paystack gem is required.
  • Store your Paystack secret key in Rails credentials or environment variables.
  • Amounts are in kobo (NGN), pesewas (GHS), or cents. Multiply by 100 before sending.
  • Skip CSRF verification for webhook actions with skip_forgery_protection.
  • Always verify transactions server-side after the redirect callback.
  • Rails conventions (MVC, ActiveRecord) map cleanly to the Paystack payment flow.

Frequently Asked Questions

Do I need a Paystack Ruby gem?
No. Net::HTTP in the Ruby standard library handles all the API calls. You can also use the httparty or faraday gems for a cleaner API, but they are not required.
Can I use Rails API mode with Paystack?
Yes. In API mode, return JSON responses instead of rendering views. The Paystack API calls are identical.
How do I handle CSRF for webhooks?
Add skip_forgery_protection to your webhook controller action. Paystack requests do not include Rails CSRF tokens.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon