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