How to add payments to your Rails app with Paystack

dngst

dngst

Posted on February 15, 2024

How to add payments to your Rails app with Paystack

This covers creating subscriptions.

When a user clicks on 'Billing', we check if the customer exists and if not create one. Next is to initialize a transaction. From the response, we get a Paystack link to the page where users can add payment option details. The callback handles the verification of the transaction. On successful verification, a subscription is created.

Docs

https://paystack.com/docs/api

Setup

PAYSTACK_SECRET_KEY can be found on your Paystack profile page on the API Keys & Webhooks tab.

Create a plan to get your PLAN_ID.

I’ve used the httparty gem to make API calls. Add it with:

$ bundle add httparty
Enter fullscreen mode Exit fullscreen mode

Routes

get '/subscribe', to: 'subscriptions#handle_payments'
get '/paystack_callback', to: 'subscriptions#paystack_callback'
Enter fullscreen mode Exit fullscreen mode

The view

<%= link_to "Billing", subscribe_path %>
Enter fullscreen mode Exit fullscreen mode

Making API calls

The Paystack service

class PaystackService
  include HTTParty
  base_uri 'https://api.paystack.co'

  def initialize(secret_key)
    @headers = {
      'Authorization' => "Bearer #{secret_key}",
      'Content-Type' => 'application/json',
      'Accept' => 'application/json'
    }
  end

  def create_customer(user)
    body = {
      first_name: user.fname,
      last_name: user.lname,
      phone: user.phone_number,
      email: user.email
    }.to_json

    self.class.post('/customer', headers: @headers, body:)
  end

  def initialize_transaction(user)
    body = {
      email: user.email,
      amount: 605.49 * 100
    }.to_json

    self.class.post('/transaction/initialize', headers: @headers, body:)
  end

  def verify_transaction(transaction_reference)
    self.class.get("/transaction/verify/#{transaction_reference}", headers: @headers)
  end

  def create_subscription(user, plan_id)
    customer_code = fetch_customer_details(user)['data']['customer_code']
    body = {
      customer: customer_code,
      plan: plan_id
    }.to_json

    self.class.post('/subscription', headers: @headers, body:)
  end

  def fetch_customer_details(user)
    self.class.get("/customer/#{user.email}", headers: @headers)
  end

  def get_subscription_code(user)
    fetch_customer_details(user)['data']['subscriptions'][0]['subscription_code']
  end

  def get_manage_subscription_link(user)
    subscription_code = get_subscription_code(user)
    response = self.class.get("/subscription/#{subscription_code}/manage/link", headers: @headers)
    response['data']['link']
  end
end
Enter fullscreen mode Exit fullscreen mode

Making use of the PaystackService

The subscriptions controller

class SubscriptionsController < ApplicationController
  before_action :initialize_paystack_service
  rescue_from SocketError, with: :handle_offline

  def handle_payments
    if customer_exists
      initialize_transaction
    else
      create_customer
    end
  end

  def customer_exists
    response = @paystack_service.fetch_customer_details(current_user)
    response['status']
  end

  def create_customer
    response = @paystack_service.create_customer(current_user)

    return unless response['status']

    initialize_transaction
  end

  def initialize_transaction
    response = @paystack_service.initialize_transaction(current_user)

    if response['status']
      payment_link = response['data']['authorization_url']
      redirect_to payment_link, allow_other_host: true
    else
      redirect_to user_path(current_user), alert: t('subscriptions.failed_to_initialize')
    end
  end

  def paystack_callback
    response = @paystack_service.verify_transaction(params[:reference])

    if response['status']
      create_subscription
    else
      redirect_to user_path(current_user), alert: t('subscriptions.failed_verification')
    end
  end

  def create_subscription
    response = @paystack_service.create_subscription(current_user, ENV.fetch('PLAN_ID', nil))

    if response['status']
      redirect_to user_path(current_user)
    else
      redirect_to user_path(current_user), alert: t('subscriptions.failed')
    end
  end

  def manage_subscription
    response = @paystack_service.get_manage_subscription_link(current_user)
    session[:manage_subscription_link] = response
    redirect_to user_path(current_user)
  end

  private

  def initialize_paystack_service
    @paystack_service = PaystackService.new(ENV.fetch('PAYSTACK_SECRET_KEY', nil))
  end

  def handle_offline
    redirect_to user_path(current_user), alert: t('subscriptions.offline')
  end
end
Enter fullscreen mode Exit fullscreen mode

To test from localhost you can use localtunnel.

$ npm install -g localtunnel
Enter fullscreen mode Exit fullscreen mode
$ lt --port 3000
Enter fullscreen mode Exit fullscreen mode

Set your callback URL as https://the-link-provided/paystack_callback

Whitelist the URL by adding it in development.rb

config.hosts << "the-link-provided.loca.lt"
Enter fullscreen mode Exit fullscreen mode

PaystackService gist

SubscriptionsController gist

💖 💪 🙅 🚩
dngst
dngst

Posted on February 15, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related