<?php
namespace App\Controller\GroupSessionController;
use App\Entity\Child;
use App\Entity\Gender;
use App\Entity\Session;
use App\Entity\SessionOrder;
// use App\Entity\StripePlanForGroup; // StripePlanForGroup no longer needed — Stripe price created dynamically
use App\Entity\Subject;
use App\Entity\TrainingReport;
use App\Entity\User;
use App\Notification\EmailNotification;
use App\Notification\SlackClient;
use App\Order\OrderHelper;
use App\Repository\DiscountRepository;
use App\Repository\HolidayRepository;
use App\Repository\LevelTestRepository;
use App\Repository\SessionRegistrationDateRepository;
use App\Repository\SessionRepository;
// use App\Repository\StripePlanForGroupRepository; // StripePlanForGroup no longer needed
use App\Repository\SubjectRepository;
use App\Service\ActiveCampaignHelper;
use App\Service\Currency\CurrencyChangeService;
use App\Service\Stripe\StripeMultiCurrencyService;
use App\StripeClient;
use Doctrine\ORM\EntityManagerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Stripe\Invoice;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session as SessionSymfo;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/session")
*/
class UserGroupSessionController extends AbstractController
{
private $em;
private $stripeClient;
private $emailNotification;
private $slackClient;
private $activeCampaignHelper;
private $stripeMultiCurrencyService;
public function __construct(EntityManagerInterface $em, StripeClient $stripeClient, EmailNotification $emailNotification, SlackClient $slackClient,
ActiveCampaignHelper $activeCampaignHelper, StripeMultiCurrencyService $stripeMultiCurrencyService
) {
$this->em = $em;
$this->stripeClient = $stripeClient;
$this->emailNotification = $emailNotification;
$this->slackClient = $slackClient;
$this->activeCampaignHelper = $activeCampaignHelper;
$this->stripeMultiCurrencyService = $stripeMultiCurrencyService;
}
//Voir
/**
* @Route("/{gender}/{tag}", name="session_index", defaults={"tag": "alKaamil"}, methods={"GET"})
* @ParamConverter("gender", options={"mapping": {"gender" : "name"}})
* @param Gender $gender
* @param $tag
* @param SubjectRepository $subjectRepository
* @param SessionRepository $sessionRepository
* @param HolidayRepository $holidayRepository
* @param SessionRegistrationDateRepository $sessionRegistrationDateRepository
* @param LevelTestRepository $levelTestRepository
*
* @return Response
*/
public function index(
Gender $gender,
$tag,
SubjectRepository $subjectRepository,
SessionRepository $sessionRepository,
HolidayRepository $holidayRepository,
SessionRegistrationDateRepository $sessionRegistrationDateRepository,
LevelTestRepository $levelTestRepository,
Request $request,
DiscountRepository $discountRepository
): Response {
$parameter = $request->query->get('d');
$discount = $discountRepository->findOneBy([
'urlParameter' => $parameter,
]);
$subject = $subjectRepository->findOneBy([
'tagName' => $tag,
]);
if ($discount) {
if ($discount->getSubject() == $subject) {
$isDiscount = true;
$discountAmount = $discount->getAmount();
$discountType = $discount->getType();
} else {
$isDiscount = false;
$discountAmount = 0;
$discountType = null;
}
} else {
$isDiscount = false;
$discountAmount = 0;
$discountType = null;
}
if ( ! $subject) {
$this->addFlash('error', "Le programme demandé n'existe pas");
return $this->redirectToRoute('session_index', [
'gender' => $gender,
'_locale' => $request->getLocale(),
]);
}
$sessions = $sessionRepository->openSessionByOrderSubjectGender(
$subject->getTagName(),
$gender->getName()
);
$holidays = $holidayRepository->findAll();
$registrationDate = $sessionRegistrationDateRepository->findOneBy([
'subject' => $subject,
]);
$levelTestInfo = $levelTestRepository->findOneBy([
'type' => "info",
'subject' => $subject,
]);
return $this->render('session/index.html.twig', [
'sessions' => $sessions,
'gender' => $gender,
'local' => $this->getParameter('locale'),
'holidays' => $holidays,
'subject' => $subject,
'registrationDate' => $registrationDate,
'levelTest' => $levelTestInfo,
'isDiscount' => $isDiscount,
'discountAmount' => $discountAmount,
'discountType' => $discountType,
'parameter' => $parameter,
]);
}
//Acheter
/**
* @Route("/order/purchase/{id}", name="session_purchase", requirements={"id":"\d+"}), methods={"GET","POST"})
* @ParamConverter("session", options={"id" : "id"})
* @Security("is_granted('ROLE_USER')")
* @param Session $session
* @param Request $request
* @param OrderHelper $orderHelper
* @param SessionSymfo $sessionSymfo
* @param StripePlanForGroupRepository $stripePlanForGroupRepository
*
* @return Response
*/
public function purchase(Session $session, Request $request, OrderHelper $orderHelper, SessionSymfo $sessionSymfo,
DiscountRepository $discountRepository, CurrencyChangeService $currencyChangeService
): Response {
if ($session->getIsClose()) {
$this->addFlash('error', 'Les inscriptions pour cette session sont fermées.');
$referer = $request->headers->get('referer');
if ($referer) {
return $this->redirect($referer);
}
return $this->redirectToRoute('user_dashboard', ['_locale' => $request->getLocale()]);
}
$isDiscount = false;
$sessionPrice = $session->getPrice();
$parameter = $request->query->get('d');
$discount = $discountRepository->findOneBy([
'urlParameter' => $parameter,
]);
if ($discount && $discount->getSubject() == $session->getLevel()->getSubject()) {
$isDiscount = true;
$discountAmount = $discount->getAmount();
$discountType = $discount->getType();
if ($discountType == 'fixe') {
$sessionPrice = $sessionPrice - $discountAmount;
} else {
$deduction = ($sessionPrice - $discountAmount) / 100;
$sessionPrice = $sessionPrice - $deduction;
}
}
// StripePlanForGroup no longer needed — Stripe price is created dynamically in createSubscription()
// The stripeId from StripePlanForGroup was unused in the multicurrency flow
$planId = null;
$user = $this->getUser();
$gender = $session->getTeacher()->getUserId()->getGender();
if ($sessionSymfo->get('code-group')) {
$stripeCoupon = $sessionSymfo->get('code-group');
$code = $stripeCoupon->amount_off / 100;
} else {
$stripeCoupon = false;
$code = 0;
}
$currencyInfo = $currencyChangeService->getCurrencyChangeForView($user);
$currency = $currencyInfo['currencyCode'] ? $currencyInfo['currencyCode'] : 'EUR';
$convertedAmount = null;
if ($currency !== 'EUR') {
try {
$convertedAmount = $currencyChangeService->convertAmount($sessionPrice, $currency);
} catch (\Exception $e) {
// If conversion fails, fallback to EUR
$currency = 'EUR';
$convertedAmount = $sessionPrice;
}
} else {
$convertedAmount = $sessionPrice;// If currency is EUR, use the base price
}
$error = false;
if ($request->isMethod('POST')) {
$token = $request->request->get('stripeToken');
$participant = $request->request->get('child-select');
$paymentType = $request->request->get('paymentType');
if ($session->getSeat() > 0) {
try {
if ($paymentType == "twoTime") {
// Paiement en deux fois avec support multicurrency : montant par échéance = total / 2
$amountPerInstallment = $convertedAmount / 2;
$invoice = $orderHelper->chargeCustomerForInstallement(
$token,
$user,
$planId,
$currency,
$amountPerInstallment,
$stripeCoupon
);
} else {
if ($session->getPrice() == 0) {
// Free session - set currency info from user's currency
$paymentInfo = [
'currency' => $currency,
'amountPaid' => 0,
'amountReceivedUsd' => null,
'fees' => 0,
];
$sessionOrder = $this->addSessionOrderToBdd($session, $user, "gratuit", 0, null, null, null, null, false, $participant, $paymentInfo);
$this->addFlash('success', 'Inscription completée ! Un mail vous a été envoyé, vérifiez vos courriers indésirables (SPAM) si vous ne l\'avez pas reçu.');
return $this->redirectToRoute(
'confirmation_purchase',
[
'userId' => $user->getId(),
'orderType' => 'group',
'orderId' => $sessionOrder->getId(),
'_locale' => $request->getLocale(),
]
);
} else {
$invoice = $orderHelper->chargeCustomerSession($token, $user, $session, $stripeCoupon, $convertedAmount,$currency);
}
}
} catch (\Stripe\Exception\CardException $e) {
//error_log("A payment error occurred: {$e->getError()->message}");
$error = 'Il y a un problème pour charger votre carte: '.$e->getError()->message;
} catch (\Stripe\Exception\InvalidRequestException $e) {
$error = 'Il y a un problème pour charger votre carte: '.$e->getError()->message;
} catch (\Exception $e) {
//error_log("Another problem occurred, maybe unrelated to Stripe.");
$error = 'Il y a un problème pour charger votre carte: '.$e;
}
if ( ! $error and $invoice->paid == true) {
$sessionOrder = $this->prepareCreationSessionOrderWithInvoice($invoice, $user, $session, $participant, $paymentType);
$this->addFlash('success', 'Inscription completée ! Un mail vous a été envoyé, vérifiez vos courriers indésirables (SPAM) si vous ne l\'avez pas reçu.');
$this->activeCampaignHelper->addOrder($sessionOrder);
$this->activeCampaignHelper->updateCustomFieldLastLevelTestForContact($sessionOrder->getStudents()->getEmail(), $session);
return $this->redirectToRoute(
'confirmation_purchase',
[
'userId' => $user->getId(),
'orderType' => 'group',
'orderId' => $sessionOrder->getId(),
'_locale' => $request->getLocale(),
]
);
} else {
$this->addFlash('error', $error);
return $this->redirectToRoute('session_purchase', [
'id' => $session->getId(),
'_locale' => $request->getLocale(),
]);
}
} else {
$this->addFlash('error', "Le groupe est complet, merci d'en choisir un autre");
return $this->redirectToRoute('session_index', [
'gender' => $gender,
'_locale' => $request->getLocale(),
]);
}
}
return $this->render('session/purchase.html.twig', [
'stripe_public_key' => $this->getParameter('stripe_public_key'),
'session' => $session,
'currentUser' => $user,
'errorCard' => $error,
'code' => $code,
// 'plan' => $plan, // StripePlanForGroup no longer needed
'price' => $convertedAmount,
'isDiscount' => $isDiscount,
'currency' => $currency,
]);
}
/**
* @Route("/order/success/takallam", name="session_success_takallam", methods={"GET","POST"})
*/
public function successTakallam(Request $request)
{
return $this->render('session/registration-takallam-success.twig');
}
/**
* @Route("/coupon", name="session_coupon_add", methods={"POST"})
* @Security("is_granted('ROLE_USER')")
* @param Request $request
* @param StripeClient $stripeClient
* @param SessionSymfo $sessionSymfo
*
* @return RedirectResponse
*/
public function addCouponAction(Request $request, StripeClient $stripeClient, SessionSymfo $sessionSymfo): RedirectResponse
{
$referer = $request->headers->get('referer');
$code = $request->request->get('code');
if ( ! $code) {
$this->addFlash('error', 'Vous n\'avez entré aucun coupon');
return $this->redirect($referer);
}
try {
$stripeCoupon = $stripeClient->findCoupon($code);
} catch (\Stripe\Exception\InvalidRequestException $e) {
$this->addFlash('error', 'Coupon Invalide');
return $this->redirect($referer);
}
if ($stripeCoupon->percent_off != null) {
$this->addFlash('error', 'Coupon invalide pour ces cours');
return $this->redirect($referer);
}
if ( ! $stripeCoupon->valid) {
$this->addFlash('error', 'Coupon expiré');
return $this->redirect($referer);
}
$sessionSymfo->set('code-group', $stripeCoupon);
return $this->redirect($referer);
}
/**
* @param Invoice $invoice
* @param User $user
* @param Session $session
* @param $participant
* @param $paymentType
*/
private function prepareCreationSessionOrderWithInvoice(Invoice $invoice, User $user, Session $session, $participant, $paymentType)
{
$stripeCustomerId = $invoice->customer;
$stripeCustomer = \Stripe\Customer::retrieve($stripeCustomerId);
// Extract payment information from invoice (currency, amounts, fees)
$paymentInfo = $this->stripeMultiCurrencyService->extractPaymentInfoFromInvoice($invoice);
if ($paymentType == "twoTime") {
$stripeSubscriptionId = $invoice->subscription;
$stripeSubscription = \Stripe\Subscription::retrieve($stripeSubscriptionId);
$subscriptionStatus = $stripeSubscription->status;
if ($subscriptionStatus == 'active') {
// For two-time payments, the paid amount is the total (both payments)
$paidAmount = ($invoice->amount_paid / 100) * 2;
$nextPaymentTimeStamp = $stripeSubscription->current_period_end;
// For two-time payments, adjust payment info to reflect total amount
$paymentInfo['amountPaid'] = $paidAmount;
// USD amount and fees should also be doubled for two-time payments
if ($paymentInfo['amountReceivedUsd'] !== null) {
$paymentInfo['amountReceivedUsd'] = $paymentInfo['amountReceivedUsd'] * 2;
}
if ($paymentInfo['fees'] !== null) {
$paymentInfo['fees'] = $paymentInfo['fees'] * 2;
}
} else {
$this->addFlash('error', "Votre paiement n'a pas abouti");
return $this->redirectToRoute('session_purchase', [
'id' => $session->getId(),
'_locale' => $user->getLanguage()->getPole(),
]);
}
} else {
$paidAmount = $invoice->amount_paid / 100;
$stripeSubscription = null;
$nextPaymentTimeStamp = null;
$stripeSubscriptionId = null;
}
$cardDetails = $this->stripeClient->retrieveSource($stripeCustomerId, $stripeCustomer->default_source);
$isTwoTime = $paymentType == "twoTime";
$cardBrand = $cardDetails->brand;
$cardLastDigits = $cardDetails->last4;
$order = $this->addSessionOrderToBdd(
$session,
$user,
$stripeCustomerId,
$paidAmount,
$cardBrand,
$cardLastDigits,
$nextPaymentTimeStamp,
$stripeSubscriptionId,
$isTwoTime,
$participant,
$paymentInfo
);
return $order;
}
private function addSessionOrderToBdd(Session $session, User $user, $stripeCustomerId, $paidAmount, $cardBrand, $cardLastDigit, $nextPaymentTimeStamp, $stripeSubscriptionId,
bool $isTwoTime, $participant, array $paymentInfo = null
) {
$childRepo = $this->em->getRepository(Child::class);
if ($participant) {
$participant = $childRepo->findOneBy(['id' => $participant]);
} else {
$participant = null;
}
$order = new SessionOrder();
$order->setSession($session)
->setStripeCustomerId($stripeCustomerId)
->setAmountPaid($paidAmount)
->setIsActif(true)
->setCanceledByStudent(false)
->setCanceledByUs(false)
->setCardBrand($cardBrand)
->setCardLast4($cardLastDigit)
->setIsTwoTime($isTwoTime)
->setNextPaymentTimeStamp($nextPaymentTimeStamp)
->setStripeSubscriptionId($stripeSubscriptionId)
->setParticipant($participant)
->setStudents($user)
->setPaymentNb(1);
// Set multi-currency payment information if available
if ($paymentInfo !== null) {
$order->setPaymentCurrency($paymentInfo['currency'])
->setAmountPaidLocalCurrency($paymentInfo['amountPaid'])
->setAmountReceivedUsd($paymentInfo['amountReceivedUsd'])
->setStripeFees($paymentInfo['fees']);
}
$session->decrementSeat();
$this->em->persist($session);
$this->em->persist($order);
$this->em->flush();
$program = $order->getSession()->getLevel()->getProgramUrls();
$this->emailNotification->notifyStudentSessionOrder($order, $user, $program);
//$this->slackClient->notifySecretariatGroupSessionInscription($order, $user);
$this->createTakallamTrainingReportIfNeeded($order);
return $order;
}
private function createTakallamTrainingReportIfNeeded(SessionOrder $sessionOrder): void
{
$subject = $sessionOrder->getSession()->getLevel()->getSubject();
if ($subject->getTagName() !== 'takallam') {
return;
}
$trainingReport = new TrainingReport();
$trainingReport
->setUser($sessionOrder->getStudents())
->setParticipant($sessionOrder->getParticipant())
->setProgram($subject)
->setLevel($sessionOrder->getSession()->getLevel())
->setTeacher($sessionOrder->getSession()->getTeacher())
->setRelatedToGroupCourse($sessionOrder)
->setHadMidExam(false)
->setIsReadyToSend(false)
->setIsSent(false)
->setAskAt(new \DateTime());
$this->em->persist($trainingReport);
$this->em->flush();
}
}