Bonaventure OgetoBy Bonaventure Ogeto|

Verify Paystack Payments in Ruby on Rails

To verify a Paystack payment in Rails, call the Paystack verify endpoint with your PaystackService, check status, amount, and currency against your Order model, and use Order.where(reference: ref, paid: false).update_all(paid: true) for idempotent fulfillment.

Verification Service Method

# app/services/paystack_service.rb (extended)
def verify_and_fulfill(reference)
  order = Order.find_by(reference: reference)
  return { error: 'Order not found' } unless order
  return { success: true, already_fulfilled: true } if order.paid?

  result = verify_transaction(reference)
  return { error: 'Verification failed' } unless result['status']
  return { error: 'Payment not successful' } unless result['data']['status'] == 'success'
  return { error: 'Amount mismatch' } if result['data']['amount'] != order.amount_kobo
  return { error: 'Currency mismatch' } if result['data']['currency'] != order.currency

  updated = Order.where(reference: reference, paid: false)
                 .update_all(paid: true, paid_at: Time.current)

  { success: true, already_fulfilled: updated == 0 }
end

Controller Usage

def callback
  reference = params[:reference] || params[:trxref]
  return redirect_to root_path, alert: 'Missing reference' unless reference

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

  if result[:success]
    render :success
  else
    flash[:error] = result[:error]
    render :failed
  end
end

Common Mistakes

  • Using update instead of update_all. update triggers callbacks and is not atomic for concurrent requests.
  • Not checking currency. Always verify the currency matches your model.
  • Mixing test and live keys. Both must be in the same mode.

Ship Payments Faster

Key Takeaways

  • Verification is a GET request to the Paystack API. Use your PaystackService for consistency.
  • Always check status, amount, and currency against your ActiveRecord model.
  • Use where(paid: false).update_all for atomic idempotent fulfillment.
  • Create a verify_and_fulfill method in your service for reuse across callbacks and webhooks.
  • Never trust URL parameters as proof of payment.
  • If verification fails due to network issues, retry. Transaction state does not change.

Frequently Asked Questions

Should I use update_all or update for fulfillment?
Use update_all with a where clause. It generates a single SQL UPDATE with a WHERE condition, making it atomic. Regular update loads the record first, leaving a window for race conditions.
Can I verify a transaction multiple times?
Yes. The Paystack verify endpoint is idempotent and returns the same result every time.
Should I verify in webhooks too?
Yes. Call verify_and_fulfill from both your callback action and webhook handler. The idempotent logic ensures only one fulfills the order.

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