Bonaventure OgetoBy Bonaventure Ogeto|

Build Paystack Subscriptions in Ruby on Rails

To build Paystack subscriptions in Rails, create a plan via the API, initialize a transaction with the plan code, handle subscription webhooks in ActiveJob, track status in an ActiveRecord model, and use before_action filters to gate premium content.

Subscription Model

rails generate model Subscription user:references subscription_code:string:uniq plan_code:string email_token:string status:string next_payment_date:datetime
rails db:migrate

Subscribe Action

def subscribe
  service = PaystackService.new
  result = service.initialize_with_plan(
    email: current_user.email,
    plan_code: params[:plan_code],
    callback_url: subscription_callback_url
  )

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

Webhook Handlers

def handle_subscription_create(data)
  user = User.find_by(email: data['customer']['email'])
  return unless user

  Subscription.find_or_initialize_by(user: user).update!(
    subscription_code: data['subscription_code'],
    plan_code: data['plan']['plan_code'],
    email_token: data['email_token'] || '',
    status: 'active',
    next_payment_date: data['next_payment_date']
  )
end

def handle_invoice_failed(data)
  sub_code = data.dig('subscription', 'subscription_code')
  Subscription.where(subscription_code: sub_code).update_all(status: 'past_due') if sub_code
end

Access Control

# app/controllers/concerns/subscription_required.rb
module SubscriptionRequired
  extend ActiveSupport::Concern

  included do
    before_action :require_active_subscription
  end

  private

  def require_active_subscription
    unless current_user&.subscription&.status == 'active'
      redirect_to pricing_path, alert: 'An active subscription is required.'
    end
  end
end

# In premium controllers:
class PremiumController < ApplicationController
  include SubscriptionRequired
end

Cancellation

def cancel
  sub = current_user.subscription
  return redirect_to dashboard_path, alert: 'No subscription' unless sub

  service = PaystackService.new
  result = service.disable_subscription(sub.subscription_code, sub.email_token)

  if result['status']
    sub.update!(status: 'cancelled')
    redirect_to dashboard_path, notice: 'Subscription cancelled'
  else
    redirect_to dashboard_path, alert: 'Cancellation failed'
  end
end

Ship Payments Faster

Key Takeaways

  • Paystack handles recurring billing. Create plans once and subscribe customers.
  • Use ActiveRecord for subscription tracking with status, subscription_code, and email_token.
  • Webhook events drive state changes. Handle subscription.create and invoice.payment_failed.
  • Use before_action to gate premium controllers behind subscription checks.
  • Store email_token for cancellation via the API.
  • ActiveJob handles async webhook processing with Sidekiq or another backend.

Frequently Asked Questions

Can I use Stripe-like subscription gems with Paystack?
No. Gems like Pay and Cashier are designed for Stripe. Build Paystack subscriptions directly using the API and ActiveRecord as shown in this guide.
How do I handle plan upgrades?
Cancel the current subscription and create a new one on the new plan. Handle prorating in your Rails application.
What if a renewal payment fails?
Paystack retries automatically. You receive invoice.payment_failed webhooks. Update the status and notify the customer.

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