<?php
namespace App\Controller\CompanySpace;
use App\Entity\Abonnement;
use App\Entity\Company;
use App\Entity\Paiement;
use App\Repository\AbonnementRepository;
use App\Repository\CompanyRepository;
use App\Repository\PaiementRepository;
use App\Repository\TypeAbonnementRepository;
use App\Services\StripeSubscriptionService;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class StripeSubscriptionController extends AbstractController
{
private $stripeSubscriptionService;
public function __construct(StripeSubscriptionService $stripeSubscriptionService)
{
$this->stripeSubscriptionService = $stripeSubscriptionService;
}
/**
* @Route("/subscription/stripe/main", name="stripe_subscription_main")
*/
public function index(CompanyRepository $companyRepository): Response
{
return $this->render('company_space/stripe_subscription/index.html.twig', [
'company' => $companyRepository->Find($this->getUser())
]);
}
/**
* @Route("/subscription/stripe/type", name="stripe_subscription_type")
*/
public function subscriptionType(TypeAbonnementRepository $typeAbonnementRepository, AbonnementRepository $abonnementRepository): Response
{
$typeAbonnements = $typeAbonnementRepository->findBy([], ['prix' => 'ASC'] );
$activeSub = $abonnementRepository->findOneBy(['company' => $this->getUser(), 'isActive' => true]);
return $this->render('company_space/stripe_subscription/type.html.twig', [
'typeAbonnements' => $typeAbonnements,
'activeSub' => $activeSub,
'company' => $this->getUser()
]);
}
/**
* @Route("/subscription/stripe/return-url", name="stripe_subscription_return_url")
*/
public function stripeSubscriptionReturnUrl(
Request $request,
LoggerInterface $logger,
AbonnementRepository $abonnementRepository,
EntityManagerInterface $entityManager,
TypeAbonnementRepository $typeAbonnementRepository,
CompanyRepository $companyRepository
): Response
{
$checkoutSessionId = $request->get('checkout_session');
$checkoutSession = $this->stripeSubscriptionService->getCheckoutSession($checkoutSessionId);
$subscriptionStripe = $this->stripeSubscriptionService->getSubscription($checkoutSession->subscription);
$subscriptionInvoice = $this->stripeSubscriptionService->getInvoice($checkoutSession->invoice);
if ($checkoutSession->payment_status !== 'paid' || $subscriptionInvoice->status !== 'paid') {
$this->addFlash('danger', 'Le paiement n\'a pas été effectué. Veuillez réessayer.');
}
$session = $checkoutSession;
$subscriptionId = $session->subscription;
if ($subscriptionId === null) {
$this->addFlash('danger', 'Aucune souscription trouvée. Veuillez réessayer.');
$logger->info('Webhook Stripe no subscription');
exit();
}
// Get subscription
$typeAbonnementId = $subscriptionStripe->plan->id;
// Get user
// TODO: pré-remplir le champ email dans le formulaire de souscription
$customerEmail = $session->customer_details->email;
/** @var Company $user */
$user = $this->getUser() != null ? $this->getUser() : $companyRepository->findOneBy(['email' => $customerEmail]);
if (!$user) {
throw new \Exception('Utilisateur non trouvé');
// $logger->info('Webhook Stripe user not found');
//Ajouter un message flash afin d'informer l'utilisateur que l'adresse e-mail ne correspond pas à un compte existant
http_response_code(404);
exit();
}
// Disable old subscription
$activeSub = $abonnementRepository->findOneBy(['company' => $user, 'isActive' => true]);
if ($activeSub) {
try {
$this->stripeSubscriptionService->cancelSubscription($activeSub->getStripeSubscriptionId());
} catch (\Throwable $th) {
$logger->info('Abonnement n°:' . $activeSub->getId() . ' non trouvé pour l\'utilisateur avec l\'e-mail: ' . $user->getEmail());
}
$activeSub->setIsActive(false);
$entityManager->persist($activeSub);
}
// Get plan
$typeAbonnement = $typeAbonnementRepository->findOneBy(['stripeID' => $typeAbonnementId]);
if (!$typeAbonnement) {
$logger->info('Webhook Stripe plan not found');
throw new Exception('Plan not found for Stripe ID: ' . $typeAbonnementId);
}
$subscription = new Abonnement();
$subscription->setTypeAbonnement($typeAbonnement);
$subscription->setStripeSubscriptionId($subscriptionStripe->id);
$logger->info('Stripe subscription start: ' . $subscriptionStripe->current_period_start);
$logger->info('Stripe subscription end: ' . $subscriptionStripe->current_period_end);
$start = new \DateTimeImmutable();
$subscription->setCreatedAt($start);
$endPrototype = clone $start;
$end = $endPrototype->modify('+1 month');
$subscription->setNextUpdate($end);
$subscription->setCompany($user);
$subscription->setIsActive(true);
$user->setStripeId($session->customer);
$entityManager->persist($subscription);
$entityManager->flush();
$invoice = new Paiement();
$invoice->setPayinId($subscriptionInvoice->id);
$invoice->setStripeInvoiceID($subscriptionInvoice->id);
$invoice->setAbonnement($subscription);
$invoice->setStripeNumber($subscriptionInvoice->number);
$invoice->setStripeAmountPaid($subscriptionInvoice->amount_paid);
// Hosted invoice url is now generated by formator
$invoice->setHostedInvoiceUrl($subscriptionInvoice->hosted_invoice_url);
$invoice->setPaid(true);
$entityManager->persist($invoice);
$entityManager->flush();
$this->addFlash('success', 'Votre abonnement a été créé avec succès !');
return $this->redirectToRoute('stripe_subscription_details', ['id' => $user->getId()]);
}
/**
* @Route("/subscription/stripe/webhook", name="stripe_subscription_webhook")
*/
public function webhook(
LoggerInterface $logger,
AbonnementRepository $abonnementRepository,
EntityManagerInterface $entityManager,
PaiementRepository $paiementRepository
): Response {
\Stripe\Stripe::setApiKey($this->getParameter('stripe_sk'));
$event = null;
// Check request
$endpoint_secret = $this->getParameter('stripe_webhook_secret');
$payload = @file_get_contents('php://input');
$sig_header = $_SERVER['HTTP_STRIPE_SIGNATURE'];
try {
$event = \Stripe\Webhook::constructEvent(
$payload,
$sig_header,
$endpoint_secret
);
} catch (\UnexpectedValueException $e) {
// Invalid payload
$logger->info('Webhook Stripe Invalid payload');
http_response_code(400);
exit();
} catch (\Stripe\Exception\SignatureVerificationException $e) {
// Invalid signature
$logger->info('Webhook Stripe Invalid signature');
http_response_code(403);
exit();
}
// Handle the event
switch ($event->type) {
case 'checkout.session.completed':
$logger->info('Webhook Stripe connect checkout.session.completed');
break;
case 'invoice.paid':
$logger->info('Invoice paid');
$subscriptionId = null;
if ($event->data->object->parent !== null) {
$subscriptionId = $event->data->object->parent->subscription_details->subscription;
}
if ($subscriptionId === null) {
$logger->info('No subscription');
return new JsonResponse([
'status' => 'error',
'message' => 'No Stripe subscription found'
], 200);
}
$subscription = null;
for ($i = 0; $i <= 4 && $subscription === null; $i++) {
$subscription = $abonnementRepository->findOneBy(['stripeSubscriptionId' => $subscriptionId]);
if ($subscription) {
break;
}
sleep(5);
}
if (!$subscription) {
$logger->info('Subscription not found');
return new Response('Non-existent subscription', 404);
}
$paiementExisting = $paiementRepository->findOneBy(['stripeInvoiceID' => $event->data->object->id]);
if ($paiementExisting) {
$logger->info('Paiement already exists for invoice: ' . $event->data->object->id);
return new Response('Paiement already exists', 200);
}
$invoice = new Paiement();
$invoice->setPayinId($event->data->object->id);
$invoice->setStripeInvoiceID($event->data->object->id);
$invoice->setAbonnement($subscription);
$invoice->setStripeNumber($event->data->object->number);
$invoice->setStripeAmountPaid($event->data->object->amount_paid);
// Hosted invoice url is now generated by formator
$invoice->setHostedInvoiceUrl($event->data->object->hosted_invoice_url);
$invoice->setPaid(true); // Set paid to true as the invoice is paid
$current_subscription_next_update = $subscription->getNextUpdate();
// Update next update date
$next_update = $current_subscription_next_update->modify('+1 month');
$subscription->setNextUpdate($next_update);
if ($subscription->getIsActive() === false) {
$subscription->setIsActive(true);
}
$entityManager->persist($subscription);
$entityManager->persist($invoice);
$entityManager->flush();
break;
case 'invoice.payment_succeeded':
$logger->info('Invoice paid');
$subscriptionId = null;
if ($event->data->object->parent !== null) {
$subscriptionId = $event->data->object->parent->subscription_details->subscription;
}
if ($subscriptionId === null) {
$logger->info('No subscription');
return new JsonResponse([
'status' => 'error',
'message' => 'No Stripe subscription found'
], 200);
}
$subscription = null;
for ($i = 0; $i <= 4 && $subscription === null; $i++) {
$subscription = $abonnementRepository->findOneBy(['stripeSubscriptionId' => $subscriptionId]);
if ($subscription) {
break;
}
sleep(5);
}
if (!$subscription) {
$logger->info('Subscription not found');
return new JsonResponse([
'status' => 'error',
'message' => 'Non-existent subscription'
], 404);
}
$paiementExisting = $paiementRepository->findOneBy(['stripeInvoiceID' => $event->data->object->id]);
if ($paiementExisting) {
$logger->info('Paiement already exists for invoice: ' . $event->data->object->id);
return new Response('Paiement already exists', 200);
}
$invoice = new Paiement();
$invoice->setPayinId($event->data->object->id);
$invoice->setStripeInvoiceID($event->data->object->id);
$invoice->setAbonnement($subscription);
$invoice->setStripeNumber($event->data->object->number);
$invoice->setStripeAmountPaid($event->data->object->amount_paid);
// Hosted invoice url is now generated by formator
$invoice->setHostedInvoiceUrl($event->data->object->hosted_invoice_url);
$invoice->setPaid(true); // Set paid to true as the invoice is paid
$current_subscription_next_update = $subscription->getNextUpdate();
// Update next update date
$next_update = $current_subscription_next_update->modify('+1 month');
$subscription->setNextUpdate($next_update);
if ($subscription->getIsActive() === false) {
$subscription->setIsActive(true);
}
$entityManager->persist($subscription);
$entityManager->persist($invoice);
$entityManager->flush();
break;
case 'invoice.payment_failed':
$logger->info('invoice.payment_failed event');
$subscriptionId = null;
if ($event->data->object->parent !== null) {
$subscriptionId = $event->data->object->parent->subscription_details->subscription;
}
if ($subscriptionId === null) {
$logger->info('No subscription for invoice.payment_failed');
return new JsonResponse([
'status' => 'error',
'message' => 'No Stripe subscription found'
], 200);
}
$subscription = $abonnementRepository->findOneBy(['stripeSubscriptionId' => $subscriptionId]);
if (!$subscription) {
$logger->info('Subscription not found for invoice.payment_failed');
return new Response('Non-existent subscription', 404);
}
$invoice = new Paiement();
$invoice->setPayinId($event->data->object->id);
$invoice->setStripeInvoiceID($event->data->object->id);
$invoice->setAbonnement($subscription);
$invoice->setStripeNumber($event->data->object->number);
$invoice->setStripeAmountPaid($event->data->object->amount_paid);
// Hosted invoice url is now generated by formator
$invoice->setHostedInvoiceUrl($event->data->object->hosted_invoice_url);
$invoice->setPaid(false); // Set paid to false as the invoice payment failed
if ($subscription->getIsActive() === true) {
$subscription->setIsActive(false);
}
$entityManager->persist($subscription);
$entityManager->persist($invoice);
$entityManager->flush();
default:
// Unexpected event type
$logger->info('Unexpected event type: ' . $event->type);
http_response_code(400);
exit();
}
http_response_code(200);
$response = new Response('success');
$response->headers->set('Content-Type', 'application/json');
return $response;
}
/**
* @Route("/subscription/stripe/details/{id}", name="stripe_subscription_details")
*/
public function stripeSubscriptionDetails(Company $company, PaiementRepository $paiementRepository)
{
//Si l'id de l'utilisateur en cours n'est pas celui de l'utilisateur recherché
if ($company->getId() !== $this->getUser()->getId()) {
return $this->redirectToRoute('front_company_space_dashboard');
}
$userSubscription = null;
if ($company->getAbonnements()->count() > 0) {
$userReccurringPaymentRegistrationId = $company->getAbonnements()->last()->getStripeSubscriptionId();
$userSubscription = $this->stripeSubscriptionService->getSubscription($userReccurringPaymentRegistrationId); //129585116
} else {
$this->addFlash('danger', "Vous n'avez pas encore d'abonnement !!");
return $this->redirectToRoute('prestat_abn');
}
$paiements = $paiementRepository->findBy(['abonnement' => $company->getAbonnements()->last()], ['id' => 'DESC']);
return $this->render('company_space/stripe_subscription/subscription_details.html.twig', [
'company' => $company,
'subscription' => $userSubscription,
'paiements' => $paiements
]);
}
/**
* @Route("/subscription/stripe/cancel/{id}", name="stripe_subscription_cancel")
*/
public function stripeSubscriptionCancel(Request $request, Company $company, EntityManagerInterface $entityManager)
{
if (!$this->getUser() ) {
return $this->redirectToRoute('front_home');
}
//Si l'id de l'utilisateur en cours n'est pas celui de l'utilisateur recherché
if ($company->getId() !== $this->getUser()->getId())
{
return $this->redirectToRoute('front_company_space_dashboard');
}
if (!$this->isCsrfTokenValid('unsubscribe' . $company->getId(), $request->request->get('token'))) {
return $this->redirectToRoute('front_company_space_dashboard');
}
if ($company->getAbonnements()->count() > 0) {
$subscription = $company->getAbonnements()->last();
$stripeSubscriptionObject = $this->stripeSubscriptionService->getSubscription($subscription->getStripeSubscriptionId());
if ($stripeSubscriptionObject != null) {
if ($stripeSubscriptionObject->status != 'canceled') {
try {
$this->stripeSubscriptionService->cancelSubscription($subscription->getStripeSubscriptionId());
$subscription->setIsActive(false);
$entityManager->persist($subscription);
$entityManager->flush();
$this->addFlash('success', 'Votre abonnement a été annulé avec succès.');
} catch (\Throwable $th) {
$this->addFlash('danger', "Une erreur est survenue lors de l'annulation de votre abonnement sur Stripe. ");
};
} else {
$subscription->setIsActive(false);
$entityManager->persist($subscription);
$entityManager->flush();
$this->addFlash('danger', "Votre abonnement a déjà été annulé.");
}
}
} else {
$this->addFlash('danger', "Vous n'avez pas d'abonnement actif à annuler.");
}
return $this->redirectToRoute('stripe_subscription_details', ['id' => $company->getId()]);
}
}