Accept Payments with Paystack in Symfony
To accept Paystack payments in Symfony, inject the HttpClientInterface, create a PaystackService that wraps API calls, and build controllers for initialization and callback handling. Use environment variables for your secret key and verify every transaction server-side.
Setup
Add your Paystack keys to .env:
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxx
Bind it as a parameter in config/services.yaml:
parameters:
paystack_secret_key: '%env(PAYSTACK_SECRET_KEY)%'
PaystackService
// src/Service/PaystackService.php
namespace App\Service;
use Symfony\Contracts\HttpClient\HttpClientInterface;
class PaystackService
{
private HttpClientInterface $client;
private string $secretKey;
public function __construct(HttpClientInterface $client, string $paystackSecretKey)
{
$this->client = $client;
$this->secretKey = $paystackSecretKey;
}
public function initializeTransaction(string $email, int $amountKobo, string $reference, string $callbackUrl): array
{
$response = $this->client->request('POST', 'https://api.paystack.co/transaction/initialize', [
'headers' => [
'Authorization' => 'Bearer ' . $this->secretKey,
'Content-Type' => 'application/json',
],
'json' => [
'email' => $email,
'amount' => $amountKobo,
'reference' => $reference,
'callback_url' => $callbackUrl,
],
]);
return $response->toArray();
}
public function verifyTransaction(string $reference): array
{
$response = $this->client->request('GET', 'https://api.paystack.co/transaction/verify/' . $reference, [
'headers' => [
'Authorization' => 'Bearer ' . $this->secretKey,
],
]);
return $response->toArray();
}
}
Payment Controller
// src/Controller/PaymentController.php
namespace App\Controller;
use App\Service\PaystackService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Uid\Uuid;
class PaymentController extends AbstractController
{
#[Route('/pay', methods: ['POST'])]
public function initialize(Request $request, PaystackService $paystack): Response
{
$email = $request->request->get('email');
$amount = (int) ($request->request->get('amount', 5000) * 100);
$reference = 'order_' . Uuid::v4()->toRfc4122();
// TODO: Save order to database
$result = $paystack->initializeTransaction(
$email, $amount, $reference,
$this->generateUrl('payment_callback', [], 0)
);
if ($result['status'] ?? false) {
return $this->redirect($result['data']['authorization_url']);
}
return new Response('Failed: ' . ($result['message'] ?? 'Unknown'), 400);
}
#[Route('/payment/callback', name: 'payment_callback')]
public function callback(Request $request, PaystackService $paystack): Response
{
$reference = $request->query->get('reference') ?? $request->query->get('trxref');
if (!$reference) {
return new Response('Missing reference', 400);
}
$result = $paystack->verifyTransaction($reference);
if (($result['status'] ?? false) && $result['data']['status'] === 'success') {
// TODO: Verify amount, mark order as paid
return $this->render('payment/success.html.twig', ['reference' => $reference]);
}
return $this->render('payment/failed.html.twig');
}
}
Service Configuration
# config/services.yaml
services:
App\Service\PaystackService:
arguments:
$paystackSecretKey: '%paystack_secret_key%'
Security Checklist
- Validate amounts server-side. Look up prices from your database.
- Set up webhooks. See Handle Paystack Webhooks in Symfony.
- Use HTTPS. Required for callback and webhook URLs.
- Verify every transaction. The redirect alone is not proof of payment.
Key Takeaways
- ✓Symfony HttpClient provides a clean API for calling the Paystack REST endpoints.
- ✓Store your Paystack keys in .env and bind them as service parameters.
- ✓Create a PaystackService that you inject into controllers via autowiring.
- ✓Amounts are in kobo (NGN), pesewas (GHS), or cents. Convert before sending.
- ✓Always verify the transaction server-side after the redirect callback.
- ✓No external packages needed. Symfony HttpClient handles all Paystack API calls.
Frequently Asked Questions
- Does Symfony need CSRF exemption for webhooks?
- Symfony does not have automatic CSRF protection on all POST routes like Laravel does. If you use the Symfony Form component, CSRF is form-specific. Your webhook route does not need special CSRF handling.
- Can I use Guzzle instead of Symfony HttpClient?
- Yes, but Symfony HttpClient is built into the framework and integrates better with dependency injection. Guzzle works fine too.
- How do I test PaystackService?
- Mock the HttpClientInterface in your unit tests. Symfony provides a MockHttpClient for this purpose.
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