To build Paystack subscriptions in Symfony, create a plan, initialize a transaction with the plan code, handle subscription webhooks to update a Doctrine entity, and use Security Voters to gate premium content behind active subscription checks.
// src/Entity/Subscription.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class Subscription
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\OneToOne(targetEntity: User::class)]
private User $user;
#[ORM\Column(length: 100, unique: true)]
private string $subscriptionCode;
#[ORM\Column(length: 100)]
private string $planCode;
#[ORM\Column(length: 100)]
private string $emailToken;
#[ORM\Column(length: 20)]
private string $status = 'active';
#[ORM\Column(nullable: true)]
private ?\DateTimeInterface $nextPaymentDate = null;
// Getters and setters...
}
#[Route('/subscribe', methods: ['POST'])]
public function subscribe(Request $request, PaystackService $paystack): Response
{
$user = $this->getUser();
$planCode = $request->request->get('plan_code');
$result = $paystack->initializeWithPlan($user->getEmail(), $planCode);
if ($result['status'] ?? false) {
return $this->redirect($result['data']['authorization_url']);
}
return new Response('Failed', 400);
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
private function handleSubscriptionCreate(array $data, EntityManagerInterface $em): void
{
$email = $data['customer']['email'];
$user = $em->getRepository(User::class)->findOneBy(['email' => $email]);
if (!$user) return;
$sub = $em->getRepository(Subscription::class)->findOneBy(['user' => $user]);
if (!$sub) {
$sub = new Subscription();
$sub->setUser($user);
}
$sub->setSubscriptionCode($data['subscription_code']);
$sub->setPlanCode($data['plan']['plan_code']);
$sub->setEmailToken($data['email_token'] ?? '');
$sub->setStatus('active');
$em->persist($sub);
$em->flush();
}
// src/Security/Voter/SubscriptionVoter.php
namespace App\Security\Voter;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class SubscriptionVoter extends Voter
{
protected function supports(string $attribute, mixed $subject): bool
{
return $attribute === 'ACTIVE_SUBSCRIPTION';
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
if (!$user) return false;
$sub = $user->getSubscription();
return $sub && $sub->getStatus() === 'active';
}
}
// In controller:
#[IsGranted('ACTIVE_SUBSCRIPTION')]
#[Route('/premium')]
public function premium(): Response
{
return $this->render('premium.html.twig');
}
#[Route('/subscription/cancel', methods: ['POST'])]
public function cancel(PaystackService $paystack, EntityManagerInterface $em): Response
{
$sub = $this->getUser()->getSubscription();
if (!$sub) {
return $this->redirectToRoute('subscribe');
}
$result = $paystack->disableSubscription($sub->getSubscriptionCode(), $sub->getEmailToken());
if ($result['status'] ?? false) {
$sub->setStatus('cancelled');
$em->flush();
}
return $this->redirectToRoute('dashboard');
}
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