<?php
namespace App\Controller;
use App\Entity\User;
use App\Entity\Subscription as Sub;
use App\Form\ProfileFormType;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Stripe\Checkout\Session;
use Stripe\Customer;
use Stripe\Event;
use Stripe\Exception\ApiErrorException;
use Stripe\Exception\SignatureVerificationException;
use Stripe\Exception\UnexpectedValueException;
use Stripe\Stripe;
use Stripe\Subscription;
use Stripe\Webhook;
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\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;
final class AccountController extends AbstractController
{
public function __construct(
private EntityManagerInterface $em,
private LoggerInterface $logger,
private string $monthlyPrice,
private string $yearlyPrice
) {
}
#[Route('/app/profile', name: 'profile')]
public function profile(Request $request, Security $security, EntityManagerInterface $em): Response
{
$user = $security->getUser();
$form = $this->createForm(ProfileFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$this->addFlash('info', 'Vos données sont mise à jour avec suucès');
$em->persist($user);
$em->flush();
return $this->redirectToRoute('profile');
}
return $this->render('app/profile.html.twig', ['form' => $form->createView()]);
}
#[Route('/app/order', name: 'order')]
public function order(): Response
{
return $this->render('app/order.html.twig');
}
#[Route('/app/checkout', name: 'checkout')]
public function checkout(Request $request, UrlGeneratorInterface $urlGenerator, Security $security, string $stripeSK): Response
{
$qte = $request->request->get('qte', 1);
$plan = $request->request->get('plan', 'monthly');
Stripe::setApiKey($stripeSK);
$session = Session::create([
'line_items' => [[
'price' => $plan == 'monthly' ? $this->monthlyPrice : $this->yearlyPrice,
'quantity' => $qte,
]],
'mode' => 'subscription',
'success_url' => $urlGenerator->generate('checkout_success', [], UrlGeneratorInterface::ABSOLUTE_URL),
'cancel_url' => $urlGenerator->generate('checkout_cancel', [], UrlGeneratorInterface::ABSOLUTE_URL),
'automatic_tax' => [
'enabled' => true,
],
'customer_email' => $security->getUser()->getEmail(),
]);
return $this->redirect($session->url, 303);
}
#[Route('/app/checkout-success', name: 'checkout_success')]
public function success(): Response
{
return $this->render('app/payment_success.html.twig');
}
#[Route('/app/checkout-cancel', name: 'checkout_cancel')]
public function cancel(): Response
{
return $this->render('app/payment_cancel.html.twig');
}
#[Route('/stripe-webhook', name: 'stripe_webhook')]
public function webhook(Request $request, string $stripeSK, string $stripeWebhookSecret): Response
{
$payload = $request->getContent();
try {
$event = Event::constructFrom(
json_decode($payload, true)
);
} catch(UnexpectedValueException $e) {
// Invalid payload
$this->logger->error('⚠️ Webhook error while parsing basic request.');
return $this->json([], Response::HTTP_BAD_REQUEST);
}
if ($stripeWebhookSecret) {
$sigHeader = $request->headers->get('stripe-signature');
try {
$event = Webhook::constructEvent(
$payload, $sigHeader, $stripeWebhookSecret
);
} catch(SignatureVerificationException $e) {
$this->logger->error('⚠️ Webhook error while validating signature.');
return $this->json([], Response::HTTP_BAD_REQUEST);
}
}
// Handle the event
switch ($event->type) {
case 'customer.subscription.created':
case 'customer.subscription.updated':
case 'customer.subscription.deleted':
$object = $event->data->object;
$this->logger->error(get_class($object));
$this->handlePaymentIntentSucceeded($object, $stripeSK);
break;
default:
// Unexpected event type
$this->logger->error('Received unknown event type');
}
return $this->json([]);
}
private function handlePaymentIntentSucceeded(Subscription $subscriptionObject, string $stripeSK): void
{
Stripe::setApiKey($stripeSK);
$customer = Customer::retrieve($subscriptionObject->customer);
$user = $this->em->getRepository(User::class)->findOneBy(['email' => $customer->email]);
$subscription = $this->em->getRepository(Sub::class)->findOneBy(['stripeSubId' => $subscriptionObject->id]) ?? new Sub();
$subscription->setStripeSubId($subscriptionObject->id);
$subscription->setStartAt((new \DateTimeImmutable())->setTimestamp($subscriptionObject->current_period_start));
$subscription->setEndAt((new \DateTimeImmutable())->setTimestamp($subscriptionObject->current_period_end));
$subscription->setAutoRenew(!$subscriptionObject->cancel_at_period_end);
$subscription->setPrice($subscriptionObject->plan->amount_decimal);
$subscription->setPlan($subscriptionObject->plan->interval);
$subscription->setQte($subscriptionObject->quantity);
$subscription->setCustomer($user);
$subscription->setStatus($subscriptionObject->status);
$this->em->persist($subscription);
$this->em->flush();
}
#[Route('/app/unsubscribe/{sub}', name: 'unsubscribe')]
public function unsubscribe(Security $security, Sub $sub, string $stripeSK): Response
{
try {
if ($security->getUser() !== $sub->getCustomer()) {
return $this->redirectToRoute('app_index');
}
Stripe::setApiKey($stripeSK);
Subscription::update($sub->getStripeSubId(), ['cancel_at_period_end' => true]);
$sub->setAutoRenew(false);
$this->em->persist($sub);
$this->em->flush();
$this->addFlash('info', 'Souscription mise à jour avec succès');
} catch (ApiErrorException $exception) {
$this->addFlash('error', $exception->getMessage());
return $this->redirectToRoute('app_index');
}
return $this->redirectToRoute('app_index');
}
#[Route('/app/resume/{sub}', name: 'resume')]
public function resume(Security $security, Sub $sub, string $stripeSK): Response
{
try {
if ($security->getUser() !== $sub->getCustomer()) {
return $this->redirectToRoute('app_index');
}
Stripe::setApiKey($stripeSK);
Subscription::update($sub->getStripeSubId(), ['cancel_at_period_end' => false]);
$sub->setAutoRenew(true);
$this->em->persist($sub);
$this->em->flush();
$this->addFlash('info', 'Souscription mise à jour avec succès');
} catch (ApiErrorException $exception) {
$this->addFlash('error', $exception->getMessage());
return $this->redirectToRoute('app_index');
}
return $this->redirectToRoute('app_index');
}
}