Verify Paystack Payments in Symfony
To verify a Paystack payment in Symfony, use your PaystackService to GET the verify endpoint. Check status, amount, and currency against your Doctrine Order entity. Use DQL UPDATE with a condition on paid=false for idempotent fulfillment.
Verification Service Method
// In PaystackService
public function verifyAndFulfill(string $reference, EntityManagerInterface $em): array
{
$order = $em->getRepository(Order::class)->findOneBy(['reference' => $reference]);
if (!$order) {
return ['success' => false, 'error' => 'Order not found'];
}
if ($order->isPaid()) {
return ['success' => true, 'already_fulfilled' => true];
}
$result = $this->verifyTransaction($reference);
if (!($result['status'] ?? false) || $result['data']['status'] !== 'success') {
return ['success' => false, 'error' => 'Payment not successful'];
}
if ($result['data']['amount'] !== $order->getAmount()) {
return ['success' => false, 'error' => 'Amount mismatch'];
}
// Atomic update
$updated = $em->createQueryBuilder()
->update(Order::class, 'o')
->set('o.paid', true)
->set('o.paidAt', ':now')
->where('o.reference = :ref')
->andWhere('o.paid = false')
->setParameter('ref', $reference)
->setParameter('now', new \DateTime())
->getQuery()
->execute();
return [
'success' => true,
'already_fulfilled' => $updated === 0,
];
}
Controller Usage
#[Route('/payment/callback')]
public function callback(Request $request, PaystackService $paystack, EntityManagerInterface $em): Response
{
$reference = $request->query->get('reference') ?? $request->query->get('trxref');
if (!$reference) {
return new Response('Missing reference', 400);
}
$result = $paystack->verifyAndFulfill($reference, $em);
if ($result['success']) {
return $this->render('payment/success.html.twig', ['reference' => $reference]);
}
return $this->render('payment/failed.html.twig', ['error' => $result['error']]);
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Common Mistakes
- Using $em->flush() without atomic conditions. Use DQL UPDATE with a WHERE clause instead.
- Not checking currency. Always verify the currency matches your entity.
- Trusting request parameters. Verify against your database, not URL params.
Key Takeaways
- ✓Extend your PaystackService with a verifyAndFulfill method that checks amount, currency, and status.
- ✓Use Doctrine DQL or QueryBuilder for atomic updates that prevent double-granting.
- ✓Both callback and webhook handlers should call the same verification service method.
- ✓Always check amount and currency against your Doctrine entity, not request parameters.
- ✓If the verify call fails, retry with exponential backoff.
- ✓The Paystack verify endpoint is idempotent. Calling it multiple times returns the same result.
Frequently Asked Questions
- Can I use the Symfony Messenger component for async verification?
- Yes. Dispatch a message from your webhook handler and process it asynchronously. The idempotent verification logic ensures safety.
- Should I verify in the webhook handler?
- Yes. Both callback and webhook should call verifyAndFulfill. The atomic update ensures only one fulfills the order.
- How do I handle verification timeouts?
- Set a timeout on the HttpClient request. If it times out, retry. The transaction state on Paystack does not change.
Learn Paystack Integration
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course